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..8f9e562e0a 100644 --- a/.devcontainer/server/container-compose-overrides.yml +++ b/.devcontainer/server/container-compose-overrides.yml @@ -16,7 +16,7 @@ services: - ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data - /etc/localtime:/etc/localtime:ro - pnpm_store_server:/buildcache/pnpm-store - - ../plugins:/build/corePlugin + - ../packages/plugins:/build/corePlugin immich-web: env_file: !reset [] immich-machine-learning: 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/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 efd501200d..bfbc7bd2e2 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -90,6 +90,11 @@ jobs: persist-credentials: false token: ${{ steps.token.outputs.token }} + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + with: + github_token: ${{ steps.token.outputs.token }} + - name: Create the Keystore if: ${{ !github.event.pull_request.head.repo.fork }} env: @@ -111,16 +116,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: @@ -131,11 +128,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 @@ -175,7 +171,10 @@ jobs: Download: ${{ env.APK_URL }} - QR code +
+ 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. @@ -189,7 +188,6 @@ jobs: ~/.gradle/wrapper ~/.android/sdk mobile/android/.gradle - mobile/.dart_tool key: ${{ steps.cache-gradle-restore.outputs.cache-primary-key }} build-sign-ios: @@ -202,6 +200,12 @@ jobs: runs-on: macos-15 steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0 + 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 @@ -211,24 +215,20 @@ 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@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: - channel: 'stable' - flutter-version-file: ./mobile/pubspec.yaml - cache: true + github_token: ${{ steps.token.outputs.token }} - 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 - working-directory: ./mobile + run: mise //mobile:codegen:pigeon - name: Setup Ruby uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 diff --git a/.github/workflows/check-openapi.yml b/.github/workflows/check-openapi.yml index 47ac1d3730..07c7505762 100644 --- a/.github/workflows/check-openapi.yml +++ b/.github/workflows/check-openapi.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Check for breaking API changes - uses: oasdiff/oasdiff-action/breaking@37bf9ff785c7315df88216660826e71be4cc03da # v0.0.44 + uses: oasdiff/oasdiff-action/breaking@26ccb332c67a45ca649de9faf60552ef1b8260d9 # v0.0.46 with: base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json revision: open-api/immich-openapi-specs.json diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index a1d725296b..fd4b7f1abe 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,7 +28,7 @@ jobs: packages: write defaults: run: - working-directory: ./cli + working-directory: ./packages/cli steps: - id: token uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0 @@ -43,7 +43,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} @@ -89,7 +89,7 @@ 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 @@ -107,7 +107,7 @@ jobs: - name: Build and push image uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.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 55fd5ea0ca..b0b5258048 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:32abe582452b12dff55055e1d6bc24508a8f17164f9d1831db7bb70953c014c6 + image: ghcr.io/immich-app/mdq:main@sha256:0a8b8867773a0f8368061f47578603f438349f8f1f28b0e16105f481e5c794e0 outputs: checked: ${{ steps.get_checkbox.outputs.checked }} steps: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 827864cec5..f9e6dbfa2d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -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@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 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@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 # â„šī¸ 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@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 with: category: '/language:${{matrix.language}}' diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index c6afd7277d..a85435ea5a 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -66,7 +66,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index 5204b61d0e..083fa009eb 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -131,7 +131,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/docs-destroy.yml b/.github/workflows/docs-destroy.yml index db3d34f22f..4186438d43 100644 --- a/.github/workflows/docs-destroy.yml +++ b/.github/workflows/docs-destroy.yml @@ -29,7 +29,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/fix-format.yml b/.github/workflows/fix-format.yml index 6c90f99bf7..e718c13792 100644 --- a/.github/workflows/fix-format.yml +++ b/.github/workflows/fix-format.yml @@ -28,7 +28,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 5e547fb47a..4df27e581e 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -17,6 +17,6 @@ jobs: 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 82b5925947..d4fe794913 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -62,7 +62,7 @@ jobs: ref: main - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 6eb3742e7a..9502d940ea 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -14,9 +14,6 @@ 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@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0 @@ -31,15 +28,15 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: 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 + run: pnpm --filter @immich/sdk publish --provenance --no-git-checks diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 70a7294424..10642fbd11 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -60,38 +60,30 @@ 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@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: - channel: 'stable' - flutter-version-file: ./mobile/pubspec.yaml + github_token: ${{ steps.token.outputs.token }} - 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 + run: flutter 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 +99,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 ea4602c660..97bccbc9ba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,14 +33,14 @@ jobs: web: - 'web/**' - 'i18n/**' - - 'open-api/typescript-sdk/**' + - 'packages/sdk/**' - 'pnpm-lock.yaml' server: - 'server/**' - 'pnpm-lock.yaml' cli: - - 'cli/**' - - 'open-api/typescript-sdk/**' + - 'packages/cli/**' + - 'packages/sdk/**' - 'pnpm-lock.yaml' e2e: - 'e2e/**' @@ -79,7 +79,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} @@ -95,7 +95,7 @@ jobs: contents: read defaults: run: - working-directory: ./cli + working-directory: ./packages/cli steps: - id: token uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0 @@ -110,7 +110,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} @@ -126,7 +126,7 @@ jobs: contents: read defaults: run: - working-directory: ./cli + working-directory: ./packages/cli steps: - id: token uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0 @@ -140,20 +140,15 @@ 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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: - node-version-file: '.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 + github_token: ${{ steps.token.outputs.token }} - - name: Install deps + - 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. @@ -190,11 +185,11 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} - - name: Run setup typescript-sdk + - name: Run setup @immich/sdk run: mise run //:sdk:install && mise run //:sdk:build - name: Run pnpm install @@ -228,7 +223,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} @@ -256,15 +251,15 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: 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 @@ -306,7 +301,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} @@ -339,7 +334,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} @@ -384,20 +379,14 @@ jobs: 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: Setup packages + run: pnpm --filter "@immich/*" install --frozen-lockfile && pnpm --filter "@immich/*" 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() }} @@ -467,9 +456,8 @@ jobs: 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 @@ -563,17 +551,22 @@ jobs: 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@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 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 }} + + - 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 @@ -597,7 +590,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} @@ -628,7 +621,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} @@ -679,18 +672,15 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: 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 @@ -699,7 +689,7 @@ jobs: with: files: | mobile/openapi - open-api/typescript-sdk + packages/sdk open-api/immich-openapi-specs.json - name: Verify files have not changed @@ -744,7 +734,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0 + uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 with: github_token: ${{ steps.token.outputs.token }} @@ -782,7 +772,7 @@ jobs: exit 1 - name: Run SQL generation - run: pnpm sync:sql + run: mise //:sql env: DB_URL: postgres://postgres:postgres@localhost:5432/immich diff --git a/.github/workflows/weblate-lock.yml b/.github/workflows/weblate-lock.yml index b05a20360c..7063820839 100644 --- a/.github/workflows/weblate-lock.yml +++ b/.github/workflows/weblate-lock.yml @@ -36,7 +36,7 @@ jobs: github-token: ${{ steps.token.outputs.token }} filters: | i18n: - - modified: 'i18n/!(en|package)**\.json' + - modified: 'i18n/!(en)**\.json' skip-force-logic: 'true' enforce-lock: 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/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/Makefile b/Makefile index 4d76913d8f..648aed5120 100644 --- a/Makefile +++ b/Makefile @@ -37,105 +37,24 @@ prod-scale: .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"\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"\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/" clean: find . -name "node_modules" -type d -prune -exec rm -rf {} + @@ -146,7 +65,3 @@ clean: 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 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/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..dfb876e6bd 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -25,10 +25,10 @@ services: - 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 @@ -74,7 +74,7 @@ services: - ${UPLOAD_LOCATION}/photos:/data - /etc/localtime:/etc/localtime:ro - pnpm_store_server:/buildcache/pnpm-store - - ../plugins:/build/corePlugin + - ../packages/plugins:/build/corePlugin env_file: - .env environment: @@ -157,7 +157,7 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 + image: docker.io/valkey/valkey:9@sha256:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193 healthcheck: test: redis-cli ping || exit 1 diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index d85186b6f1..24ecb02624 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:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193 healthcheck: test: redis-cli ping || exit 1 restart: always diff --git a/docker/docker-compose.rootless.yml b/docker/docker-compose.rootless.yml index 9e600289b9..3f3e53424b 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:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193 user: '1000:1000' security_opt: - no-new-privileges:true diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 610b375011..5f3ad35245 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:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193 healthcheck: test: redis-cli ping || exit 1 restart: always 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/devcontainers.md b/docs/docs/developer/devcontainers.md index 4bd60262ad..99f340c557 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,20 +252,11 @@ To connect the mobile app to your Dev Container: The Dev Container supports multiple ways to run tests: -#### Using Make Commands (Recommended) +#### Using Mise 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 - -# Run all tests -make test-all # Runs tests for all components - -# Medium tests (integration tests) -make test-medium-dev # End-to-end tests +mise run checklist # in `server/`, `web/`, `packages/cli` ``` #### Using PNPM Directly @@ -289,48 +280,16 @@ 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 # 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 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..c4ed44c77b 100644 --- a/docs/docs/developer/pr-checklist.md +++ b/docs/docs/developer/pr-checklist.md @@ -34,21 +34,23 @@ Run all web checks with `pnpm run check:all` Run all server checks with `pnpm run check:all` ::: -:::info Auto Fix +:::tip Auto Fix You can use `pnpm run __:fix` to potentially correct some issues automatically for `pnpm run format` and `lint`. ::: -## 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`. ::: ## OpenAPI diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md index abdb3befbe..c5d782fb52 100644 --- a/docs/docs/developer/setup.md +++ b/docs/docs/developer/setup.md @@ -58,7 +58,7 @@ You can access the web from `http://your-machine-ip:3000` or `http://localhost:3 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 -` +1. Build the Immich SDK - `pnpm --filter @immich/sdk install && pnpm --filter @immich/sdk build` 2. Enter the web directory - `cd web/` 3. Install web dependencies - `pnpm i` 4. Start the web development server diff --git a/docs/docs/developer/testing.md b/docs/docs/developer/testing.md index d7c9edcd31..219c33d1a1 100644 --- a/docs/docs/developer/testing.md +++ b/docs/docs/developer/testing.md @@ -17,15 +17,14 @@ 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 `/`) +- `pnpm install` +- `pnpm --filter "@immich/*" build` +- `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/install/config-file.md b/docs/docs/install/config-file.md index b0491a21e3..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"], 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/e2e/docker-compose.yml b/e2e/docker-compose.yml index c8a3b975d4..0ccd54cf3f 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:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193 healthcheck: test: redis-cli ping || exit 1 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..09d33b735b 100644 --- a/e2e/src/specs/server/api/search.e2e-spec.ts +++ b/e2e/src/specs/server/api/search.e2e-spec.ts @@ -441,7 +441,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/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..52dfa4c493 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: [], @@ -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/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/utils.ts b/e2e/src/utils.ts index aa4c3b8499..74c2832c3e 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(' ')}`]); diff --git a/i18n/en.json b/i18n/en.json index 5e073559ac..697aa7f2fa 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -885,15 +885,13 @@ "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", "day": "Day", @@ -1403,6 +1401,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", @@ -1582,8 +1581,8 @@ "mobile_app_download_onboarding_note": "Download the companion mobile app using the following options", "model": "Model", "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", @@ -1893,6 +1892,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", @@ -2192,6 +2192,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", @@ -2207,6 +2208,9 @@ "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", 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/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.toml b/mise.toml index 5aa7054c27..f190490f17 100644 --- a/mise.toml +++ b/mise.toml @@ -2,9 +2,9 @@ experimental_monorepo_root = true [monorepo] config_roots = [ - "plugins", + "packages/plugins", "server", - "cli", + "packages/cli", "deployment", "mobile", "e2e", @@ -21,11 +21,12 @@ pnpm = "10.33.1" terragrunt = "1.0.3" opentofu = "1.11.6" java = "21.0.2" +"npm:oazapfts" = "7.5.0" [tools."github:CQLabs/homebrew-dcm"] 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:jellyfin/jellyfin-ffmpeg"] version = "7.1.3-6" @@ -40,20 +41,43 @@ macos-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz" experimental = true pin = true +[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 = "//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" + # 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" 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/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/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 95fa57d4d2..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,7 +2750,7 @@ } }, { - "id": 32, + "id": 30, "references": [], "type": "table", "data": { @@ -2826,13 +2798,13 @@ } }, { - "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, @@ -2840,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": [ @@ -2861,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, @@ -2882,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, @@ -2896,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, @@ -2910,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, @@ -2924,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, @@ -2940,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, @@ -2954,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, @@ -3066,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": [ @@ -3112,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)" } ] }, @@ -3282,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": [ @@ -3327,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/ios/Runner/Background/BackgroundWorker.g.swift b/mobile/ios/Runner/Background/BackgroundWorker.g.swift index 40553441a6..bd01e953f9 100644 --- a/mobile/ios/Runner/Background/BackgroundWorker.g.swift +++ b/mobile/ios/Runner/Background/BackgroundWorker.g.swift @@ -348,7 +348,7 @@ class BackgroundWorkerBgHostApiSetup { /// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. protocol BackgroundWorkerFlutterApiProtocol { func onIosUpload(isRefresh isRefreshArg: Bool, maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result) -> 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/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..a810877dcc 100644 --- a/mobile/lib/domain/models/asset/remote_asset.model.dart +++ b/mobile/lib/domain/models/asset/remote_asset.model.dart @@ -10,6 +10,7 @@ class RemoteAsset extends BaseAsset { final AssetVisibility visibility; final String ownerId; final String? stackId; + final DateTime? uploadedAt; const RemoteAsset({ required this.id, @@ -20,6 +21,7 @@ class RemoteAsset extends BaseAsset { required super.type, required super.createdAt, required super.updatedAt, + this.uploadedAt, super.width, super.height, super.durationMs, @@ -55,6 +57,7 @@ class RemoteAsset extends BaseAsset { type: $type, createdAt: $createdAt, updatedAt: $updatedAt, + uploadedAt: ${uploadedAt ?? ""}, width: ${width ?? ""}, height: ${height ?? ""}, durationMs: ${durationMs ?? ""}, @@ -71,14 +74,19 @@ 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; } @override @@ -89,7 +97,8 @@ class RemoteAsset extends BaseAsset { localId.hashCode ^ thumbHash.hashCode ^ visibility.hashCode ^ - stackId.hashCode; + stackId.hashCode ^ + uploadedAt.hashCode; RemoteAsset copyWith({ String? id, @@ -100,6 +109,7 @@ class RemoteAsset extends BaseAsset { AssetType? type, DateTime? createdAt, DateTime? updatedAt, + DateTime? uploadedAt, int? width, int? height, int? durationMs, @@ -119,6 +129,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, @@ -144,6 +155,7 @@ class RemoteAssetExif extends RemoteAsset { required super.type, required super.createdAt, required super.updatedAt, + super.uploadedAt, super.width, super.height, super.durationMs, @@ -158,8 +170,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 +192,7 @@ class RemoteAssetExif extends RemoteAsset { AssetType? type, DateTime? createdAt, DateTime? updatedAt, + DateTime? uploadedAt, int? width, int? height, int? durationMs, @@ -196,6 +213,7 @@ class RemoteAssetExif extends RemoteAsset { 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, 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/app_config.dart b/mobile/lib/domain/models/config/app_config.dart index 7179137af4..beca1c21e7 100644 --- a/mobile/lib/domain/models/config/app_config.dart +++ b/mobile/lib/domain/models/config/app_config.dart @@ -1,22 +1,58 @@ 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/theme_config.dart'; +import 'package:immich_mobile/domain/models/config/timeline_config.dart'; +import 'package:immich_mobile/domain/models/config/viewer_config.dart'; class AppConfig { final ThemeConfig theme; final CleanupConfig cleanup; + final MapConfig map; + final TimelineConfig timeline; + final ImageConfig image; + final ViewerConfig viewer; - const AppConfig({this.theme = const .new(), this.cleanup = const .new()}); + const AppConfig({ + this.theme = const .new(), + this.cleanup = const .new(), + this.map = const .new(), + this.timeline = const .new(), + this.image = const .new(), + this.viewer = const .new(), + }); - AppConfig copyWith({ThemeConfig? theme, CleanupConfig? cleanup}) => - .new(theme: theme ?? this.theme, cleanup: cleanup ?? this.cleanup); + AppConfig copyWith({ + ThemeConfig? theme, + CleanupConfig? cleanup, + MapConfig? map, + TimelineConfig? timeline, + ImageConfig? image, + ViewerConfig? viewer, + }) => .new( + theme: theme ?? this.theme, + cleanup: cleanup ?? this.cleanup, + map: map ?? this.map, + timeline: timeline ?? this.timeline, + image: image ?? this.image, + viewer: viewer ?? this.viewer, + ); @override bool operator ==(Object other) => - identical(this, other) || (other is AppConfig && other.theme == theme && other.cleanup == cleanup); + identical(this, other) || + (other is AppConfig && + other.theme == theme && + other.cleanup == cleanup && + other.map == map && + other.timeline == timeline && + other.image == image && + other.viewer == viewer); @override - int get hashCode => Object.hash(theme, cleanup); + int get hashCode => Object.hash(theme, cleanup, map, timeline, image, viewer); @override - String toString() => 'AppConfig(theme: $theme, cleanup: $cleanup)'; + String toString() => + 'AppConfig(theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer)'; } 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/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/metadata_key.dart b/mobile/lib/domain/models/metadata_key.dart index 667822663a..61a3cebc8a 100644 --- a/mobile/lib/domain/models/metadata_key.dart +++ b/mobile/lib/domain/models/metadata_key.dart @@ -7,6 +7,7 @@ import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/config/app_config.dart'; import 'package:immich_mobile/domain/models/config/system_config.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; +import 'package:immich_mobile/domain/models/timeline.model.dart'; enum MetadataDomain { appConfig('config.app'), @@ -23,9 +24,36 @@ enum MetadataKey { themeDynamic(.appConfig, 'theme.dynamic', false), themeColorfulInterface(.appConfig, 'theme.colorfulInterface', true), + // Image + imagePreferRemote(.appConfig, 'image.preferRemote', false), + imageLoadOriginal(.appConfig, 'image.loadOriginal', false), + + // Viewer + viewerLoopVideo(.appConfig, 'viewer.loopVideo', true), + viewerLoadOriginalVideo(.appConfig, 'viewer.loadOriginalVideo', false), + viewerAutoPlayVideo(.appConfig, 'viewer.autoPlayVideo', true), + viewerTapToNavigate(.appConfig, 'viewer.tapToNavigate', false), + + // Timeline + timelineTilesPerRow(.appConfig, 'timeline.tilesPerRow', 4), + timelineGroupAssetsBy( + .appConfig, + 'timeline.groupAssetsBy', + GroupAssetsBy.day, + _EnumCodec(GroupAssetsBy.values), + ), + timelineStorageIndicator(.appConfig, 'timeline.storageIndicator', true), + // Log logLevel(.systemConfig, 'log.level', .info, _EnumCodec(LogLevel.values)), + // Map + mapShowFavoriteOnly(.appConfig, 'map.showFavoriteOnly', false), + mapRelativeDate(.appConfig, 'map.relativeDate', 0), + mapIncludeArchived(.appConfig, 'map.includeArchived', false), + mapThemeMode(.appConfig, 'map.themeMode', .system, _EnumCodec(ThemeMode.values)), + mapWithPartners(.appConfig, 'map.withPartners', false), + // Cleanup cleanupKeepFavorites(.appConfig, 'cleanup.keepFavorites', true), cleanupKeepMediaType( @@ -115,12 +143,18 @@ final class _ListCodec extends _MetadataCodec> { List? decode(String raw) { try { final decoded = jsonDecode(raw); - if (decoded is! List) return null; + if (decoded is! List) { + return null; + } final result = []; for (final item in decoded) { - if (item is! String) return null; + if (item is! String) { + return null; + } final element = _elementCodec.decode(item); - if (element == null) return null; + if (element == null) { + return null; + } result.add(element); } return result; 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..0dc48de3b1 100644 --- a/mobile/lib/domain/models/setting.model.dart +++ b/mobile/lib/domain/models/setting.model.dart @@ -1,13 +1,6 @@ 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); 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 8a7cd3a71e..63281e49da 100644 --- a/mobile/lib/domain/models/store.model.dart +++ b/mobile/lib/domain/models/store.model.dart @@ -4,50 +4,18 @@ 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), - tilesPerRow._(103), - dynamicLayout._(104), - groupAssetsBy._(105), - uploadErrorNotificationGracePeriod._(106), - backgroundBackupTotalProgress._(107), - backgroundBackupSingleProgress._(108), - storageIndicator._(109), - thumbnailCacheSize._(110), - imageCacheSize._(111), - albumThumbnailCacheSize._(112), selectedAlbumSortOrder._(113), advancedTroubleshooting._(114), - 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), - syncAlbums._(131), // Auto endpoint switching @@ -56,34 +24,24 @@ enum StoreKey { localEndpoint._(134), externalEndpointList._(135), - // Video settings - loadOriginalVideo._(136), manageLocalMediaAndroid._(137), - // Read-only Mode settings readonlyModeEnabled._(138), - - autoPlayVideo._(139), albumGridView._(140), - // 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 syncMigrationStatus._(1013), // Legacy keys that have been migrated to the new metadata store + legacyLoopVideo._(117), + legacyLoadOriginalVideo._(136), + legacyAutoPlayVideo._(139), + legacyTapToNavigate._(141), + legacyPreferRemoteImage._(116), + legacyLoadOriginal._(101), legacyPrimaryColor._(128), legacyDynamicTheme._(129), legacyColorfulInterface._(130), @@ -93,6 +51,14 @@ enum StoreKey { 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); @@ -117,7 +83,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/background_worker.service.dart b/mobile/lib/domain/services/background_worker.service.dart index a2e96f2313..0c8746700c 100644 --- a/mobile/lib/domain/services/background_worker.service.dart +++ b/mobile/lib/domain/services/background_worker.service.dart @@ -105,46 +105,58 @@ 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 Future onIosUpload(bool isRefresh, int? maxSeconds) async { - _logger.info('iOS background upload started with maxSeconds: ${maxSeconds}s'); + final hashTimeout = isRefresh ? const Duration(seconds: 5) : Duration(minutes: _isBackupEnabled ? 3 : 6); + final backupTimeout = maxSeconds != null ? Duration(seconds: maxSeconds - 1) : null; + return _backgroundLoop(hashTimeout: hashTimeout, backupTimeout: backupTimeout, debugLabel: 'iOS background upload'); + } + + 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 { - final timeout = isRefresh ? const Duration(seconds: 5) : Duration(minutes: _isBackupEnabled ? 3 : 6); - if (!await _syncAssets(hashTimeout: timeout)) { + if (!await _syncAssets(hashTimeout: hashTimeout)) { _logger.warning("Remote sync did not complete successfully, skipping backup"); return; } final backupFuture = _handleBackup(); - if (maxSeconds != null) { - await backupFuture.timeout(Duration(seconds: maxSeconds - 1), onTimeout: () {}); - } else { + 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 iOS background upload", error, stack); + _logger.severe("Failed to complete $debugLabel", error, stack); } finally { sw.stop(); - _logger.info("iOS background upload completed in ${sw.elapsed.inSeconds}s"); + _logger.info("$debugLabel completed in ${sw.elapsed.inSeconds}s"); await _cleanup(); } } @@ -177,7 +189,9 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { final nativeSyncApi = _ref?.read(nativeSyncApiProvider); _logger.info("Cleaning up background worker"); - _cancellationToken.complete(); + if (!_cancellationToken.isCompleted) { + _cancellationToken.complete(); + } final cleanupFutures = [ nativeSyncApi?.cancelHashing(), workerManagerPatch.dispose().catchError((_) async { diff --git a/mobile/lib/domain/services/local_sync.service.dart b/mobile/lib/domain/services/local_sync.service.dart index 1d9ab1e490..34300dee3d 100644 --- a/mobile/lib/domain/services/local_sync.service.dart +++ b/mobile/lib/domain/services/local_sync.service.dart @@ -93,8 +93,7 @@ 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) { final dbAlbum = dbAlbums.firstWhereOrNull((a) => a.id == album.id); diff --git a/mobile/lib/domain/services/remote_album.service.dart b/mobile/lib/domain/services/remote_album.service.dart index f060ba9290..d0af52dcfd 100644 --- a/mobile/lib/domain/services/remote_album.service.dart +++ b/mobile/lib/domain/services/remote_album.service.dart @@ -184,7 +184,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/store.service.dart b/mobile/lib/domain/services/store.service.dart index b325ffd631..16ed64e6d3 100644 --- a/mobile/lib/domain/services/store.service.dart +++ b/mobile/lib/domain/services/store.service.dart @@ -72,7 +72,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_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index 906e352b49..9c8bac4c92 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.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 { @@ -132,6 +133,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 { @@ -318,7 +326,9 @@ class SyncStreamService { } Future handleWsAssetUploadReadyV1Batch(List batchData) async { - if (batchData.isEmpty) return; + if (batchData.isEmpty) { + return; + } _logger.info('Processing batch of ${batchData.length} AssetUploadReadyV1 events'); @@ -359,7 +369,9 @@ class SyncStreamService { } Future handleWsAssetUploadReadyV2Batch(List batchData) async { - if (batchData.isEmpty) return; + if (batchData.isEmpty) { + return; + } _logger.info('Processing batch of ${batchData.length} AssetUploadReadyV2 events'); diff --git a/mobile/lib/domain/services/timeline.service.dart b/mobile/lib/domain/services/timeline.service.dart index a055f8bcae..5779ee1053 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/metadata.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; import 'package:immich_mobile/utils/async_mutex.dart'; @@ -35,18 +34,21 @@ enum TimelineOrigin { deepLink, albumActivities, folder, + recentlyAdded, } class TimelineFactory { final DriftTimelineRepository _timelineRepository; - final SettingsService _settingsService; + final MetadataRepository _metadataRepository; - const TimelineFactory({required DriftTimelineRepository timelineRepository, required SettingsService settingsService}) - : _timelineRepository = timelineRepository, - _settingsService = settingsService; + const TimelineFactory({ + required DriftTimelineRepository timelineRepository, + required MetadataRepository metadataRepository, + }) : _timelineRepository = timelineRepository, + _metadataRepository = metadataRepository; GroupAssetsBy get groupBy { - final group = GroupAssetsBy.values[_settingsService.get(Setting.groupAssetsBy)]; + final group = _metadataRepository.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 +63,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)); diff --git a/mobile/lib/domain/services/user.service.dart b/mobile/lib/domain/services/user.service.dart index 1f9c015ad7..e7b4b0f4e6 100644 --- a/mobile/lib/domain/services/user.service.dart +++ b/mobile/lib/domain/services/user.service.dart @@ -30,7 +30,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/extensions/asset_extensions.dart b/mobile/lib/extensions/asset_extensions.dart index 3a994f9cb8..7e1bef1a1c 100644 --- a/mobile/lib/extensions/asset_extensions.dart +++ b/mobile/lib/extensions/asset_extensions.dart @@ -11,6 +11,7 @@ extension DTOToAsset on api.AssetResponseDto { checksum: checksum, createdAt: fileCreatedAt, updatedAt: updatedAt, + uploadedAt: createdAt, ownerId: ownerId, visibility: visibility.toAssetVisibility(), durationMs: duration, @@ -33,6 +34,7 @@ extension DTOToAsset on api.AssetResponseDto { checksum: checksum, createdAt: fileCreatedAt, updatedAt: updatedAt, + uploadedAt: createdAt, ownerId: ownerId, visibility: visibility.toAssetVisibility(), durationMs: duration, 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/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/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/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..8644667168 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, 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/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 b291207e42..e81fe58ba9 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.dart @@ -98,7 +98,7 @@ class Drift extends $Drift { } @override - int get schemaVersion => 25; + int get schemaVersion => 26; @override MigrationStrategy get migration => MigrationStrategy( @@ -266,6 +266,15 @@ class Drift extends $Drift { }, from24To25: (m, v25) async { 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); }, ), ); diff --git a/mobile/lib/infrastructure/repositories/db.repository.drift.dart b/mobile/lib/infrastructure/repositories/db.repository.drift.dart index 01f44372fb..c43a83f86a 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.drift.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.drift.dart @@ -113,13 +113,11 @@ abstract class $Drift extends i0.GeneratedDatabase { i4.idxLocalAssetChecksum, i4.idxLocalAssetCloudId, i3.idxStackPrimaryAssetId, - i2.idxRemoteAssetOwnerChecksum, i2.uQRemoteAssetsOwnerChecksum, i2.uQRemoteAssetsOwnerLibraryChecksum, i2.idxRemoteAssetChecksum, i2.idxRemoteAssetStackId, - i2.idxRemoteAssetLocalDateTimeDay, - i2.idxRemoteAssetLocalDateTimeMonth, + i2.idxRemoteAssetOwnerVisibilityDeletedCreated, authUserEntity, userMetadataEntity, partnerEntity, @@ -137,11 +135,13 @@ abstract class $Drift extends i0.GeneratedDatabase { metadataEntity, i10.idxPartnerSharedWithId, i11.idxLatLng, + i11.idxRemoteExifCity, i12.idxRemoteAlbumAssetAlbumAsset, i14.idxRemoteAssetCloudId, i17.idxPersonOwnerId, i18.idxAssetFacePersonId, i18.idxAssetFaceAssetId, + i18.idxAssetFaceVisiblePerson, i20.idxTrashedLocalAssetChecksum, i20.idxTrashedLocalAssetAlbum, i21.idxAssetEditAssetId, diff --git a/mobile/lib/infrastructure/repositories/db.repository.steps.dart b/mobile/lib/infrastructure/repositories/db.repository.steps.dart index 4237cc41e3..1fb88de1d0 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, @@ -12414,11 +12412,13 @@ final class Schema25 extends i0.VersionedSchema { 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( @@ -12883,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)', @@ -12903,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)', @@ -12943,6 +12943,602 @@ i1.GeneratedColumn _column_211(String aliasedName) => 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', + ); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, @@ -12968,6 +13564,7 @@ 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, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -13091,6 +13688,11 @@ 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; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -13122,6 +13724,7 @@ 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, }) => i0.VersionedSchema.stepByStepHelper( step: migrationSteps( from1To2: from1To2, @@ -13148,5 +13751,6 @@ i1.OnUpgrade stepByStep({ from22To23: from22To23, from23To24: from23To24, from24To25: from24To25, + from25To26: from25To26, ), ); diff --git a/mobile/lib/infrastructure/repositories/metadata.repository.dart b/mobile/lib/infrastructure/repositories/metadata.repository.dart index 6dafa5f02a..d8c8f55898 100644 --- a/mobile/lib/infrastructure/repositories/metadata.repository.dart +++ b/mobile/lib/infrastructure/repositories/metadata.repository.dart @@ -48,7 +48,9 @@ class MetadataRepository extends DriftDatabaseRepository { T _read(MetadataKey key) => (_cache[key] as T?) ?? key.defaultValue; Future write(MetadataKey key, U value) async { - if (_read(key) == value) return; + if (_read(key) == value) { + return; + } await _db .into(_db.metadataEntity) @@ -79,13 +81,17 @@ class MetadataRepository extends DriftDatabaseRepository { final keyMap = MetadataKey.asKeyMap(); for (final row in rows) { final key = keyMap[row.key]; - if (key == null) continue; + if (key == null) { + continue; + } _updateCache(key, key.decode(row.value)); } } void _updateCache(MetadataKey key, T value) { - if (_cache[key] == value) return; + if (_cache[key] == value) { + return; + } _cache[key] = value; key.domain.rebuild(this); } @@ -114,6 +120,25 @@ extension on MetadataDomain { cutoffDaysAgo: repo._read(.cleanupCutoffDaysAgo), defaultsInitialized: repo._read(.cleanupDefaultsInitialized), ), + map: .new( + relativeDays: repo._read(.mapRelativeDate), + favoritesOnly: repo._read(.mapShowFavoriteOnly), + includeArchived: repo._read(.mapIncludeArchived), + themeMode: repo._read(.mapThemeMode), + withPartners: repo._read(.mapWithPartners), + ), + timeline: .new( + tilesPerRow: repo._read(.timelineTilesPerRow), + groupAssetsBy: repo._read(.timelineGroupAssetsBy), + storageIndicator: repo._read(.timelineStorageIndicator), + ), + image: .new(preferRemote: repo._read(.imagePreferRemote), loadOriginal: repo._read(.imageLoadOriginal)), + viewer: .new( + loopVideo: repo._read(.viewerLoopVideo), + loadOriginalVideo: repo._read(.viewerLoadOriginalVideo), + autoPlayVideo: repo._read(.viewerAutoPlayVideo), + tapToNavigate: repo._read(.viewerTapToNavigate), + ), ); case .systemConfig: repo._systemConfig = .new(logLevel: repo._read(.logLevel)); diff --git a/mobile/lib/infrastructure/repositories/remote_album.repository.dart b/mobile/lib/infrastructure/repositories/remote_album.repository.dart index 19ebcaac45..c3b972d85f 100644 --- a/mobile/lib/infrastructure/repositories/remote_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_album.repository.dart @@ -360,7 +360,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/sync_api.repository.dart b/mobile/lib/infrastructure/repositories/sync_api.repository.dart index 366803ee31..d9d262e64f 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'; @@ -38,7 +36,6 @@ 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)); request.headers.addAll(headers); request.body = jsonEncode( @@ -77,7 +74,6 @@ class SyncApiRepository { ? SyncRequestType.assetFacesV2 : SyncRequestType.assetFacesV1, ], - reset: shouldReset, ).toJson(), ); @@ -101,9 +97,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 d9b9b3fa97..b7593c3202 100644 --- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart @@ -14,6 +14,7 @@ import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.da import 'package:immich_mobile/infrastructure/entities/asset_face.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'; @@ -45,25 +46,35 @@ 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.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(); + }); + } 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); @@ -191,6 +202,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), @@ -229,6 +241,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/timeline.repository.dart b/mobile/lib/infrastructure/repositories/timeline.repository.dart index d1a2538b6a..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, @@ -317,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() & @@ -597,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, @@ -609,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); @@ -616,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]) @@ -692,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/main.dart b/mobile/lib/main.dart index 14d09e4cb7..19455be61c 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -179,19 +179,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 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/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/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..93a1d629b8 100644 --- a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart +++ b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart @@ -83,7 +83,9 @@ class _DriftBackupAlbumSelectionPageState 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..1fa183456c 100644 --- a/mobile/lib/pages/common/headers_settings.page.dart +++ b/mobile/lib/pages/common/headers_settings.page.dart @@ -93,7 +93,9 @@ class HeaderSettingsPage extends HookConsumerWidget { 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; } 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..261c6975ef 100644 --- a/mobile/lib/pages/library/shared_link/shared_link.page.dart +++ b/mobile/lib/pages/library/shared_link/shared_link.page.dart @@ -20,7 +20,9 @@ class SharedLinkPage extends HookConsumerWidget { useEffect(() { ref.read(sharedLinksStateProvider.notifier).fetchLinks(); return () { - if (!context.mounted) return; + if (!context.mounted) { + return; + } ref.invalidate(sharedLinksStateProvider); }; }, []); 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/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_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..09c7912bc6 100644 --- a/mobile/lib/presentation/pages/drift_remote_album.page.dart +++ b/mobile/lib/presentation/pages/drift_remote_album.page.dart @@ -245,7 +245,9 @@ 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(); 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 c5d7d0f36d..7b747738dd 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'; @@ -708,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; @@ -735,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( @@ -875,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..ecfe4a60fe 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 @@ -50,7 +50,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; 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/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_permanent_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart index 27a1a4d8af..d2df013369 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 @@ -33,7 +33,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()); 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/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/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 73b23b6414..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 @@ -111,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, @@ -121,17 +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; + 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 bfd9738dc7..77f693f5c4 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart @@ -14,13 +14,12 @@ import 'package:immich_mobile/extensions/scroll_extensions.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_details.widget.dart'; 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/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/metadata.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart'; import 'package:immich_mobile/widgets/photo_view/photo_view.dart'; @@ -62,7 +61,9 @@ class _AssetPageState extends ConsumerState { super.initState(); _eventSubscription = EventStream.shared.listen(_onEvent); WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted || !_scrollController.hasClients) return; + if (!mounted || !_scrollController.hasClients) { + return; + } _scrollController.snapPosition.snapOffset = _snapOffset; if (_showingDetails && _snapOffset > 0) { _scrollController.jumpTo(_snapOffset); @@ -87,7 +88,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); } @@ -128,7 +131,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) { @@ -141,7 +146,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(); @@ -151,7 +158,9 @@ class _AssetPageState extends ConsumerState { } void _endDrag(DragEndDetails details) { - if (_dragStart == null) return; + if (_dragStart == null) { + return; + } final start = _dragStart; _dragStart = null; @@ -188,7 +197,9 @@ class _AssetPageState extends ConsumerState { PhotoViewControllerBase controller, PhotoViewScaleStateController scaleStateController, ) { - if (!_showingDetails && _isZoomed) return; + if (!_showingDetails && _isZoomed) { + return; + } _beginDrag(details); } @@ -215,9 +226,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(metadataProvider).appConfig.viewer.tapToNavigate; if (!tapToNavigate) { _viewer.toggleControls(); return; @@ -247,31 +260,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); 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/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/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/video_viewer.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart index fb5c02599d..97ca8ace10 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart @@ -3,21 +3,17 @@ import 'dart:async'; 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/metadata.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'; @@ -61,7 +57,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 +83,35 @@ 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 { 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 +128,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(metadataProvider).appConfig.viewer.loadOriginalVideo; final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback'; final String videoUrl = videoAsset.livePhotoVideoId != null ? '$serverEndpoint/assets/${videoAsset.livePhotoVideoId}/$postfixUrl' @@ -134,25 +142,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(metadataProvider).appConfig.viewer.autoPlayVideo; + if (autoPlayVideo || widget.asset.isMotionPhoto) { + await _notifier.play(); + } } void _onPlaybackEnded() { - if (!mounted) return; + if (!mounted) { + return; + } _notifier.onNativePlaybackEnded(); @@ -162,12 +180,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 +202,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(metadataProvider).appConfig.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 +231,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..308f6a72a3 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 @@ -4,8 +4,8 @@ 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'; @@ -48,10 +48,9 @@ class ViewerKebabMenu extends ConsumerWidget { source: ActionSource.viewer, isCasting: isCasting, timelineOrigin: timelineOrigin, - originalTheme: originalTheme, ); - final menuChildren = ActionButtonBuilder.buildViewerKebabMenu(actionContext, context, ref); + final menuChildren = ActionButtonBuilder.buildViewerKebabMenu(actionContext, context); return MenuAnchor( consumeOutsideTap: true, @@ -67,10 +66,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 42d11a8063..3b158c63a8 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,4 +1,5 @@ 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'; @@ -10,11 +11,13 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_act 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/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}); @@ -95,16 +98,17 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { ), SafeArea( bottom: false, - child: SizedBox.square( + child: SizedBox( + height: preferredSize.height, child: Theme( data: context.themeData.copyWith(iconTheme: const IconThemeData(size: 22, color: Colors.white)), - child: Row( - children: [ - const _AppBarBackButton(), - const Spacer(), - if (!showingDetails && !isReadonlyModeEnabled) - if (isInLockedView) ...lockedViewActions else ...actions, - ], + 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, ), ), ), @@ -139,3 +143,32 @@ class _AppBarBackButton extends ConsumerWidget { ); } } + +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/images/image_provider.dart b/mobile/lib/presentation/widgets/images/image_provider.dart index ea416d9d71..9364fdd091 100644 --- a/mobile/lib/presentation/widgets/images/image_provider.dart +++ b/mobile/lib/presentation/widgets/images/image_provider.dart @@ -3,9 +3,8 @@ 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/metadata.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'; @@ -189,4 +188,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 || !MetadataRepository.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..6376e07405 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/metadata.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..18beb89b58 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)); diff --git a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart index 406ca30820..8720cc4253 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/metadata.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; diff --git a/mobile/lib/presentation/widgets/map/map.state.dart b/mobile/lib/presentation/widgets/map/map.state.dart index bfd3011050..b90ce922aa 100644 --- a/mobile/lib/presentation/widgets/map/map.state.dart +++ b/mobile/lib/presentation/widgets/map/map.state.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/models/metadata_key.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/metadata.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 +81,38 @@ class MapStateNotifier extends Notifier { } void switchFavoriteOnly(bool isFavoriteOnly) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapShowFavoriteOnly, isFavoriteOnly); + ref.read(metadataProvider).write(MetadataKey.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(metadataProvider).write(MetadataKey.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(metadataProvider).write(MetadataKey.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(metadataProvider).write(MetadataKey.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..126254e687 100644 --- a/mobile/lib/presentation/widgets/timeline/fixed/row.dart +++ b/mobile/lib/presentation/widgets/timeline/fixed/row.dart @@ -77,7 +77,9 @@ class RenderFixedRow extends RenderBox double _height; set height(double value) { - if (_height == value) return; + if (_height == value) { + return; + } _height = value; markNeedsLayout(); } @@ -86,7 +88,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 +99,9 @@ class RenderFixedRow extends RenderBox double _spacing; set spacing(double value) { - if (_spacing == value) return; + if (_spacing == value) { + return; + } _spacing = value; markNeedsLayout(); } @@ -104,7 +110,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..250cea8229 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; }); diff --git a/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart b/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart index f0dfef571c..c2905bcafa 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; } 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..7b88800f22 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/metadata.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..8974b20d1a 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -10,7 +10,7 @@ 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/metadata_key.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 +22,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/metadata.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'; @@ -74,7 +74,7 @@ class Timeline extends StatelessWidget { (ref) => TimelineArgs( maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight, - columnCount: ref.watch(settingsProvider.select((s) => s.get(Setting.tilesPerRow))), + columnCount: ref.watch(appConfigProvider.select((config) => config.timeline.tilesPerRow)), showStorageIndicator: showStorageIndicator, withStack: withStack, groupBy: groupBy, @@ -161,7 +161,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 +201,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 +331,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!; @@ -399,7 +403,9 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { segments: segments, delegate: SliverChildBuilderDelegate( (ctx, index) { - if (index >= childCount) return null; + if (index >= childCount) { + return null; + } final segment = segments.findByIndex(index); return segment?.builder(ctx, index) ?? const SizedBox.shrink(); }, @@ -453,7 +459,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { _restoreAssetIndex = targetAssetIndex; }); - ref.read(settingsProvider.notifier).set(Setting.tilesPerRow, _perRow); + ref.read(metadataProvider).write(MetadataKey.timelineTilesPerRow, _perRow); } }; }, 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/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index a5f67215a8..11e0dcd49c 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -65,7 +65,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; diff --git a/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart b/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart index 96ff5f704a..9f3b05b9b0 100644 --- a/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart +++ b/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart @@ -45,8 +45,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 && @@ -83,7 +87,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); } @@ -138,6 +144,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/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..4c2a110fde 100644 --- a/mobile/lib/providers/auth.provider.dart +++ b/mobile/lib/providers/auth.provider.dart @@ -11,12 +11,11 @@ import 'package:immich_mobile/providers/api.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'; @@ -144,7 +143,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) { 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 c18185710e..e4a3d10a15 100644 --- a/mobile/lib/providers/cleanup.provider.dart +++ b/mobile/lib/providers/cleanup.provider.dart @@ -132,7 +132,9 @@ class CleanupNotifier extends StateNotifier { void applyDefaultAlbumSelections(List<(String id, String name)> albums) { final isInitialized = _metadataRepository.appConfig.cleanup.defaultsInitialized; - if (isInitialized) return; + if (isInitialized) { + return; + } final toKeep = _cleanupService.getDefaultKeepAlbumIds(albums); diff --git a/mobile/lib/providers/infrastructure/action.provider.dart b/mobile/lib/providers/infrastructure/action.provider.dart index d0d1d5d424..8b3dd7d73e 100644 --- a/mobile/lib/providers/infrastructure/action.provider.dart +++ b/mobile/lib/providers/infrastructure/action.provider.dart @@ -239,6 +239,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); @@ -518,7 +538,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/remote_album.provider.dart b/mobile/lib/providers/infrastructure/remote_album.provider.dart index 949e6d747e..b9a0e91ce5 100644 --- a/mobile/lib/providers/infrastructure/remote_album.provider.dart +++ b/mobile/lib/providers/infrastructure/remote_album.provider.dart @@ -24,7 +24,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); diff --git a/mobile/lib/providers/infrastructure/timeline.provider.dart b/mobile/lib/providers/infrastructure/timeline.provider.dart index 06ec0242b2..9f2fdec519 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/metadata.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), + metadataRepository: ref.watch(metadataProvider), ), ); 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..b0a59f6a1e 100644 --- a/mobile/lib/providers/map/map_state.provider.dart +++ b/mobile/lib/providers/map/map_state.provider.dart @@ -1,38 +1,38 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/metadata_key.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/metadata.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(metadataProvider).write(MetadataKey.mapThemeMode, mode); state = state.copyWith(themeMode: mode); } void switchFavoriteOnly(bool isFavoriteOnly) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapShowFavoriteOnly, isFavoriteOnly); + ref.read(metadataProvider).write(MetadataKey.mapShowFavoriteOnly, isFavoriteOnly); state = state.copyWith(showFavoriteOnly: isFavoriteOnly, shouldRefetchMarkers: true); } @@ -41,17 +41,17 @@ class MapStateNotifier extends Notifier { } void switchIncludeArchived(bool isIncludeArchived) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapIncludeArchived, isIncludeArchived); + ref.read(metadataProvider).write(MetadataKey.mapIncludeArchived, isIncludeArchived); state = state.copyWith(includeArchived: isIncludeArchived, shouldRefetchMarkers: true); } void switchWithPartners(bool isWithPartners) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapwithPartners, isWithPartners); + ref.read(metadataProvider).write(MetadataKey.mapWithPartners, isWithPartners); state = state.copyWith(withPartners: isWithPartners, shouldRefetchMarkers: true); } void setRelativeTime(int relativeTime) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapRelativeDate, relativeTime); + ref.read(metadataProvider).write(MetadataKey.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/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/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/websocket.provider.dart b/mobile/lib/providers/websocket.provider.dart index 60afcec2d2..5450120316 100644 --- a/mobile/lib/providers/websocket.provider.dart +++ b/mobile/lib/providers/websocket.provider.dart @@ -29,7 +29,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 +60,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) { 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..fdb4e3323b 100644 --- a/mobile/lib/repositories/asset_api.repository.dart +++ b/mobile/lib/repositories/asset_api.repository.dart @@ -31,6 +31,16 @@ class AssetApiRepository extends ApiRepository { 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))); } diff --git a/mobile/lib/repositories/auth_api.repository.dart b/mobile/lib/repositories/auth_api.repository.dart index 4b0880ddcf..446aba68b3 100644 --- a/mobile/lib/repositories/auth_api.repository.dart +++ b/mobile/lib/repositories/auth_api.repository.dart @@ -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)); } 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/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/router.dart b/mobile/lib/routing/router.dart index 0a9f6c4199..1cc5faa733 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -58,6 +58,7 @@ 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_trash.page.dart'; import 'package:immich_mobile/presentation/pages/drift_user_selection.page.dart'; @@ -168,6 +169,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]), diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index c025da0f73..72054cf194 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 { diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index 44b070e954..4e51c32f97 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -108,6 +108,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); diff --git a/mobile/lib/services/api.service.dart b/mobile/lib/services/api.service.dart index ec4720f313..33c87798a1 100644 --- a/mobile/lib/services/api.service.dart +++ b/mobile/lib/services/api.service.dart @@ -186,7 +186,9 @@ class ApiService { final List list = jsonDecode(externalJson); for (final entry in list) { final url = AuxilaryEndpoint.fromJson(entry).url; - if (url.isNotEmpty) urls.add(url); + if (url.isNotEmpty) { + urls.add(url); + } } } return urls; diff --git a/mobile/lib/services/app_settings.service.dart b/mobile/lib/services/app_settings.service.dart index 72fd389871..1b9a38bc19 100644 --- a/mobile/lib/services/app_settings.service.dart +++ b/mobile/lib/services/app_settings.service.dart @@ -2,47 +2,13 @@ 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), - 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), - 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), diff --git a/mobile/lib/services/auth.service.dart b/mobile/lib/services/auth.service.dart index 667681e579..1b5eaab715 100644 --- a/mobile/lib/services/auth.service.dart +++ b/mobile/lib/services/auth.service.dart @@ -123,7 +123,6 @@ 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), diff --git a/mobile/lib/services/background_upload.service.dart b/mobile/lib/services/background_upload.service.dart index d54a677c24..b76b9dcd61 100644 --- a/mobile/lib/services/background_upload.service.dart +++ b/mobile/lib/services/background_upload.service.dart @@ -82,7 +82,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 && @@ -396,6 +398,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..3f7277579f 100644 --- a/mobile/lib/services/foreground_upload.service.dart +++ b/mobile/lib/services/foreground_upload.service.dart @@ -324,12 +324,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 +432,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(), diff --git a/mobile/lib/utils/action_button.utils.dart b/mobile/lib/utils/action_button.utils.dart index 4f7ad83093..d527f3a59e 100644 --- a/mobile/lib/utils/action_button.utils.dart +++ b/mobile/lib/utils/action_button.utils.dart @@ -22,10 +22,10 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/open_in_browse 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/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/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 +44,6 @@ class ActionButtonContext { final ActionSource source; final bool isCasting; final TimelineOrigin timelineOrigin; - final ThemeData? originalTheme; final int selectedCount; const ActionButtonContext({ @@ -59,7 +58,6 @@ class ActionButtonContext { required this.source, this.isCasting = false, this.timelineOrigin = TimelineOrigin.main, - this.originalTheme, this.selectedCount = 1, }); } @@ -244,7 +242,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 +256,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 @@ -320,7 +315,7 @@ class ActionButtonBuilder { return _actionTypes.where((type) => type.shouldShow(context)).map((type) => type.buildButton(context)).toList(); } - static List buildViewerKebabMenu(ActionButtonContext context, BuildContext buildContext, WidgetRef ref) { + static List buildViewerKebabMenu(ActionButtonContext context, BuildContext buildContext) { final visibleButtons = defaultViewerKebabMenuOrder .where((type) => !defaultViewerBottomBarButtons.contains(type) && type.shouldShow(context)) .toList(); @@ -336,7 +331,7 @@ class ActionButtonBuilder { if (lastGroup != null && type.kebabMenuGroup != lastGroup) { result.add(const Divider(height: 1)); } - result.add(type.buildButton(context, buildContext, false, true).build(buildContext, ref)); + result.add(type.buildButton(context, buildContext, false, true)); lastGroup = type.kebabMenuGroup; } 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/migration.dart b/mobile/lib/utils/migration.dart index b3da549f98..8f0eb00b16 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -7,6 +7,7 @@ import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; import 'package:immich_mobile/domain/models/metadata_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/metadata.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; @@ -32,21 +33,27 @@ Future migrateDatabaseIfNeeded(Drift drift) async { Future _migrateTo25() async { final accessToken = Store.tryGet(StoreKey.accessToken); - if (accessToken == null || accessToken.isEmpty) return; + if (accessToken == null || accessToken.isEmpty) { + return; + } final serverUrls = ApiService.getServerUrls(); - if (serverUrls.isEmpty) return; + if (serverUrls.isEmpty) { + return; + } await NetworkRepository.setHeaders(ApiService.getRequestHeaders(), serverUrls, token: accessToken); } Future _migrateTo26(Drift drift) async { final migrator = _StoreMigrator(drift); - await migrator.migrateEnumName(StoreKey.legacyThemeMode, MetadataKey.themeMode, ThemeMode.values); await migrator.migrateEnumIndex(StoreKey.legacyLogLevel, MetadataKey.logLevel, LogLevel.values); + // Theme + await migrator.migrateEnumName(StoreKey.legacyThemeMode, MetadataKey.themeMode, ThemeMode.values); await migrator.migrateEnumName(StoreKey.legacyPrimaryColor, MetadataKey.themePrimaryColor, ImmichColorPreset.values); await migrator.migrateBool(StoreKey.legacyDynamicTheme, MetadataKey.themeDynamic); await migrator.migrateBool(StoreKey.legacyColorfulInterface, MetadataKey.themeColorfulInterface); + // Cleanup final cleanupKeepAlbumIds = await migrator.readLegacyStoreString(StoreKey.legacyCleanupKeepAlbumIds.id); if (cleanupKeepAlbumIds != null) { final ids = cleanupKeepAlbumIds.split(',').where((id) => id.isNotEmpty).toList(); @@ -67,6 +74,28 @@ Future _migrateTo26(Drift drift) async { ); await migrator.migrateInt(StoreKey.legacyCleanupCutoffDaysAgo, MetadataKey.cleanupCutoffDaysAgo); await migrator.migrateBool(StoreKey.legacyCleanupDefaultsInitialized, MetadataKey.cleanupDefaultsInitialized); + // Map + await migrator.migrateBool(StoreKey.legacyMapShowFavoriteOnly, MetadataKey.mapShowFavoriteOnly); + await migrator.migrateInt(StoreKey.legacyMapRelativeDate, MetadataKey.mapRelativeDate); + await migrator.migrateBool(StoreKey.legacyMapIncludeArchived, MetadataKey.mapIncludeArchived); + await migrator.migrateEnumIndex(StoreKey.legacyMapThemeMode, MetadataKey.mapThemeMode, ThemeMode.values); + await migrator.migrateBool(StoreKey.legacyMapwithPartners, MetadataKey.mapWithPartners); + // Timeline + await migrator.migrateInt(StoreKey.legacyTilesPerRow, MetadataKey.timelineTilesPerRow); + await migrator.migrateEnumIndex( + StoreKey.legacyGroupAssetsBy, + MetadataKey.timelineGroupAssetsBy, + GroupAssetsBy.values, + ); + await migrator.migrateBool(StoreKey.legacyStorageIndicator, MetadataKey.timelineStorageIndicator); + // Image + await migrator.migrateBool(StoreKey.legacyPreferRemoteImage, MetadataKey.imagePreferRemote); + await migrator.migrateBool(StoreKey.legacyLoadOriginal, MetadataKey.imageLoadOriginal); + // Viewer + await migrator.migrateBool(StoreKey.legacyLoopVideo, MetadataKey.viewerLoopVideo); + await migrator.migrateBool(StoreKey.legacyLoadOriginalVideo, MetadataKey.viewerLoadOriginalVideo); + await migrator.migrateBool(StoreKey.legacyAutoPlayVideo, MetadataKey.viewerAutoPlayVideo); + await migrator.migrateBool(StoreKey.legacyTapToNavigate, MetadataKey.viewerTapToNavigate); await migrator.complete(); } @@ -79,7 +108,9 @@ class _StoreMigrator { Future migrateEnumIndex(StoreKey legacyKey, MetadataKey newKey, List values) async { final index = await readLegacyStoreInt(legacyKey.id); - if (index == null) return; + if (index == null) { + return; + } final enumValue = values.elementAtOrNull(index) ?? newKey.defaultValue; _cache[newKey] = enumValue; @@ -92,7 +123,9 @@ class _StoreMigrator { List values, ) async { final name = await readLegacyStoreString(legacyKey.id); - if (name == null) return; + if (name == null) { + return; + } final enumValue = values.firstWhere((e) => e.name == name, orElse: () => newKey.defaultValue); _cache[newKey] = enumValue; @@ -101,7 +134,9 @@ class _StoreMigrator { Future migrateBool(StoreKey legacyKey, MetadataKey newKey) async { final intValue = await readLegacyStoreInt(legacyKey.id); - if (intValue == null) return; + if (intValue == null) { + return; + } final boolValue = intValue != 0; _cache[newKey] = boolValue; @@ -110,7 +145,9 @@ class _StoreMigrator { Future migrateInt(StoreKey legacyKey, MetadataKey newKey) async { final intValue = await readLegacyStoreInt(legacyKey.id); - if (intValue == null) return; + if (intValue == null) { + return; + } _cache[newKey] = intValue; _migratedStoreIds.add(legacyKey.id); @@ -140,7 +177,9 @@ class _StoreMigrator { } Future deleteLegacyStoreRows(List ids) async { - if (ids.isEmpty) return; + if (ids.isEmpty) { + return; + } await (_db.storeEntity.delete()..where((t) => t.id.isIn(ids))).go(); } } diff --git a/mobile/lib/utils/semver.dart b/mobile/lib/utils/semver.dart index aebfd2fe4c..06b186daa3 100644 --- a/mobile/lib/utils/semver.dart +++ b/mobile/lib/utils/semver.dart @@ -63,7 +63,9 @@ 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; } diff --git a/mobile/lib/utils/url_helper.dart b/mobile/lib/utils/url_helper.dart index e3d5b8ed57..fc3b4bbb3f 100644 --- a/mobile/lib/utils/url_helper.dart +++ b/mobile/lib/utils/url_helper.dart @@ -42,13 +42,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 +79,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 02b2d927b5..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()); 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/forms/login/login_form.dart b/mobile/lib/widgets/forms/login/login_form.dart index fb3b9c5977..d53cf1d1d2 100644 --- a/mobile/lib/widgets/forms/login/login_form.dart +++ b/mobile/lib/widgets/forms/login/login_form.dart @@ -40,7 +40,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 +53,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(); } 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 e2502aebae..60557aaaca 100644 --- a/mobile/lib/widgets/settings/advanced_settings.dart +++ b/mobile/lib/widgets/settings/advanced_settings.dart @@ -32,7 +32,11 @@ class AdvancedSettings extends HookConsumerWidget { final isManageMediaSupported = useState(false); final manageMediaAndroidPermission = useState(false); final levelId = useState(ref.read(systemConfigProvider).logLevel.index); - final preferRemote = useAppSettingsState(AppSettingsEnum.preferRemoteImage); + final preferRemote = useState(ref.read(appConfigProvider).image.preferRemote); + useValueChanged( + preferRemote.value, + (_, __) => ref.read(metadataProvider).write(.imagePreferRemote, preferRemote.value), + ); final readonlyModeEnabled = useAppSettingsState(AppSettingsEnum.readonlyModeEnabled); final logLevel = Level.LEVELS[levelId.value].name; 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..b9f81da79e 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,13 @@ 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/metadata_key.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/metadata.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 +16,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(metadataProvider).write(MetadataKey.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 +52,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..20025286f4 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,11 @@ 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/domain/models/metadata_key.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/metadata.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 +14,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(metadataProvider).write(MetadataKey.timelineTilesPerRow, tilesPerRow.value); + }); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -29,7 +33,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..21d751c26f 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,11 @@ 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/domain/models/metadata_key.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.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/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 +16,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(metadataProvider).write(MetadataKey.timelineStorageIndicator, value); ref.invalidate(appSettingsServiceProvider); ref.invalidate(settingsProvider); }, 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..7858033401 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/metadata.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(metadataProvider).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..5af64b0be9 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/metadata.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(metadataProvider).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/video_viewer_settings.dart b/mobile/lib/widgets/settings/asset_viewer_settings/video_viewer_settings.dart index c03dcc51b4..8d62544dd4 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/metadata.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(metadataProvider).write(.viewerAutoPlayVideo, useAutoPlayVideo.value); + }); + useValueChanged(useLoopVideo.value, (_, __) { + ref.read(metadataProvider).write(.viewerLoopVideo, useLoopVideo.value); + }); + useValueChanged(useOriginalVideo.value, (_, __) { + ref.read(metadataProvider).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 c7264ada59..7ec4b21c1f 100644 --- a/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart +++ b/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart @@ -74,7 +74,9 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton> } catch (_) { } finally { Future.delayed(const Duration(seconds: 1), () { - if (!mounted) return; + if (!mounted) { + return; + } setState(() { isAlbumSyncInProgress = false; }); 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/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/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/wm_executor.dart b/mobile/lib/wm_executor.dart index a10b651696..2eb31fe300 100644 --- a/mobile/lib/wm_executor.dart +++ b/mobile/lib/wm_executor.dart @@ -43,7 +43,9 @@ mixin _ExecutorLogger on Mixinable<_Executor> { } void logMessage(String message) { - if (log) print(message); + if (log) { + print(message); + } } } @@ -219,7 +221,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 +239,9 @@ class _Executor extends Mixinable<_Executor> with _ExecutorLogger { }, ) .whenComplete(() { - if (_dynamicSpawning && _queue.isEmpty) availableWorker.kill(); + if (_dynamicSpawning && _queue.isEmpty) { + availableWorker.kill(); + } _schedule(); }); } @@ -249,7 +255,9 @@ class _Executor extends Mixinable<_Executor> with _ExecutorLogger { targetWorker?.cancelGentle(); } else { targetWorker?.kill(); - if (!_dynamicSpawning) targetWorker?.initialize(); + if (!_dynamicSpawning) { + targetWorker?.initialize(); + } } super._cancel(task); } diff --git a/mobile/makefile b/mobile/makefile index 3a0a263687..5a21287b85 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.toml b/mobile/mise.toml index ed928f2445..89a9f0035c 100644 --- a/mobile/mise.toml +++ b/mobile/mise.toml @@ -29,12 +29,15 @@ 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 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", ] [tasks."codegen:translation"] @@ -60,13 +63,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" @@ -83,75 +88,6 @@ run = "dart run drift_dev make-migrations" # 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 +118,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/README.md b/mobile/openapi/README.md index 93b3a45eb0..2538f8e7a7 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -357,7 +357,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 +375,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,7 +483,6 @@ 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) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index fc554b4970..097f0b41bb 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -105,7 +105,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 +123,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,7 +231,6 @@ 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'; diff --git a/mobile/openapi/lib/api/timeline_api.dart b/mobile/openapi/lib/api/timeline_api.dart index 30a4c123f1..6c72f62604 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, }) 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)); } @@ -161,6 +167,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 +192,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, }) 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, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -223,6 +232,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 +257,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, }) async { // ignore: prefer_const_declarations final apiPath = r'/timeline/buckets'; @@ -274,6 +286,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)); } @@ -336,6 +351,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 +376,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, }) 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, ); 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 bb006fdd65..e04f800d3e 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -256,8 +256,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 +292,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 +508,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': diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index 3b36b23d6c..340962cde5 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(); } 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_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..eca87789ce 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -42,7 +42,6 @@ class AssetResponseDto { this.tags = const [], required this.thumbhash, required this.type, - this.unassignedFaces = const [], required this.updatedAt, required this.visibility, required this.width, @@ -139,7 +138,7 @@ class AssetResponseDto { /// Owner user ID String ownerId; - List people; + List people; /// Is resized /// @@ -159,8 +158,6 @@ class AssetResponseDto { 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,13 +236,12 @@ 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 = {}; @@ -323,7 +318,6 @@ class AssetResponseDto { // 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) { @@ -366,13 +360,12 @@ class AssetResponseDto { originalPath: mapValueOfType(json, r'originalPath')!, owner: UserResponseDto.fromJson(json[r'owner']), ownerId: mapValueOfType(json, r'ownerId')!, - people: PersonWithFacesResponseDto.listFromJson(json[r'people']), + people: PersonResponseDto.listFromJson(json[r'people']), resized: mapValueOfType(json, r'resized'), stack: AssetStackResponseDto.fromJson(json[r'stack']), tags: TagResponseDto.listFromJson(json[r'tags']), 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/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/sync_asset_v1.dart b/mobile/openapi/lib/model/sync_asset_v1.dart index d08de6ab72..9a7a3a1f16 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,11 +147,18 @@ 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 @@ -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..7d1dfa298e 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,11 +150,18 @@ 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 @@ -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/time_bucket_asset_response_dto.dart b/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart index 08e690de71..45e793e9e3 100644 --- a/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart +++ b/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart @@ -15,6 +15,7 @@ class TimeBucketAssetResponseDto { TimeBucketAssetResponseDto({ this.city = const [], this.country = const [], + this.createdAt = const [], this.duration = const [], this.fileCreatedAt = const [], this.id = const [], @@ -39,6 +40,9 @@ class TimeBucketAssetResponseDto { /// Array of country names extracted from EXIF GPS data List 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; @@ -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,12 +137,13 @@ 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; + json[r'createdAt'] = this.createdAt; json[r'duration'] = this.duration; json[r'fileCreatedAt'] = this.fileCreatedAt; json[r'id'] = this.id; @@ -171,6 +178,9 @@ class TimeBucketAssetResponseDto { country: json[r'country'] is Iterable ? (json[r'country'] as Iterable).cast().toList(growable: false) : const [], + 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) : const [], @@ -268,6 +278,7 @@ class TimeBucketAssetResponseDto { static const requiredKeys = { 'city', 'country', + 'createdAt', 'duration', 'fileCreatedAt', 'id', 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/test/domain/repositories/sync_stream_repository_test.dart b/mobile/test/domain/repositories/sync_stream_repository_test.dart index a26683213c..4199a5b756 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'; @@ -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/store_service_test.dart b/mobile/test/domain/services/store_service_test.dart index 8ceb1e3c9c..0a55f8d5c7 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 _kEnableBackup = false; +const _kVersion = 2; void main() { late StoreService sut; @@ -24,15 +23,13 @@ void main() { // 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.enableBackup); 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.enableBackup, _kEnableBackup), + 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.enableBackup), _kEnableBackup); + 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.enableBackup), isNull); + expect(sut.tryGet(StoreKey.version), isNull); }); }); } diff --git a/mobile/test/drift/main/generated/schema.dart b/mobile/test/drift/main/generated/schema.dart index 3014477c55..a1bae8f6dd 100644 --- a/mobile/test/drift/main/generated/schema.dart +++ b/mobile/test/drift/main/generated/schema.dart @@ -29,6 +29,7 @@ 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; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -84,6 +85,8 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v24.DatabaseAtV24(db); case 25: return v25.DatabaseAtV25(db); + case 26: + return v26.DatabaseAtV26(db); default: throw MissingSchemaException(version, versions); } @@ -115,5 +118,6 @@ class GeneratedHelper implements SchemaInstantiationHelper { 23, 24, 25, + 26, ]; } diff --git a/mobile/test/drift/main/generated/schema_v25.dart b/mobile/test/drift/main/generated/schema_v25.dart index 932b50498c..aad45f0bd3 100644 --- a/mobile/test/drift/main/generated/schema_v25.dart +++ b/mobile/test/drift/main/generated/schema_v25.dart @@ -9030,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)', @@ -9050,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); @@ -9085,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)', @@ -9105,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)', @@ -9133,13 +9133,11 @@ class DatabaseAtV25 extends GeneratedDatabase { idxLocalAssetChecksum, idxLocalAssetCloudId, idxStackPrimaryAssetId, - idxRemoteAssetOwnerChecksum, uQRemoteAssetsOwnerChecksum, uQRemoteAssetsOwnerLibraryChecksum, idxRemoteAssetChecksum, idxRemoteAssetStackId, - idxRemoteAssetLocalDateTimeDay, - idxRemoteAssetLocalDateTimeMonth, + idxRemoteAssetOwnerVisibilityDeletedCreated, authUserEntity, userMetadataEntity, partnerEntity, @@ -9157,11 +9155,13 @@ class DatabaseAtV25 extends GeneratedDatabase { metadata, idxPartnerSharedWithId, idxLatLng, + idxRemoteExifCity, idxRemoteAlbumAssetAlbumAsset, idxRemoteAssetCloudId, idxPersonOwnerId, idxAssetFacePersonId, idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, idxTrashedLocalAssetChecksum, idxTrashedLocalAssetAlbum, idxAssetEditAssetId, 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/fixtures/sync_stream.stub.dart b/mobile/test/fixtures/sync_stream.stub.dart index c2254c0a03..6d8c0bfdf2 100644 --- a/mobile/test/fixtures/sync_stream.stub.dart +++ b/mobile/test/fixtures/sync_stream.stub.dart @@ -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 806cde9b75..672776b226 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 _kTestBackupRequireWifi = false; +const _kTestBackupRequireCharging = 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.backupRequireWifi.id), - intValue: const Value(_kTestBackupRequireWifi ? 1 : 0), - stringValue: const Value(null), - ), - ); - batch.insert( - db.storeEntity, - StoreEntityCompanion( - id: Value(StoreKey.backupFailedSince.id), - intValue: Value(_kTestBackupFailed.millisecondsSinceEpoch), + id: Value(StoreKey.backupRequireCharging.id), + intValue: const Value(_kTestBackupRequireCharging ? 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? backupRequireWifi = await sut.tryGet(StoreKey.backupRequireWifi); - expect(backupRequireWifi, isNull); - await sut.upsert(StoreKey.backupRequireWifi, _kTestBackupRequireWifi); - backupRequireWifi = await sut.tryGet(StoreKey.backupRequireWifi); - expect(backupRequireWifi, _kTestBackupRequireWifi); + bool? backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging); + expect(backupRequireCharging, isNull); + await sut.upsert(StoreKey.backupRequireCharging, _kTestBackupRequireCharging); + backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging); + expect(backupRequireCharging, _kTestBackupRequireCharging); }); test('converts user', () async { @@ -115,11 +98,11 @@ void main() { }); test('delete()', () async { - bool? backupRequireWifi = await sut.tryGet(StoreKey.backupRequireWifi); - expect(backupRequireWifi, isFalse); - await sut.delete(StoreKey.backupRequireWifi); - backupRequireWifi = await sut.tryGet(StoreKey.backupRequireWifi); - expect(backupRequireWifi, isNull); + bool? backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging); + expect(backupRequireCharging, isFalse); + await sut.delete(StoreKey.backupRequireCharging); + backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging); + expect(backupRequireCharging, isNull); }); test('deleteAll()', () async { @@ -164,14 +147,12 @@ void main() { emitsInOrder([ [ const StoreDto(StoreKey.version, _kTestVersion), - StoreDto(StoreKey.backupFailedSince, _kTestBackupFailed), - const StoreDto(StoreKey.backupRequireWifi, _kTestBackupRequireWifi), + const StoreDto(StoreKey.backupRequireCharging, _kTestBackupRequireCharging), const StoreDto(StoreKey.accessToken, _kTestAccessToken), ], [ const StoreDto(StoreKey.version, _kTestVersion + 10), - StoreDto(StoreKey.backupFailedSince, _kTestBackupFailed), - const StoreDto(StoreKey.backupRequireWifi, _kTestBackupRequireWifi), + const StoreDto(StoreKey.backupRequireCharging, _kTestBackupRequireCharging), const StoreDto(StoreKey.accessToken, _kTestAccessToken), ], ]), diff --git a/mobile/test/medium/repositories/metadata_repository_test.dart b/mobile/test/medium/repositories/metadata_repository_test.dart index 32f613483d..7b185f3bec 100644 --- a/mobile/test/medium/repositories/metadata_repository_test.dart +++ b/mobile/test/medium/repositories/metadata_repository_test.dart @@ -79,7 +79,6 @@ void main() { expect(sut.appConfig.theme.mode, ThemeMode.system); await MetadataRepository.refresh(); - expect(sut.appConfig.theme.mode, ThemeMode.dark); }); @@ -90,7 +89,6 @@ void main() { expect(sut.appConfig.theme.mode, ThemeMode.dark); await MetadataRepository.refresh(); - expect(sut.appConfig.theme.mode, ThemeMode.system); }); @@ -135,5 +133,4 @@ void main() { await expectation; }); }); - } 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/utils_legacy/action_button_utils_test.dart b/mobile/test/utils_legacy/action_button_utils_test.dart index 9956dfa2d0..79f4e04b52 100644 --- a/mobile/test/utils_legacy/action_button_utils_test.dart +++ b/mobile/test/utils_legacy/action_button_utils_test.dart @@ -35,6 +35,7 @@ RemoteAsset createRemoteAsset({ AssetType type = AssetType.image, DateTime? createdAt, DateTime? updatedAt, + DateTime? uploadedAt, bool isFavorite = false, }) { return RemoteAsset( @@ -46,6 +47,7 @@ RemoteAsset createRemoteAsset({ ownerId: 'owner-id', createdAt: createdAt ?? DateTime.now(), updatedAt: updatedAt ?? DateTime.now(), + uploadedAt: uploadedAt ?? DateTime.now(), isFavorite: isFavorite, isEdited: false, ); diff --git a/open-api/bin/generate-dart-sdk.sh b/open-api/bin/generate-dart-sdk.sh new file mode 100755 index 0000000000..e81d28096f --- /dev/null +++ b/open-api/bin/generate-dart-sdk.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +OPENAPI_GENERATOR_VERSION=v7.12.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 ; +export declare const servers: { + server1: string; +}; +export type UserResponseDto = { + avatarColor: UserAvatarColor; + /** User email */ + email: string; + /** User ID */ + id: string; + /** User name */ + name: string; + /** Profile change date */ + profileChangedAt: string; + /** Profile image path */ + profileImagePath: string; +}; +export type ActivityResponseDto = { + /** Asset ID (if activity is for an asset) */ + assetId: string | null; + /** Comment text (for comment activities) */ + comment?: string | null; + /** Creation date */ + createdAt: string; + /** Activity ID */ + id: string; + "type": ReactionType; + user: UserResponseDto; +}; +export type ActivityCreateDto = { + /** Album ID */ + albumId: string; + /** Asset ID (if activity is for an asset) */ + assetId?: string; + /** Comment text (required if type is comment) */ + comment?: string; + "type": ReactionType; +}; +export type ActivityStatisticsResponseDto = { + /** Number of comments */ + comments: number; + /** Number of likes */ + likes: number; +}; +export type DatabaseBackupDeleteDto = { + /** Backup filenames to delete */ + backups: string[]; +}; +export type DatabaseBackupDto = { + /** Backup filename */ + filename: string; + /** Backup file size */ + filesize: number; + /** Backup timezone */ + timezone: string; +}; +export type DatabaseBackupListResponseDto = { + /** List of backups */ + backups: DatabaseBackupDto[]; +}; +export type DatabaseBackupUploadDto = { + /** Database backup file */ + file?: Blob; +}; +export type SetMaintenanceModeDto = { + action: MaintenanceAction; + /** Restore backup filename */ + restoreBackupFilename?: string; +}; +export type MaintenanceDetectInstallStorageFolderDto = { + /** Number of files in the folder */ + files: number; + folder: StorageFolder; + /** Whether the folder is readable */ + readable: boolean; + /** Whether the folder is writable */ + writable: boolean; +}; +export type MaintenanceDetectInstallResponseDto = { + storage: MaintenanceDetectInstallStorageFolderDto[]; +}; +export type MaintenanceLoginDto = { + /** Maintenance token */ + token?: string; +}; +export type MaintenanceAuthDto = { + /** Maintenance username */ + username: string; +}; +export type MaintenanceStatusResponseDto = { + action: MaintenanceAction; + active: boolean; + error?: string; + progress?: number; + task?: string; +}; +export type NotificationCreateDto = { + /** Additional notification data */ + data?: { + [key: string]: any; + }; + /** Notification description */ + description?: string | null; + level?: NotificationLevel; + /** Date when notification was read */ + readAt?: string | null; + /** Notification title */ + title: string; + "type"?: NotificationType; + /** User ID to send notification to */ + userId: string; +}; +export type NotificationDto = { + /** Creation date */ + createdAt: string; + /** Additional notification data */ + data?: { + [key: string]: any; + }; + /** Notification description */ + description?: string; + /** Notification ID */ + id: string; + level: NotificationLevel; + /** Date when notification was read */ + readAt?: string; + /** Notification title */ + title: string; + "type": NotificationType; +}; +export type TemplateDto = { + /** Template name */ + template: string; +}; +export type TemplateResponseDto = { + /** Template HTML content */ + html: string; + /** Template name */ + name: string; +}; +export type SystemConfigSmtpTransportDto = { + /** SMTP server hostname */ + host: string; + /** Whether to ignore SSL certificate errors */ + ignoreCert: boolean; + /** SMTP password */ + password: string; + /** SMTP server port */ + port: number; + /** Whether to use secure connection (TLS/SSL) */ + secure: boolean; + /** SMTP username */ + username: string; +}; +export type SystemConfigSmtpDto = { + /** Whether SMTP email notifications are enabled */ + enabled: boolean; + /** Email address to send from */ + "from": string; + /** Email address for replies */ + replyTo: string; + transport: SystemConfigSmtpTransportDto; +}; +export type TestEmailResponseDto = { + /** Email message ID */ + messageId: string; +}; +export type UserLicense = { + /** Activation date */ + activatedAt: string; + /** Activation key */ + activationKey: string; + /** License key (format: /^IM(SV|CL)(-[\dA-Za-z]{4}){8}$/) */ + licenseKey: string; +}; +export type UserAdminResponseDto = { + avatarColor: UserAvatarColor; + /** Creation date */ + createdAt: string; + /** Deletion date */ + deletedAt: string | null; + /** User email */ + email: string; + /** User ID */ + id: string; + /** Is admin user */ + isAdmin: boolean; + license: (UserLicense) | null; + /** User name */ + name: string; + /** OAuth ID */ + oauthId: string; + /** Profile change date */ + profileChangedAt: string; + /** Profile image path */ + profileImagePath: string; + /** Storage quota in bytes */ + quotaSizeInBytes: number | null; + /** Storage usage in bytes */ + quotaUsageInBytes: number | null; + /** Require password change on next login */ + shouldChangePassword: boolean; + status: UserStatus; + /** Storage label */ + storageLabel: string | null; + /** Last update date */ + updatedAt: string; +}; +export type UserAdminCreateDto = { + avatarColor?: (UserAvatarColor) | null; + /** User email */ + email: string; + /** Grant admin privileges */ + isAdmin?: boolean; + /** User name */ + name: string; + /** Send notification email */ + notify?: boolean; + /** User password */ + password: string; + /** PIN code */ + pinCode?: string | null; + /** Storage quota in bytes */ + quotaSizeInBytes?: number | null; + /** Require password change on next login */ + shouldChangePassword?: boolean; + /** Storage label */ + storageLabel?: string | null; +}; +export type UserAdminDeleteDto = { + /** Force delete even if user has assets */ + force?: boolean; +}; +export type UserAdminUpdateDto = { + avatarColor?: (UserAvatarColor) | null; + /** User email */ + email?: string; + /** Grant admin privileges */ + isAdmin?: boolean; + /** User name */ + name?: string; + /** User password */ + password?: string; + /** PIN code */ + pinCode?: string | null; + /** Storage quota in bytes */ + quotaSizeInBytes?: number | null; + /** Require password change on next login */ + shouldChangePassword?: boolean; + /** Storage label */ + storageLabel?: string | null; +}; +export type AlbumsResponse = { + defaultAssetOrder: AssetOrder; +}; +export type CastResponse = { + /** Whether Google Cast is enabled */ + gCastEnabled: boolean; +}; +export type DownloadResponse = { + /** Maximum archive size in bytes */ + archiveSize: number; + /** Whether to include embedded videos in downloads */ + includeEmbeddedVideos: boolean; +}; +export type EmailNotificationsResponse = { + /** Whether to receive email notifications for album invites */ + albumInvite: boolean; + /** Whether to receive email notifications for album updates */ + albumUpdate: boolean; + /** Whether email notifications are enabled */ + enabled: boolean; +}; +export type FoldersResponse = { + /** Whether folders are enabled */ + enabled: boolean; + /** Whether folders appear in web sidebar */ + sidebarWeb: boolean; +}; +export type MemoriesResponse = { + /** Memory duration in seconds */ + duration: number; + /** Whether memories are enabled */ + enabled: boolean; +}; +export type PeopleResponse = { + /** Whether people are enabled */ + enabled: boolean; + /** Whether people appear in web sidebar */ + sidebarWeb: boolean; +}; +export type PurchaseResponse = { + /** Date until which to hide buy button */ + hideBuyButtonUntil: string; + /** Whether to show support badge */ + showSupportBadge: boolean; +}; +export type RatingsResponse = { + /** Whether ratings are enabled */ + enabled: boolean; +}; +export type SharedLinksResponse = { + /** Whether shared links are enabled */ + enabled: boolean; + /** Whether shared links appear in web sidebar */ + sidebarWeb: boolean; +}; +export type TagsResponse = { + /** Whether tags are enabled */ + enabled: boolean; + /** Whether tags appear in web sidebar */ + sidebarWeb: boolean; +}; +export type UserPreferencesResponseDto = { + albums: AlbumsResponse; + cast: CastResponse; + download: DownloadResponse; + emailNotifications: EmailNotificationsResponse; + folders: FoldersResponse; + memories: MemoriesResponse; + people: PeopleResponse; + purchase: PurchaseResponse; + ratings: RatingsResponse; + sharedLinks: SharedLinksResponse; + tags: TagsResponse; +}; +export type AlbumsUpdate = { + defaultAssetOrder?: AssetOrder; +}; +export type AvatarUpdate = { + color?: UserAvatarColor; +}; +export type CastUpdate = { + /** Whether Google Cast is enabled */ + gCastEnabled?: boolean; +}; +export type DownloadUpdate = { + /** Maximum archive size in bytes */ + archiveSize?: number; + /** Whether to include embedded videos in downloads */ + includeEmbeddedVideos?: boolean; +}; +export type EmailNotificationsUpdate = { + /** Whether to receive email notifications for album invites */ + albumInvite?: boolean; + /** Whether to receive email notifications for album updates */ + albumUpdate?: boolean; + /** Whether email notifications are enabled */ + enabled?: boolean; +}; +export type FoldersUpdate = { + /** Whether folders are enabled */ + enabled?: boolean; + /** Whether folders appear in web sidebar */ + sidebarWeb?: boolean; +}; +export type MemoriesUpdate = { + /** Memory duration in seconds */ + duration?: number; + /** Whether memories are enabled */ + enabled?: boolean; +}; +export type PeopleUpdate = { + /** Whether people are enabled */ + enabled?: boolean; + /** Whether people appear in web sidebar */ + sidebarWeb?: boolean; +}; +export type PurchaseUpdate = { + /** Date until which to hide buy button */ + hideBuyButtonUntil?: string; + /** Whether to show support badge */ + showSupportBadge?: boolean; +}; +export type RatingsUpdate = { + /** Whether ratings are enabled */ + enabled?: boolean; +}; +export type SharedLinksUpdate = { + /** Whether shared links are enabled */ + enabled?: boolean; + /** Whether shared links appear in web sidebar */ + sidebarWeb?: boolean; +}; +export type TagsUpdate = { + /** Whether tags are enabled */ + enabled?: boolean; + /** Whether tags appear in web sidebar */ + sidebarWeb?: boolean; +}; +export type UserPreferencesUpdateDto = { + albums?: AlbumsUpdate; + avatar?: AvatarUpdate; + cast?: CastUpdate; + download?: DownloadUpdate; + emailNotifications?: EmailNotificationsUpdate; + folders?: FoldersUpdate; + memories?: MemoriesUpdate; + people?: PeopleUpdate; + purchase?: PurchaseUpdate; + ratings?: RatingsUpdate; + sharedLinks?: SharedLinksUpdate; + tags?: TagsUpdate; +}; +export type SessionResponseDto = { + /** App version */ + appVersion: string | null; + /** Creation date */ + createdAt: string; + /** Is current session */ + current: boolean; + /** Device OS */ + deviceOS: string; + /** Device type */ + deviceType: string; + /** Expiration date */ + expiresAt?: string; + /** Session ID */ + id: string; + /** Is pending sync reset */ + isPendingSyncReset: boolean; + /** Last update date */ + updatedAt: string; +}; +export type AssetStatsResponseDto = { + /** Number of images */ + images: number; + /** Total number of assets */ + total: number; + /** Number of videos */ + videos: number; +}; +export type AlbumUserResponseDto = { + role: AlbumUserRole; + user: UserResponseDto; +}; +export type ContributorCountResponseDto = { + /** Number of assets contributed */ + assetCount: number; + /** User ID */ + userId: string; +}; +export type AlbumResponseDto = { + /** Album name */ + albumName: string; + /** Thumbnail asset ID */ + albumThumbnailAssetId: string | null; + /** First entry is always the album owner. Second entry is the auth user, if it differs from the owner. The rest are ordered alphabetically. */ + albumUsers: AlbumUserResponseDto[]; + /** Number of assets */ + assetCount: number; + contributorCounts?: ContributorCountResponseDto[]; + /** Creation date */ + createdAt: string; + /** Album description */ + description: string; + /** End date (latest asset) */ + endDate?: string; + /** Has shared link */ + hasSharedLink: boolean; + /** Album ID */ + id: string; + /** Activity feed enabled */ + isActivityEnabled: boolean; + /** Last modified asset timestamp */ + lastModifiedAssetTimestamp?: string; + order?: AssetOrder; + /** Is shared album */ + shared: boolean; + /** Start date (earliest asset) */ + startDate?: string; + /** Last update date */ + updatedAt: string; +}; +export type AlbumUserCreateDto = { + role: AlbumUserRole; + /** User ID */ + userId: string; +}; +export type CreateAlbumDto = { + /** Album name */ + albumName: string; + /** Album users */ + albumUsers?: AlbumUserCreateDto[]; + /** Initial asset IDs */ + assetIds?: string[]; + /** Album description */ + description?: string; +}; +export type AlbumsAddAssetsDto = { + /** Album IDs */ + albumIds: string[]; + /** Asset IDs */ + assetIds: string[]; +}; +export type AlbumsAddAssetsResponseDto = { + error?: BulkIdErrorReason; + /** Operation success */ + success: boolean; +}; +export type AlbumStatisticsResponseDto = { + /** Number of non-shared albums */ + notShared: number; + /** Number of owned albums */ + owned: number; + /** Number of shared albums */ + shared: number; +}; +export type UpdateAlbumDto = { + /** Album name */ + albumName?: string; + /** Album thumbnail asset ID */ + albumThumbnailAssetId?: string; + /** Album description */ + description?: string; + /** Enable activity feed */ + isActivityEnabled?: boolean; + order?: AssetOrder; +}; +export type BulkIdsDto = { + /** IDs to process */ + ids: string[]; +}; +export type BulkIdResponseDto = { + error?: BulkIdErrorReason; + errorMessage?: string; + /** ID */ + id: string; + /** Whether operation succeeded */ + success: boolean; +}; +export type MapMarkerResponseDto = { + /** City name */ + city: string | null; + /** Country name */ + country: string | null; + /** Asset ID */ + id: string; + /** Latitude */ + lat: number; + /** Longitude */ + lon: number; + /** State/Province name */ + state: string | null; +}; +export type UpdateAlbumUserDto = { + role: AlbumUserRole; +}; +export type AlbumUserAddDto = { + /** Album user role */ + role?: AlbumUserRole; + /** User ID */ + userId: string; +}; +export type AddUsersDto = { + /** Album users to add */ + albumUsers: AlbumUserAddDto[]; +}; +export type ApiKeyResponseDto = { + /** Creation date */ + createdAt: string; + /** API key ID */ + id: string; + /** API key name */ + name: string; + /** List of permissions */ + permissions: Permission[]; + /** Last update date */ + updatedAt: string; +}; +export type ApiKeyCreateDto = { + /** API key name */ + name?: string; + /** List of permissions */ + permissions: Permission[]; +}; +export type ApiKeyCreateResponseDto = { + apiKey: ApiKeyResponseDto; + /** API key secret (only shown once) */ + secret: string; +}; +export type ApiKeyUpdateDto = { + /** API key name */ + name?: string; + /** List of permissions */ + permissions?: Permission[]; +}; +export type AssetBulkDeleteDto = { + /** Force delete even if in use */ + force?: boolean; + /** IDs to process */ + ids: string[]; +}; +export type AssetMetadataUpsertItemDto = { + /** Metadata key */ + key: string; + /** Metadata value (object) */ + value: { + [key: string]: any; + }; +}; +export type AssetMediaCreateDto = { + /** Asset file data */ + assetData: Blob; + /** Duration in milliseconds (for videos) */ + duration?: number; + /** File creation date */ + fileCreatedAt: string; + /** File modification date */ + fileModifiedAt: string; + /** Filename */ + filename?: string; + /** Mark as favorite */ + isFavorite?: boolean; + /** Live photo video ID */ + livePhotoVideoId?: string; + /** Asset metadata items */ + metadata?: AssetMetadataUpsertItemDto[]; + /** Sidecar file data */ + sidecarData?: Blob; + visibility?: AssetVisibility; +}; +export type AssetMediaResponseDto = { + /** Asset media ID */ + id: string; + status: AssetMediaStatus; +}; +export type AssetBulkUpdateDto = { + /** Original date and time */ + dateTimeOriginal?: string; + /** Relative time offset in seconds */ + dateTimeRelative?: number; + /** Asset description */ + description?: string; + /** Duplicate ID */ + duplicateId?: string | null; + /** Asset IDs to update */ + ids: string[]; + /** Mark as favorite */ + isFavorite?: boolean; + /** Latitude coordinate */ + latitude?: number; + /** Longitude coordinate */ + longitude?: number; + /** Rating in range [1-5], or null for unrated */ + rating?: number | null; + /** Time zone (IANA timezone) */ + timeZone?: string; + visibility?: AssetVisibility; +}; +export type AssetBulkUploadCheckItem = { + /** Base64 or hex encoded SHA1 hash */ + checksum: string; + /** Asset ID */ + id: string; +}; +export type AssetBulkUploadCheckDto = { + /** Assets to check */ + assets: AssetBulkUploadCheckItem[]; +}; +export type AssetBulkUploadCheckResult = { + action: AssetUploadAction; + /** Existing asset ID if duplicate */ + assetId?: string; + /** Asset ID */ + id: string; + /** Whether existing asset is trashed */ + isTrashed?: boolean; + reason?: AssetRejectReason; +}; +export type AssetBulkUploadCheckResponseDto = { + /** Upload check results */ + results: AssetBulkUploadCheckResult[]; +}; +export type AssetCopyDto = { + /** Copy album associations */ + albums?: boolean; + /** Copy favorite status */ + favorite?: boolean; + /** Copy shared links */ + sharedLinks?: boolean; + /** Copy sidecar file */ + sidecar?: boolean; + /** Source asset ID */ + sourceId: string; + /** Copy stack association */ + stack?: boolean; + /** Target asset ID */ + targetId: string; +}; +export type AssetJobsDto = { + /** Asset IDs */ + assetIds: string[]; + name: AssetJobName; +}; +export type AssetMetadataBulkDeleteItemDto = { + /** Asset ID */ + assetId: string; + /** Metadata key */ + key: string; +}; +export type AssetMetadataBulkDeleteDto = { + /** Metadata items to delete */ + items: AssetMetadataBulkDeleteItemDto[]; +}; +export type AssetMetadataBulkUpsertItemDto = { + /** Asset ID */ + assetId: string; + /** Metadata key */ + key: string; + /** Metadata value (object) */ + value: { + [key: string]: any; + }; +}; +export type AssetMetadataBulkUpsertDto = { + /** Metadata items to upsert */ + items: AssetMetadataBulkUpsertItemDto[]; +}; +export type AssetMetadataBulkResponseDto = { + /** Asset ID */ + assetId: string; + /** Metadata key */ + key: string; + /** Last update date */ + updatedAt: string; + /** Metadata value (object) */ + value: { + [key: string]: any; + }; +}; +export type ExifResponseDto = { + /** City name */ + city?: string | null; + /** Country name */ + country?: string | null; + /** Original date/time */ + dateTimeOriginal?: string | null; + /** Image description */ + description?: string | null; + /** Image height in pixels */ + exifImageHeight?: number | null; + /** Image width in pixels */ + exifImageWidth?: number | null; + /** Exposure time */ + exposureTime?: string | null; + /** F-number (aperture) */ + fNumber?: number | null; + /** File size in bytes */ + fileSizeInByte?: number | null; + /** Focal length in mm */ + focalLength?: number | null; + /** ISO sensitivity */ + iso?: number | null; + /** GPS latitude */ + latitude?: number | null; + /** Lens model */ + lensModel?: string | null; + /** GPS longitude */ + longitude?: number | null; + /** Camera make */ + make?: string | null; + /** Camera model */ + model?: string | null; + /** Modification date/time */ + modifyDate?: string | null; + /** Image orientation */ + orientation?: string | null; + /** Projection type */ + projectionType?: string | null; + /** Rating */ + rating?: number | null; + /** State/province name */ + state?: string | null; + /** 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 = { + /** Person date of birth */ + birthDate: string | null; + /** Person color (hex) */ + color?: string; + faces: AssetFaceWithoutPersonResponseDto[]; + /** 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 AssetStackResponseDto = { + /** Number of assets in stack */ + assetCount: number; + /** Stack ID */ + id: string; + /** Primary asset ID */ + primaryAssetId: string; +}; +export type TagResponseDto = { + /** Tag color (hex) */ + color?: string; + /** Creation date */ + createdAt: string; + /** Tag ID */ + id: string; + /** Tag name */ + name: string; + /** Parent tag ID */ + parentId?: string; + /** Last update date */ + updatedAt: string; + /** Tag value (full path) */ + value: string; +}; +export type AssetResponseDto = { + /** Base64 encoded SHA1 hash */ + checksum: string; + /** The UTC timestamp when the asset was originally uploaded to Immich. */ + createdAt: string; + /** Duplicate group ID */ + duplicateId?: string | null; + /** Video/gif duration in milliseconds (null for static images) */ + duration: number | null; + exifInfo?: ExifResponseDto; + /** 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. */ + fileCreatedAt: string; + /** The UTC timestamp when the file was last modified on the filesystem. This reflects the last time the physical file was changed, which may be different from when the photo was originally taken. */ + fileModifiedAt: string; + /** Whether asset has metadata */ + hasMetadata: boolean; + /** Asset height */ + height: number | null; + /** Asset ID */ + id: string; + /** Is archived */ + isArchived: boolean; + /** Is edited */ + isEdited: boolean; + /** Is favorite */ + isFavorite: boolean; + /** Is offline */ + isOffline: boolean; + /** Is trashed */ + isTrashed: boolean; + /** Library ID */ + libraryId?: string | null; + /** Live photo video ID */ + livePhotoVideoId?: string | null; + /** 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. */ + localDateTime: string; + /** Original file name */ + originalFileName: string; + /** Original MIME type */ + originalMimeType?: string; + /** Original file path */ + originalPath: string; + owner?: UserResponseDto; + /** Owner user ID */ + ownerId: string; + people?: PersonWithFacesResponseDto[]; + /** Is resized */ + resized?: boolean; + stack?: (AssetStackResponseDto) | null; + tags?: TagResponseDto[]; + /** 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; + /** Asset width */ + width: number | null; +}; +export type UpdateAssetDto = { + /** Original date and time */ + dateTimeOriginal?: string; + /** Asset description */ + description?: string; + /** Mark as favorite */ + isFavorite?: boolean; + /** Latitude coordinate */ + latitude?: number; + /** Live photo video ID */ + livePhotoVideoId?: string | null; + /** Longitude coordinate */ + longitude?: number; + /** Rating in range [1-5], or null for unrated */ + rating?: number | null; + visibility?: AssetVisibility; +}; +export type CropParameters = { + /** Height of the crop */ + height: number; + /** Width of the crop */ + width: number; + /** Top-Left X coordinate of crop */ + x: number; + /** Top-Left Y coordinate of crop */ + y: number; +}; +export type RotateParameters = { + /** Rotation angle in degrees */ + angle: number; +}; +export type MirrorParameters = { + axis: MirrorAxis; +}; +export type AssetEditActionItemResponseDto = { + action: AssetEditAction; + /** Asset edit ID */ + id: string; + /** List of edit actions to apply (crop, rotate, or mirror) */ + parameters: CropParameters | RotateParameters | MirrorParameters; +}; +export type AssetEditsResponseDto = { + /** Asset ID these edits belong to */ + assetId: string; + /** List of edit actions applied to the asset */ + edits: AssetEditActionItemResponseDto[]; +}; +export type AssetEditActionItemDto = { + action: AssetEditAction; + /** List of edit actions to apply (crop, rotate, or mirror) */ + parameters: CropParameters | RotateParameters | MirrorParameters; +}; +export type AssetEditsCreateDto = { + /** List of edit actions to apply (crop, rotate, or mirror) */ + edits: AssetEditActionItemDto[]; +}; +export type AssetMetadataResponseDto = { + /** Metadata key */ + key: string; + /** Last update date */ + updatedAt: string; + /** Metadata value (object) */ + value: { + [key: string]: any; + }; +}; +export type AssetMetadataUpsertDto = { + /** Metadata items to upsert */ + items: AssetMetadataUpsertItemDto[]; +}; +export type AssetOcrResponseDto = { + assetId: string; + /** Confidence score for text detection box */ + boxScore: number; + id: string; + /** Recognized text */ + text: string; + /** Confidence score for text recognition */ + textScore: number; + /** Normalized x coordinate of box corner 1 (0-1) */ + x1: number; + /** Normalized x coordinate of box corner 2 (0-1) */ + x2: number; + /** Normalized x coordinate of box corner 3 (0-1) */ + x3: number; + /** Normalized x coordinate of box corner 4 (0-1) */ + x4: number; + /** Normalized y coordinate of box corner 1 (0-1) */ + y1: number; + /** Normalized y coordinate of box corner 2 (0-1) */ + y2: number; + /** Normalized y coordinate of box corner 3 (0-1) */ + y3: number; + /** Normalized y coordinate of box corner 4 (0-1) */ + y4: number; +}; +export type SignUpDto = { + /** User email */ + email: string; + /** User name */ + name: string; + /** User password */ + password: string; +}; +export type ChangePasswordDto = { + /** Invalidate all other sessions */ + invalidateSessions?: boolean; + /** New password (min 8 characters) */ + newPassword: string; + /** Current password */ + password: string; +}; +export type LoginCredentialDto = { + /** User email */ + email: string; + /** User password */ + password: string; +}; +export type LoginResponseDto = { + /** Access token */ + accessToken: string; + /** Is admin user */ + isAdmin: boolean; + /** Is onboarded */ + isOnboarded: boolean; + /** User name */ + name: string; + /** Profile image path */ + profileImagePath: string; + /** Should change password */ + shouldChangePassword: boolean; + /** User email */ + userEmail: string; + /** User ID */ + userId: string; +}; +export type LogoutResponseDto = { + /** Redirect URI */ + redirectUri: string; + /** Logout successful */ + successful: boolean; +}; +export type PinCodeResetDto = { + /** User password (required if PIN code is not provided) */ + password?: string; + /** New PIN code (4-6 digits) */ + pinCode?: string; +}; +export type PinCodeSetupDto = { + /** PIN code (4-6 digits) */ + pinCode: string; +}; +export type PinCodeChangeDto = { + /** New PIN code (4-6 digits) */ + newPinCode: string; + /** User password (required if PIN code is not provided) */ + password?: string; + /** New PIN code (4-6 digits) */ + pinCode?: string; +}; +export type SessionUnlockDto = { + /** User password (required if PIN code is not provided) */ + password?: string; + /** New PIN code (4-6 digits) */ + pinCode?: string; +}; +export type AuthStatusResponseDto = { + /** Session expiration date */ + expiresAt?: string; + /** Is elevated session */ + isElevated: boolean; + /** Has password set */ + password: boolean; + /** Has PIN code set */ + pinCode: boolean; + /** PIN expiration date */ + pinExpiresAt?: string; +}; +export type ValidateAccessTokenResponseDto = { + /** Authentication status */ + authStatus: boolean; +}; +export type DownloadArchiveDto = { + /** Asset IDs */ + assetIds: string[]; + /** Download edited asset if available */ + edited?: boolean; +}; +export type DownloadInfoDto = { + /** Album ID to download */ + albumId?: string; + /** Archive size limit in bytes */ + archiveSize?: number; + /** Asset IDs to download */ + assetIds?: string[]; + /** User ID to download assets from */ + userId?: string; +}; +export type DownloadArchiveInfo = { + /** Asset IDs in this archive */ + assetIds: string[]; + /** Archive size in bytes */ + size: number; +}; +export type DownloadResponseDto = { + /** Archive information */ + archives: DownloadArchiveInfo[]; + /** Total size in bytes */ + totalSize: number; +}; +export type DuplicateResponseDto = { + /** Duplicate assets */ + assets: AssetResponseDto[]; + /** Duplicate group ID */ + duplicateId: string; + /** Suggested asset IDs to keep based on file size and EXIF data */ + suggestedKeepAssetIds: string[]; +}; +export type DuplicateResolveGroupDto = { + duplicateId: string; + /** Asset IDs to keep */ + keepAssetIds: string[]; + /** Asset IDs to trash or delete */ + trashAssetIds: string[]; +}; +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; + /** 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; + person: (PersonResponseDto) | null; + sourceType?: SourceType; +}; +export type AssetFaceCreateDto = { + /** Asset ID */ + assetId: string; + /** Face bounding box height */ + height: number; + /** Image height in pixels */ + imageHeight: number; + /** Image width in pixels */ + imageWidth: number; + /** Person ID */ + personId: string; + /** Face bounding box width */ + width: number; + /** Face bounding box X coordinate */ + x: number; + /** Face bounding box Y coordinate */ + y: number; +}; +export type AssetFaceDeleteDto = { + /** Force delete even if person has other faces */ + force: boolean; +}; +export type FaceDto = { + /** Face ID */ + id: string; +}; +export type QueueStatisticsDto = { + /** Number of active jobs */ + active: number; + /** Number of completed jobs */ + completed: number; + /** Number of delayed jobs */ + delayed: number; + /** Number of failed jobs */ + failed: number; + /** Number of paused jobs */ + paused: number; + /** Number of waiting jobs */ + waiting: number; +}; +export type QueueStatusLegacyDto = { + /** Whether the queue is currently active (has running jobs) */ + isActive: boolean; + /** Whether the queue is paused */ + isPaused: boolean; +}; +export type QueueResponseLegacyDto = { + jobCounts: QueueStatisticsDto; + queueStatus: QueueStatusLegacyDto; +}; +export type QueuesResponseLegacyDto = { + backgroundTask: QueueResponseLegacyDto; + backupDatabase: QueueResponseLegacyDto; + duplicateDetection: QueueResponseLegacyDto; + editor: QueueResponseLegacyDto; + faceDetection: QueueResponseLegacyDto; + facialRecognition: QueueResponseLegacyDto; + library: QueueResponseLegacyDto; + metadataExtraction: QueueResponseLegacyDto; + migration: QueueResponseLegacyDto; + notifications: QueueResponseLegacyDto; + ocr: QueueResponseLegacyDto; + search: QueueResponseLegacyDto; + sidecar: QueueResponseLegacyDto; + smartSearch: QueueResponseLegacyDto; + storageTemplateMigration: QueueResponseLegacyDto; + thumbnailGeneration: QueueResponseLegacyDto; + videoConversion: QueueResponseLegacyDto; + workflow: QueueResponseLegacyDto; +}; +export type JobCreateDto = { + name: ManualJobName; +}; +export type QueueCommandDto = { + command: QueueCommand; + /** Force the command execution (if applicable) */ + force?: boolean; +}; +export type LibraryResponseDto = { + /** Number of assets */ + assetCount: number; + /** Creation date */ + createdAt: string; + /** Exclusion patterns */ + exclusionPatterns: string[]; + /** Library ID */ + id: string; + /** Import paths */ + importPaths: string[]; + /** Library name */ + name: string; + /** Owner user ID */ + ownerId: string; + /** Last refresh date */ + refreshedAt: string | null; + /** Last update date */ + updatedAt: string; +}; +export type CreateLibraryDto = { + /** Exclusion patterns (max 128) */ + exclusionPatterns?: string[]; + /** Import paths (max 128) */ + importPaths?: string[]; + /** Library name */ + name?: string; + /** Owner user ID */ + ownerId: string; +}; +export type UpdateLibraryDto = { + /** Exclusion patterns (max 128) */ + exclusionPatterns?: string[]; + /** Import paths (max 128) */ + importPaths?: string[]; + /** Library name */ + name?: string; +}; +export type LibraryStatsResponseDto = { + /** Number of photos */ + photos: number; + /** Total number of assets */ + total: number; + /** Storage usage in bytes */ + usage: number; + /** Number of videos */ + videos: number; +}; +export type ValidateLibraryDto = { + /** Exclusion patterns (max 128) */ + exclusionPatterns?: string[]; + /** Import paths to validate (max 128) */ + importPaths?: string[]; +}; +export type ValidateLibraryImportPathResponseDto = { + /** Import path */ + importPath: string; + /** Is valid */ + isValid: boolean; + /** Validation message */ + message?: string; +}; +export type ValidateLibraryResponseDto = { + /** Validation results for import paths */ + importPaths?: ValidateLibraryImportPathResponseDto[]; +}; +export type MapReverseGeocodeResponseDto = { + /** City name */ + city: string | null; + /** Country name */ + country: string | null; + /** State/Province name */ + state: string | null; +}; +export type OnThisDayDto = { + /** Year for on this day memory */ + year: number; +}; +export type MemoryResponseDto = { + assets: AssetResponseDto[]; + /** Creation date */ + createdAt: string; + data: OnThisDayDto; + /** Deletion date */ + deletedAt?: string; + /** Date when memory should be hidden */ + hideAt?: string; + /** Memory ID */ + id: string; + /** Is memory saved */ + isSaved: boolean; + /** Memory date */ + memoryAt: string; + /** Owner user ID */ + ownerId: string; + /** Date when memory was seen */ + seenAt?: string; + /** Date when memory should be shown */ + showAt?: string; + "type": MemoryType; + /** Last update date */ + updatedAt: string; +}; +export type MemoryCreateDto = { + /** Asset IDs to associate with memory */ + assetIds?: string[]; + data: OnThisDayDto; + /** Date when memory should be hidden */ + hideAt?: string; + /** Is memory saved */ + isSaved?: boolean; + /** Memory date */ + memoryAt: string; + /** Date when memory was seen */ + seenAt?: string; + /** Date when memory should be shown */ + showAt?: string; + "type": MemoryType; +}; +export type MemoryStatisticsResponseDto = { + /** Total number of memories */ + total: number; +}; +export type MemoryUpdateDto = { + /** Is memory saved */ + isSaved?: boolean; + /** Memory date */ + memoryAt?: string; + /** Date when memory was seen */ + seenAt?: string; +}; +export type NotificationDeleteAllDto = { + /** Notification IDs to delete */ + ids: string[]; +}; +export type NotificationUpdateAllDto = { + /** Notification IDs to update */ + ids: string[]; + /** Date when notifications were read */ + readAt?: string | null; +}; +export type NotificationUpdateDto = { + /** Date when notification was read */ + readAt?: string | null; +}; +export type OAuthConfigDto = { + /** OAuth code challenge (PKCE) */ + codeChallenge?: string; + /** OAuth redirect URI */ + redirectUri: string; + /** OAuth state parameter */ + state?: string; +}; +export type OAuthAuthorizeResponseDto = { + /** OAuth authorization URL */ + url: string; +}; +export type OAuthBackchannelLogoutDto = { + /** OAuth logout token */ + logout_token: string; +}; +export type OAuthCallbackDto = { + /** OAuth code verifier (PKCE) */ + codeVerifier?: string; + /** OAuth state parameter */ + state?: string; + /** OAuth callback URL */ + url: string; +}; +export type PartnerResponseDto = { + avatarColor: UserAvatarColor; + /** User email */ + email: string; + /** User ID */ + id: string; + /** Show in timeline */ + inTimeline?: boolean; + /** User name */ + name: string; + /** Profile change date */ + profileChangedAt: string; + /** Profile image path */ + profileImagePath: string; +}; +export type PartnerCreateDto = { + /** User ID to share with */ + sharedWithId: string; +}; +export type PartnerUpdateDto = { + /** Show partner assets in timeline */ + inTimeline: boolean; +}; +export type PeopleResponseDto = { + /** Whether there are more pages */ + hasNextPage?: boolean; + /** Number of hidden people */ + hidden: number; + people: PersonResponseDto[]; + /** Total number of people */ + total: number; +}; +export type PersonCreateDto = { + /** Person date of birth */ + birthDate?: string | null; + /** Person color (hex) */ + color?: string | null; + /** Mark as favorite */ + isFavorite?: boolean; + /** Person visibility (hidden) */ + isHidden?: boolean; + /** Person name */ + name?: string; +}; +export type PeopleUpdateItem = { + /** Person date of birth */ + birthDate?: string | null; + /** Person color (hex) */ + color?: string | null; + /** Asset ID used for feature face thumbnail */ + featureFaceAssetId?: string; + /** Person ID */ + id: string; + /** Mark as favorite */ + isFavorite?: boolean; + /** Person visibility (hidden) */ + isHidden?: boolean; + /** Person name */ + name?: string; +}; +export type PeopleUpdateDto = { + /** People to update */ + people: PeopleUpdateItem[]; +}; +export type PersonUpdateDto = { + /** Person date of birth */ + birthDate?: string | null; + /** Person color (hex) */ + color?: string | null; + /** Asset ID used for feature face thumbnail */ + featureFaceAssetId?: string; + /** Mark as favorite */ + isFavorite?: boolean; + /** Person visibility (hidden) */ + isHidden?: boolean; + /** Person name */ + name?: string; +}; +export type MergePersonDto = { + /** Person IDs to merge */ + ids: string[]; +}; +export type AssetFaceUpdateItem = { + /** Asset ID */ + assetId: string; + /** Person ID */ + personId: string; +}; +export type AssetFaceUpdateDto = { + /** Face update items */ + data: AssetFaceUpdateItem[]; +}; +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 */ + 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 */ + title: 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 name */ + name: string; + /** Plugin title */ + title: string; + /** Last update date */ + updatedAt: string; + /** Plugin version */ + version: string; +}; +export type PluginTriggerResponseDto = { + contextType: PluginContextType; + "type": PluginTriggerType; +}; +export type QueueResponseDto = { + /** Whether the queue is paused */ + isPaused: boolean; + name: QueueName; + statistics: QueueStatisticsDto; +}; +export type QueueUpdateDto = { + /** Whether to pause the queue */ + isPaused?: boolean; +}; +export type QueueDeleteDto = { + /** If true, will also remove failed jobs from the queue. */ + failed?: boolean; +}; +export type QueueJobResponseDto = { + /** Job data payload */ + data: { + [key: string]: any; + }; + /** Job ID */ + id?: string; + name: JobName; + /** Job creation timestamp */ + timestamp: number; +}; +export type SearchExploreItem = { + data: AssetResponseDto; + /** Explore value */ + value: string; +}; +export type SearchExploreResponseDto = { + /** Explore field name */ + fieldName: string; + items: SearchExploreItem[]; +}; +export type MetadataSearchDto = { + /** Filter by album IDs */ + albumIds?: string[]; + /** Filter by file checksum */ + checksum?: string; + /** Filter by city name */ + city?: string | null; + /** Filter by country name */ + country?: string | null; + /** Filter by creation date (after) */ + createdAfter?: string; + /** Filter by creation date (before) */ + createdBefore?: string; + /** Filter by description text */ + description?: string; + /** Filter by encoded video file path */ + encodedVideoPath?: string; + /** Filter by asset ID */ + id?: string; + /** Filter by encoded status */ + isEncoded?: boolean; + /** Filter by favorite status */ + isFavorite?: boolean; + /** Filter by motion photo status */ + isMotion?: boolean; + /** Filter assets not in any album */ + isNotInAlbum?: boolean; + /** Filter by offline status */ + isOffline?: boolean; + /** Filter by lens model */ + lensModel?: string | null; + /** Library ID to filter by */ + libraryId?: string | null; + /** Filter by camera make */ + make?: string | null; + /** Filter by camera model */ + model?: string | null; + /** Filter by OCR text content */ + ocr?: string; + /** Sort order */ + order?: AssetOrder; + /** Filter by original file name */ + originalFileName?: string; + /** Filter by original file path */ + originalPath?: string; + /** Page number */ + page?: number; + /** Filter by person IDs */ + personIds?: string[]; + /** Filter by preview file path */ + previewPath?: string; + /** Filter by rating [1-5], or null for unrated */ + rating?: number | null; + /** Number of results to return */ + size?: number; + /** Filter by state/province name */ + state?: string | null; + /** Filter by tag IDs */ + tagIds?: string[] | null; + /** Filter by taken date (after) */ + takenAfter?: string; + /** Filter by taken date (before) */ + takenBefore?: string; + /** Filter by thumbnail file path */ + thumbnailPath?: string; + /** Filter by trash date (after) */ + trashedAfter?: string; + /** Filter by trash date (before) */ + trashedBefore?: string; + "type"?: AssetTypeEnum; + /** Filter by update date (after) */ + updatedAfter?: string; + /** Filter by update date (before) */ + updatedBefore?: string; + visibility?: AssetVisibility; + /** Include deleted assets */ + withDeleted?: boolean; + /** Include EXIF data in response */ + withExif?: boolean; + /** Include people data in response */ + withPeople?: boolean; + /** Include stacked assets */ + withStacked?: boolean; +}; +export type SearchFacetCountResponseDto = { + /** Number of assets with this facet value */ + count: number; + /** Facet value */ + value: string; +}; +export type SearchFacetResponseDto = { + counts: SearchFacetCountResponseDto[]; + /** Facet field name */ + fieldName: string; +}; +export type SearchAlbumResponseDto = { + /** Number of albums in this page */ + count: number; + facets: SearchFacetResponseDto[]; + items: AlbumResponseDto[]; + /** Total number of matching albums */ + total: number; +}; +export type SearchAssetResponseDto = { + /** Number of assets in this page */ + count: number; + facets: SearchFacetResponseDto[]; + items: AssetResponseDto[]; + /** Next page token */ + nextPage: string | null; + /** Total number of matching assets */ + total: number; +}; +export type SearchResponseDto = { + albums: SearchAlbumResponseDto; + assets: SearchAssetResponseDto; +}; +export type PlacesResponseDto = { + /** Administrative level 1 name (state/province) */ + admin1name?: string; + /** Administrative level 2 name (county/district) */ + admin2name?: string; + /** Latitude coordinate */ + latitude: number; + /** Longitude coordinate */ + longitude: number; + /** Place name */ + name: string; +}; +export type RandomSearchDto = { + /** Filter by album IDs */ + albumIds?: string[]; + /** Filter by city name */ + city?: string | null; + /** Filter by country name */ + country?: string | null; + /** Filter by creation date (after) */ + createdAfter?: string; + /** Filter by creation date (before) */ + createdBefore?: string; + /** Filter by encoded status */ + isEncoded?: boolean; + /** Filter by favorite status */ + isFavorite?: boolean; + /** Filter by motion photo status */ + isMotion?: boolean; + /** Filter assets not in any album */ + isNotInAlbum?: boolean; + /** Filter by offline status */ + isOffline?: boolean; + /** Filter by lens model */ + lensModel?: string | null; + /** Library ID to filter by */ + libraryId?: string | null; + /** Filter by camera make */ + make?: string | null; + /** Filter by camera model */ + model?: string | null; + /** Filter by OCR text content */ + ocr?: string; + /** Filter by person IDs */ + personIds?: string[]; + /** Filter by rating [1-5], or null for unrated */ + rating?: number | null; + /** Number of results to return */ + size?: number; + /** Filter by state/province name */ + state?: string | null; + /** Filter by tag IDs */ + tagIds?: string[] | null; + /** Filter by taken date (after) */ + takenAfter?: string; + /** Filter by taken date (before) */ + takenBefore?: string; + /** Filter by trash date (after) */ + trashedAfter?: string; + /** Filter by trash date (before) */ + trashedBefore?: string; + "type"?: AssetTypeEnum; + /** Filter by update date (after) */ + updatedAfter?: string; + /** Filter by update date (before) */ + updatedBefore?: string; + visibility?: AssetVisibility; + /** Include deleted assets */ + withDeleted?: boolean; + /** Include EXIF data in response */ + withExif?: boolean; + /** Include people data in response */ + withPeople?: boolean; + /** Include stacked assets */ + withStacked?: boolean; +}; +export type SmartSearchDto = { + /** Filter by album IDs */ + albumIds?: string[]; + /** Filter by city name */ + city?: string | null; + /** Filter by country name */ + country?: string | null; + /** Filter by creation date (after) */ + createdAfter?: string; + /** Filter by creation date (before) */ + createdBefore?: string; + /** Filter by encoded status */ + isEncoded?: boolean; + /** Filter by favorite status */ + isFavorite?: boolean; + /** Filter by motion photo status */ + isMotion?: boolean; + /** Filter assets not in any album */ + isNotInAlbum?: boolean; + /** Filter by offline status */ + isOffline?: boolean; + /** Search language code */ + language?: string; + /** Filter by lens model */ + lensModel?: string | null; + /** Library ID to filter by */ + libraryId?: string | null; + /** Filter by camera make */ + make?: string | null; + /** Filter by camera model */ + model?: string | null; + /** Filter by OCR text content */ + ocr?: string; + /** Page number */ + page?: number; + /** Filter by person IDs */ + personIds?: string[]; + /** Natural language search query */ + query?: string; + /** Asset ID to use as search reference */ + queryAssetId?: string; + /** Filter by rating [1-5], or null for unrated */ + rating?: number | null; + /** Number of results to return */ + size?: number; + /** Filter by state/province name */ + state?: string | null; + /** Filter by tag IDs */ + tagIds?: string[] | null; + /** Filter by taken date (after) */ + takenAfter?: string; + /** Filter by taken date (before) */ + takenBefore?: string; + /** Filter by trash date (after) */ + trashedAfter?: string; + /** Filter by trash date (before) */ + trashedBefore?: string; + "type"?: AssetTypeEnum; + /** Filter by update date (after) */ + updatedAfter?: string; + /** Filter by update date (before) */ + updatedBefore?: string; + visibility?: AssetVisibility; + /** Include deleted assets */ + withDeleted?: boolean; + /** Include EXIF data in response */ + withExif?: boolean; +}; +export type StatisticsSearchDto = { + /** Filter by album IDs */ + albumIds?: string[]; + /** Filter by city name */ + city?: string | null; + /** Filter by country name */ + country?: string | null; + /** Filter by creation date (after) */ + createdAfter?: string; + /** Filter by creation date (before) */ + createdBefore?: string; + /** Filter by description text */ + description?: string; + /** Filter by encoded status */ + isEncoded?: boolean; + /** Filter by favorite status */ + isFavorite?: boolean; + /** Filter by motion photo status */ + isMotion?: boolean; + /** Filter assets not in any album */ + isNotInAlbum?: boolean; + /** Filter by offline status */ + isOffline?: boolean; + /** Filter by lens model */ + lensModel?: string | null; + /** Library ID to filter by */ + libraryId?: string | null; + /** Filter by camera make */ + make?: string | null; + /** Filter by camera model */ + model?: string | null; + /** Filter by OCR text content */ + ocr?: string; + /** Filter by person IDs */ + personIds?: string[]; + /** Filter by rating [1-5], or null for unrated */ + rating?: number | null; + /** Filter by state/province name */ + state?: string | null; + /** Filter by tag IDs */ + tagIds?: string[] | null; + /** Filter by taken date (after) */ + takenAfter?: string; + /** Filter by taken date (before) */ + takenBefore?: string; + /** Filter by trash date (after) */ + trashedAfter?: string; + /** Filter by trash date (before) */ + trashedBefore?: string; + "type"?: AssetTypeEnum; + /** Filter by update date (after) */ + updatedAfter?: string; + /** Filter by update date (before) */ + updatedBefore?: string; + visibility?: AssetVisibility; +}; +export type SearchStatisticsResponseDto = { + /** Total number of matching assets */ + total: number; +}; +export type ServerAboutResponseDto = { + /** Build identifier */ + build?: string; + /** Build image name */ + buildImage?: string; + /** Build image URL */ + buildImageUrl?: string; + /** Build URL */ + buildUrl?: string; + /** ExifTool version */ + exiftool?: string; + /** FFmpeg version */ + ffmpeg?: string; + /** ImageMagick version */ + imagemagick?: string; + /** libvips version */ + libvips?: string; + /** Whether the server is licensed */ + licensed: boolean; + /** Node.js version */ + nodejs?: string; + /** Repository name */ + repository?: string; + /** Repository URL */ + repositoryUrl?: string; + /** Source commit hash */ + sourceCommit?: string; + /** Source reference (branch/tag) */ + sourceRef?: string; + /** Source URL */ + sourceUrl?: string; + /** Third-party bug/feature URL */ + thirdPartyBugFeatureUrl?: string; + /** Third-party documentation URL */ + thirdPartyDocumentationUrl?: string; + /** Third-party source URL */ + thirdPartySourceUrl?: string; + /** Third-party support URL */ + thirdPartySupportUrl?: string; + /** Server version */ + version: string; + /** URL to version information */ + versionUrl: string; +}; +export type ServerApkLinksDto = { + /** APK download link for ARM64 v8a architecture */ + arm64v8a: string; + /** APK download link for ARM EABI v7a architecture */ + armeabiv7a: string; + /** APK download link for universal architecture */ + universal: string; + /** APK download link for x86_64 architecture */ + x86_64: string; +}; +export type ServerConfigDto = { + /** External domain URL */ + externalDomain: string; + /** Whether the server has been initialized */ + isInitialized: boolean; + /** Whether the admin has completed onboarding */ + isOnboarded: boolean; + /** Login page message */ + loginPageMessage: string; + /** Whether maintenance mode is active */ + maintenanceMode: boolean; + /** Map dark style URL */ + mapDarkStyleUrl: string; + /** Map light style URL */ + mapLightStyleUrl: string; + /** OAuth button text */ + oauthButtonText: string; + /** Whether public user registration is enabled */ + publicUsers: boolean; + /** Number of days before trashed assets are permanently deleted */ + trashDays: number; + /** Delay in days before deleted users are permanently removed */ + userDeleteDelay: number; +}; +export type ServerFeaturesDto = { + /** Whether config file is available */ + configFile: boolean; + /** Whether duplicate detection is enabled */ + duplicateDetection: boolean; + /** Whether email notifications are enabled */ + email: boolean; + /** Whether facial recognition is enabled */ + facialRecognition: boolean; + /** Whether face import is enabled */ + importFaces: boolean; + /** Whether map feature is enabled */ + map: boolean; + /** Whether OAuth is enabled */ + oauth: boolean; + /** Whether OAuth auto-launch is enabled */ + oauthAutoLaunch: boolean; + /** Whether OCR is enabled */ + ocr: boolean; + /** Whether password login is enabled */ + passwordLogin: boolean; + /** Whether reverse geocoding is enabled */ + reverseGeocoding: boolean; + /** Whether search is enabled */ + search: boolean; + /** Whether sidecar files are supported */ + sidecar: boolean; + /** Whether smart search is enabled */ + smartSearch: boolean; + /** Whether trash feature is enabled */ + trash: boolean; +}; +export type LicenseKeyDto = { + /** Activation key */ + activationKey: string; + /** License key (format: /^IM(SV|CL)(-[\dA-Za-z]{4}){8}$/) */ + licenseKey: string; +}; +export type ServerMediaTypesResponseDto = { + /** Supported image MIME types */ + image: string[]; + /** Supported sidecar MIME types */ + sidecar: string[]; + /** Supported video MIME types */ + video: string[]; +}; +export type ServerPingResponse = { + res: string; +}; +export type UsageByUserDto = { + /** Number of photos */ + photos: number; + /** User quota size in bytes (null if unlimited) */ + quotaSizeInBytes: number | null; + /** Total storage usage in bytes */ + usage: number; + /** Storage usage for photos in bytes */ + usagePhotos: number; + /** Storage usage for videos in bytes */ + usageVideos: number; + /** User ID */ + userId: string; + /** User name */ + userName: string; + /** Number of videos */ + videos: number; +}; +export type ServerStatsResponseDto = { + /** Total number of photos */ + photos: number; + /** Total storage usage in bytes */ + usage: number; + /** Array of usage for each user */ + usageByUser: UsageByUserDto[]; + /** Storage usage for photos in bytes */ + usagePhotos: number; + /** Storage usage for videos in bytes */ + usageVideos: number; + /** Total number of videos */ + videos: number; +}; +export type ServerStorageResponseDto = { + /** Available disk space (human-readable format) */ + diskAvailable: string; + /** Available disk space in bytes */ + diskAvailableRaw: number; + /** Total disk size (human-readable format) */ + diskSize: string; + /** Total disk size in bytes */ + diskSizeRaw: number; + /** Disk usage percentage (0-100) */ + diskUsagePercentage: number; + /** Used disk space (human-readable format) */ + diskUse: string; + /** Used disk space in bytes */ + diskUseRaw: number; +}; +export type ServerVersionResponseDto = { + /** Major version number */ + major: number; + /** Minor version number */ + minor: number; + /** Patch version number */ + patch: number; +}; +export type VersionCheckStateResponseDto = { + /** Last check timestamp */ + checkedAt: string | null; + /** Release version */ + releaseVersion: string | null; +}; +export type ServerVersionHistoryResponseDto = { + /** When this version was first seen */ + createdAt: string; + /** Version history entry ID */ + id: string; + /** Version string */ + version: string; +}; +export type SessionCreateDto = { + /** Device OS */ + deviceOS?: string; + /** Device type */ + deviceType?: string; + /** Session duration in seconds */ + duration?: number; +}; +export type SessionCreateResponseDto = { + /** App version */ + appVersion: string | null; + /** Creation date */ + createdAt: string; + /** Is current session */ + current: boolean; + /** Device OS */ + deviceOS: string; + /** Device type */ + deviceType: string; + /** Expiration date */ + expiresAt?: string; + /** Session ID */ + id: string; + /** Is pending sync reset */ + isPendingSyncReset: boolean; + /** Session token */ + token: string; + /** Last update date */ + updatedAt: string; +}; +export type SessionUpdateDto = { + /** Reset pending sync state */ + isPendingSyncReset?: boolean; +}; +export type SharedLinkResponseDto = { + album?: AlbumResponseDto; + /** Allow downloads */ + allowDownload: boolean; + /** Allow uploads */ + allowUpload: boolean; + assets: AssetResponseDto[]; + /** Creation date */ + createdAt: string; + /** Link description */ + description: string | null; + /** Expiration date */ + expiresAt: string | null; + /** Shared link ID */ + id: string; + /** Encryption key (base64url) */ + key: string; + /** Has password */ + password: string | null; + /** Show metadata */ + showMetadata: boolean; + /** Custom URL slug */ + slug: string | null; + "type": SharedLinkType; + /** Owner user ID */ + userId: string; +}; +export type SharedLinkCreateDto = { + /** Album ID (for album sharing) */ + albumId?: string; + /** Allow downloads */ + allowDownload?: boolean; + /** Allow uploads */ + allowUpload?: boolean; + /** Asset IDs (for individual assets) */ + assetIds?: string[]; + /** Link description */ + description?: string | null; + /** Expiration date */ + expiresAt?: string | null; + /** Link password */ + password?: string | null; + /** Show metadata */ + showMetadata?: boolean; + /** Custom URL slug */ + slug?: string | null; + "type": SharedLinkType; +}; +export type SharedLinkLoginDto = { + /** Shared link password */ + password: string; +}; +export type SharedLinkEditDto = { + /** Allow downloads */ + 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 */ + expiresAt?: string | null; + /** Link password */ + password?: string | null; + /** Show metadata */ + showMetadata?: boolean; + /** Custom URL slug */ + slug?: string | null; +}; +export type AssetIdsDto = { + /** Asset IDs */ + assetIds: string[]; +}; +export type AssetIdsResponseDto = { + /** Asset ID */ + assetId: string; + error?: AssetIdErrorReason; + /** Whether operation succeeded */ + success: boolean; +}; +export type StackResponseDto = { + assets: AssetResponseDto[]; + /** Stack ID */ + id: string; + /** Primary asset ID */ + primaryAssetId: string; +}; +export type StackCreateDto = { + /** Asset IDs (first becomes primary, min 2) */ + assetIds: string[]; +}; +export type StackUpdateDto = { + /** Primary asset ID */ + primaryAssetId?: string; +}; +export type SyncAckDeleteDto = { + /** Sync entity types to delete acks for */ + types?: SyncEntityType[]; +}; +export type SyncAckDto = { + /** Acknowledgment ID */ + ack: string; + "type": SyncEntityType; +}; +export type SyncAckSetDto = { + /** Acknowledgment IDs (max 1000) */ + acks: string[]; +}; +export type SyncStreamDto = { + /** Reset sync state */ + reset?: boolean; + /** Sync request types */ + types: SyncRequestType[]; +}; +export type DatabaseBackupConfig = { + /** Cron expression */ + cronExpression: string; + /** Enabled */ + enabled: boolean; + /** Keep last amount */ + keepLastAmount: number; +}; +export type SystemConfigBackupsDto = { + database: DatabaseBackupConfig; +}; +export type SystemConfigFFmpegDto = { + accel: TranscodeHWAccel; + /** Accelerated decode */ + accelDecode: boolean; + /** Accepted audio codecs */ + acceptedAudioCodecs: AudioCodec[]; + /** Accepted containers */ + acceptedContainers: VideoContainer[]; + /** Accepted video codecs */ + acceptedVideoCodecs: VideoCodec[]; + /** B-frames */ + bframes: number; + cqMode: CQMode; + /** CRF */ + crf: number; + /** GOP size */ + gopSize: number; + /** Max bitrate */ + maxBitrate: string; + /** Preferred hardware device */ + preferredHwDevice: string; + /** Preset */ + preset: string; + /** References */ + refs: number; + targetAudioCodec: AudioCodec; + /** Target resolution */ + targetResolution: string; + targetVideoCodec: VideoCodec; + /** Temporal AQ */ + temporalAQ: boolean; + /** Threads */ + threads: number; + tonemap: ToneMapping; + transcode: TranscodePolicy; + /** Two pass */ + twoPass: boolean; +}; +export type SystemConfigGeneratedFullsizeImageDto = { + /** Enabled */ + enabled: boolean; + format: ImageFormat; + /** Progressive */ + progressive?: boolean; + /** Quality */ + quality: number; +}; +export type SystemConfigGeneratedImageDto = { + format: ImageFormat; + /** Progressive */ + progressive?: boolean; + /** Quality */ + quality: number; + /** Size */ + size: number; +}; +export type SystemConfigImageDto = { + colorspace: Colorspace; + /** Extract embedded */ + extractEmbedded: boolean; + fullsize: SystemConfigGeneratedFullsizeImageDto; + preview: SystemConfigGeneratedImageDto; + thumbnail: SystemConfigGeneratedImageDto; +}; +export type JobSettingsDto = { + /** Concurrency */ + concurrency: number; +}; +export type SystemConfigJobDto = { + backgroundTask: JobSettingsDto; + editor: JobSettingsDto; + faceDetection: JobSettingsDto; + library: JobSettingsDto; + metadataExtraction: JobSettingsDto; + migration: JobSettingsDto; + notifications: JobSettingsDto; + ocr: JobSettingsDto; + search: JobSettingsDto; + sidecar: JobSettingsDto; + smartSearch: JobSettingsDto; + thumbnailGeneration: JobSettingsDto; + videoConversion: JobSettingsDto; + workflow: JobSettingsDto; +}; +export type SystemConfigLibraryScanDto = { + /** Cron expression */ + cronExpression: string; + /** Enabled */ + enabled: boolean; +}; +export type SystemConfigLibraryWatchDto = { + /** Enabled */ + enabled: boolean; +}; +export type SystemConfigLibraryDto = { + scan: SystemConfigLibraryScanDto; + watch: SystemConfigLibraryWatchDto; +}; +export type SystemConfigLoggingDto = { + /** Enabled */ + enabled: boolean; + level: LogLevel; +}; +export type MachineLearningAvailabilityChecksDto = { + /** Enabled */ + enabled: boolean; + interval: number; + timeout: number; +}; +export type ClipConfig = { + /** Whether the task is enabled */ + enabled: boolean; + /** Name of the model to use */ + modelName: string; +}; +export type DuplicateDetectionConfig = { + /** Whether the task is enabled */ + enabled: boolean; + /** Maximum distance threshold for duplicate detection */ + maxDistance: number; +}; +export type FacialRecognitionConfig = { + /** Whether the task is enabled */ + enabled: boolean; + /** Maximum distance threshold for face recognition */ + maxDistance: number; + /** Minimum number of faces required for recognition */ + minFaces: number; + /** Minimum confidence score for face detection */ + minScore: number; + /** Name of the model to use */ + modelName: string; +}; +export type OcrConfig = { + /** Whether the task is enabled */ + enabled: boolean; + /** Maximum resolution for OCR processing */ + maxResolution: number; + /** Minimum confidence score for text detection */ + minDetectionScore: number; + /** Minimum confidence score for text recognition */ + minRecognitionScore: number; + /** Name of the model to use */ + modelName: string; +}; +export type SystemConfigMachineLearningDto = { + availabilityChecks: MachineLearningAvailabilityChecksDto; + clip: ClipConfig; + duplicateDetection: DuplicateDetectionConfig; + /** Enabled */ + enabled: boolean; + facialRecognition: FacialRecognitionConfig; + ocr: OcrConfig; + /** ML service URLs */ + urls: string[]; +}; +export type SystemConfigMapDto = { + /** Dark map style URL */ + darkStyle: string; + /** Enabled */ + enabled: boolean; + /** Light map style URL */ + lightStyle: string; +}; +export type SystemConfigFacesDto = { + /** Import */ + "import": boolean; +}; +export type SystemConfigMetadataDto = { + faces: SystemConfigFacesDto; +}; +export type SystemConfigNewVersionCheckDto = { + /** Enabled */ + enabled: boolean; +}; +export type SystemConfigNightlyTasksDto = { + /** Cluster new faces */ + clusterNewFaces: boolean; + /** Database cleanup */ + databaseCleanup: boolean; + /** Generate memories */ + generateMemories: boolean; + /** Missing thumbnails */ + missingThumbnails: boolean; + /** Start time */ + startTime: string; + /** Sync quota usage */ + syncQuotaUsage: boolean; +}; +export type SystemConfigNotificationsDto = { + smtp: SystemConfigSmtpDto; +}; +export type SystemConfigOAuthDto = { + /** Allow insecure requests */ + allowInsecureRequests: boolean; + /** Auto launch */ + autoLaunch: boolean; + /** Auto register */ + autoRegister: boolean; + /** Button text */ + buttonText: string; + /** Client ID */ + clientId: string; + /** Client secret */ + clientSecret: string; + /** Default storage quota */ + defaultStorageQuota: number | null; + /** Enabled */ + enabled: boolean; + /** End session endpoint */ + endSessionEndpoint: string; + /** Issuer URL */ + issuerUrl: string; + /** Mobile override enabled */ + mobileOverrideEnabled: boolean; + /** Mobile redirect URI (set to empty string to disable) */ + mobileRedirectUri: string; + /** Profile signing algorithm */ + profileSigningAlgorithm: string; + /** OAuth prompt parameter (e.g. select_account, login, consent) */ + prompt: string; + /** Role claim */ + roleClaim: string; + /** Scope */ + scope: string; + /** Signing algorithm */ + signingAlgorithm: string; + /** Storage label claim */ + storageLabelClaim: string; + /** Storage quota claim */ + storageQuotaClaim: string; + /** Timeout */ + timeout: number; + tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod; +}; +export type SystemConfigPasswordLoginDto = { + /** Enabled */ + enabled: boolean; +}; +export type SystemConfigReverseGeocodingDto = { + /** Enabled */ + enabled: boolean; +}; +export type SystemConfigServerDto = { + /** External domain */ + externalDomain: string; + /** Login page message */ + loginPageMessage: string; + /** Public users */ + publicUsers: boolean; +}; +export type SystemConfigStorageTemplateDto = { + /** Enabled */ + enabled: boolean; + /** Hash verification enabled */ + hashVerificationEnabled: boolean; + /** Template */ + template: string; +}; +export type SystemConfigTemplateEmailsDto = { + /** Album invite template */ + albumInviteTemplate: string; + /** Album update template */ + albumUpdateTemplate: string; + /** Welcome template */ + welcomeTemplate: string; +}; +export type SystemConfigTemplatesDto = { + email: SystemConfigTemplateEmailsDto; +}; +export type SystemConfigThemeDto = { + /** Custom CSS for theming */ + customCss: string; +}; +export type SystemConfigTrashDto = { + /** Days */ + days: number; + /** Enabled */ + enabled: boolean; +}; +export type SystemConfigUserDto = { + /** Delete delay */ + deleteDelay: number; +}; +export type SystemConfigDto = { + backup: SystemConfigBackupsDto; + ffmpeg: SystemConfigFFmpegDto; + image: SystemConfigImageDto; + job: SystemConfigJobDto; + library: SystemConfigLibraryDto; + logging: SystemConfigLoggingDto; + machineLearning: SystemConfigMachineLearningDto; + map: SystemConfigMapDto; + metadata: SystemConfigMetadataDto; + newVersionCheck: SystemConfigNewVersionCheckDto; + nightlyTasks: SystemConfigNightlyTasksDto; + notifications: SystemConfigNotificationsDto; + oauth: SystemConfigOAuthDto; + passwordLogin: SystemConfigPasswordLoginDto; + reverseGeocoding: SystemConfigReverseGeocodingDto; + server: SystemConfigServerDto; + storageTemplate: SystemConfigStorageTemplateDto; + templates: SystemConfigTemplatesDto; + theme: SystemConfigThemeDto; + trash: SystemConfigTrashDto; + user: SystemConfigUserDto; +}; +export type SystemConfigTemplateStorageOptionDto = { + /** Available day format options for storage template */ + dayOptions: string[]; + /** Available hour format options for storage template */ + hourOptions: string[]; + /** Available minute format options for storage template */ + minuteOptions: string[]; + /** Available month format options for storage template */ + monthOptions: string[]; + /** Available preset template options */ + presetOptions: string[]; + /** Available second format options for storage template */ + secondOptions: string[]; + /** Available week format options for storage template */ + weekOptions: string[]; + /** Available year format options for storage template */ + yearOptions: string[]; +}; +export type AdminOnboardingUpdateDto = { + /** Is admin onboarded */ + isOnboarded: boolean; +}; +export type ReverseGeocodingStateResponseDto = { + /** Last import file name */ + lastImportFileName: string | null; + /** Last update timestamp */ + lastUpdate: string | null; +}; +export type TagCreateDto = { + /** Tag color (hex) */ + color?: string | null; + /** Tag name */ + name: string; + /** Parent tag ID */ + parentId?: string | null; +}; +export type TagUpsertDto = { + /** Tag names to upsert */ + tags: string[]; +}; +export type TagBulkAssetsDto = { + /** Asset IDs */ + assetIds: string[]; + /** Tag IDs */ + tagIds: string[]; +}; +export type TagBulkAssetsResponseDto = { + /** Number of assets tagged */ + count: number; +}; +export type TagUpdateDto = { + /** Tag color (hex) */ + color?: string | null; +}; +export type TimeBucketAssetResponseDto = { + /** Array of city names extracted from EXIF GPS data */ + city: (string | null)[]; + /** Array of country names extracted from EXIF GPS data */ + country: (string | null)[]; + /** Array of video/gif durations in milliseconds (null for static images) */ + duration: (number | null)[]; + /** Array of file creation timestamps in UTC */ + fileCreatedAt: string[]; + /** Array of asset IDs in the time bucket */ + id: string[]; + /** Array indicating whether each asset is favorited */ + isFavorite: boolean[]; + /** Array indicating whether each asset is an image (false for videos) */ + isImage: boolean[]; + /** Array indicating whether each asset is in the trash */ + isTrashed: boolean[]; + /** Array of latitude coordinates extracted from EXIF GPS data */ + latitude?: (number | null)[]; + /** Array of live photo video asset IDs (null for non-live photos) */ + livePhotoVideoId: (string | null)[]; + /** 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. */ + localOffsetHours: number[]; + /** Array of longitude coordinates extracted from EXIF GPS data */ + longitude?: (number | null)[]; + /** Array of owner IDs for each asset */ + ownerId: string[]; + /** Array of projection types for 360° content (e.g., "EQUIRECTANGULAR", "CUBEFACE", "CYLINDRICAL") */ + projectionType: (string | null)[]; + /** Array of aspect ratios (width/height) for each asset */ + ratio: number[]; + /** Array of stack information as [stackId, assetCount] tuples (null for non-stacked assets) */ + stack?: (string[] | null)[]; + /** Array of BlurHash strings for generating asset previews (base64 encoded) */ + thumbhash: (string | null)[]; + /** Array of visibility statuses for each asset (e.g., ARCHIVE, TIMELINE, HIDDEN, LOCKED) */ + visibility: AssetVisibility[]; +}; +export type TimeBucketsResponseDto = { + /** Number of assets in this time bucket */ + count: number; + /** Time bucket identifier in YYYY-MM-DD format representing the start of the time period */ + timeBucket: string; +}; +export type TrashResponseDto = { + /** Number of items in trash */ + count: number; +}; +export type UserUpdateMeDto = { + avatarColor?: (UserAvatarColor) | null; + /** User email */ + email?: string; + /** User name */ + name?: string; + /** User password (deprecated, use change password endpoint) */ + password?: string; +}; +export type OnboardingResponseDto = { + /** Is user onboarded */ + isOnboarded: boolean; +}; +export type OnboardingDto = { + /** Is user onboarded */ + isOnboarded: boolean; +}; +export type CreateProfileImageDto = { + /** Profile image file */ + file: Blob; +}; +export type CreateProfileImageResponseDto = { + /** Profile image change date */ + profileChangedAt: string; + /** Profile image file path */ + profileImagePath: string; + /** 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 WorkflowResponseDto = { + /** Workflow actions */ + actions: WorkflowActionResponseDto[]; + /** Creation date */ + createdAt: string; + /** Workflow description */ + description: string; + /** 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; +}; +export type WorkflowCreateDto = { + /** Workflow actions */ + actions: WorkflowActionItemDto[]; + /** Workflow description */ + description?: string; + /** Workflow enabled */ + enabled?: boolean; + /** Workflow filters */ + filters: WorkflowFilterItemDto[]; + /** Workflow name */ + name: string; + triggerType: PluginTriggerType; +}; +export type WorkflowUpdateDto = { + /** Workflow actions */ + actions?: WorkflowActionItemDto[]; + /** Workflow description */ + description?: string; + /** Workflow enabled */ + enabled?: boolean; + /** Workflow filters */ + filters?: WorkflowFilterItemDto[]; + /** Workflow name */ + name?: string; + triggerType?: PluginTriggerType; +}; +export type LicenseResponseDto = UserLicense; +export type SyncAckV1 = {}; +export type SyncAlbumDeleteV1 = { + /** Album ID */ + albumId: string; +}; +export type SyncAlbumToAssetDeleteV1 = { + /** Album ID */ + albumId: string; + /** Asset ID */ + assetId: string; +}; +export type SyncAlbumToAssetV1 = { + /** Album ID */ + albumId: string; + /** Asset ID */ + assetId: string; +}; +export type SyncAlbumUserDeleteV1 = { + /** Album ID */ + albumId: string; + /** User ID */ + userId: string; +}; +export type SyncAlbumUserV1 = { + /** Album ID */ + albumId: string; + role: AlbumUserRole; + /** User ID */ + userId: string; +}; +export type SyncAlbumV1 = { + /** Created at */ + createdAt: string; + /** Album description */ + description: string; + /** Album ID */ + id: string; + /** Is activity enabled */ + isActivityEnabled: boolean; + /** Album name */ + name: string; + order: AssetOrder; + /** Owner ID */ + ownerId: string; + /** Thumbnail asset ID */ + thumbnailAssetId: string | null; + /** Updated at */ + updatedAt: string; +}; +export type SyncAlbumV2 = { + /** Created at */ + createdAt: string; + /** Album description */ + description: string; + /** Album ID */ + id: string; + /** Is activity enabled */ + isActivityEnabled: boolean; + /** Album name */ + name: string; + order: AssetOrder; + /** Thumbnail asset ID */ + thumbnailAssetId: string | null; + /** Updated at */ + updatedAt: string; +}; +export type SyncAssetDeleteV1 = { + /** Asset ID */ + assetId: string; +}; +export type SyncAssetEditDeleteV1 = { + /** Edit ID */ + editId: string; +}; +export type SyncAssetEditV1 = { + action: AssetEditAction; + /** Asset ID */ + assetId: string; + /** Edit ID */ + id: string; + /** Edit parameters */ + parameters: { + [key: string]: any; + }; + /** Edit sequence */ + sequence: number; +}; +export type SyncAssetExifV1 = { + /** Asset ID */ + assetId: string; + /** City */ + city: string | null; + /** Country */ + country: string | null; + /** Date time original */ + dateTimeOriginal: string | null; + /** Description */ + description: string | null; + /** Exif image height */ + exifImageHeight: number | null; + /** Exif image width */ + exifImageWidth: number | null; + /** Exposure time */ + exposureTime: string | null; + /** F number */ + fNumber: number | null; + /** File size in byte */ + fileSizeInByte: number | null; + /** Focal length */ + focalLength: number | null; + /** FPS */ + fps: number | null; + /** ISO */ + iso: number | null; + /** Latitude */ + latitude: number | null; + /** Lens model */ + lensModel: string | null; + /** Longitude */ + longitude: number | null; + /** Make */ + make: string | null; + /** Model */ + model: string | null; + /** Modify date */ + modifyDate: string | null; + /** Orientation */ + orientation: string | null; + /** Profile description */ + profileDescription: string | null; + /** Projection type */ + projectionType: string | null; + /** Rating */ + rating: number | null; + /** State */ + state: string | null; + /** Time zone */ + timeZone: string | null; +}; +export type SyncAssetFaceDeleteV1 = { + /** Asset face ID */ + assetFaceId: string; +}; +export type SyncAssetFaceV1 = { + /** Asset ID */ + assetId: string; + /** Bounding box X1 */ + boundingBoxX1: number; + /** Bounding box X2 */ + boundingBoxX2: number; + /** Bounding box Y1 */ + boundingBoxY1: number; + /** Bounding box Y2 */ + boundingBoxY2: number; + /** Asset face ID */ + id: string; + /** Image height */ + imageHeight: number; + /** Image width */ + imageWidth: number; + /** Person ID */ + personId: string | null; + /** Source type */ + sourceType: string; +}; +export type SyncAssetFaceV2 = { + /** Asset ID */ + assetId: string; + /** Bounding box X1 */ + boundingBoxX1: number; + /** Bounding box X2 */ + boundingBoxX2: number; + /** Bounding box Y1 */ + boundingBoxY1: number; + /** Bounding box Y2 */ + boundingBoxY2: number; + /** Face deleted at */ + deletedAt: string | null; + /** Asset face ID */ + id: string; + /** Image height */ + imageHeight: number; + /** Image width */ + imageWidth: number; + /** Is the face visible in the asset */ + isVisible: boolean; + /** Person ID */ + personId: string | null; + /** Source type */ + sourceType: string; +}; +export type SyncAssetMetadataDeleteV1 = { + /** Asset ID */ + assetId: string; + /** Key */ + key: string; +}; +export type SyncAssetMetadataV1 = { + /** Asset ID */ + assetId: string; + /** Key */ + key: string; + /** Value */ + value: { + [key: string]: any; + }; +}; +export type SyncAssetV1 = { + /** Checksum */ + checksum: string; + /** Deleted at */ + deletedAt: string | null; + /** Duration */ + duration: string | null; + /** File created at */ + fileCreatedAt: string | null; + /** File modified at */ + fileModifiedAt: string | null; + /** Asset height */ + height: number | null; + /** Asset ID */ + id: string; + /** Is edited */ + isEdited: boolean; + /** Is favorite */ + isFavorite: boolean; + /** Library ID */ + libraryId: string | null; + /** Live photo video ID */ + livePhotoVideoId: string | null; + /** Local date time */ + localDateTime: string | null; + /** Original file name */ + originalFileName: string; + /** Owner ID */ + ownerId: string; + /** Stack ID */ + stackId: string | null; + /** Thumbhash */ + thumbhash: string | null; + "type": AssetTypeEnum; + visibility: AssetVisibility; + /** Asset width */ + width: number | null; +}; +export type SyncAssetV2 = { + /** Checksum */ + checksum: string; + /** Deleted at */ + deletedAt: string | null; + /** Duration */ + duration: number | null; + /** File created at */ + fileCreatedAt: string | null; + /** File modified at */ + fileModifiedAt: string | null; + /** Asset height */ + height: number | null; + /** Asset ID */ + id: string; + /** Is edited */ + isEdited: boolean; + /** Is favorite */ + isFavorite: boolean; + /** Library ID */ + libraryId: string | null; + /** Live photo video ID */ + livePhotoVideoId: string | null; + /** Local date time */ + localDateTime: string | null; + /** Original file name */ + originalFileName: string; + /** Owner ID */ + ownerId: string; + /** Stack ID */ + stackId: string | null; + /** Thumbhash */ + thumbhash: string | null; + "type": AssetTypeEnum; + visibility: AssetVisibility; + /** Asset width */ + width: number | null; +}; +export type SyncAuthUserV1 = { + avatarColor?: (UserAvatarColor) | null; + /** User deleted at */ + deletedAt: string | null; + /** User email */ + email: string; + /** User has profile image */ + hasProfileImage: boolean; + /** User ID */ + id: string; + /** User is admin */ + isAdmin: boolean; + /** User name */ + name: string; + /** User OAuth ID */ + oauthId: string; + /** User pin code */ + pinCode: string | null; + /** User profile changed at */ + profileChangedAt: string; + /** Quota size in bytes */ + quotaSizeInBytes: number | null; + /** Quota usage in bytes */ + quotaUsageInBytes: number; + /** User storage label */ + storageLabel: string | null; +}; +export type SyncCompleteV1 = {}; +export type SyncMemoryAssetDeleteV1 = { + /** Asset ID */ + assetId: string; + /** Memory ID */ + memoryId: string; +}; +export type SyncMemoryAssetV1 = { + /** Asset ID */ + assetId: string; + /** Memory ID */ + memoryId: string; +}; +export type SyncMemoryDeleteV1 = { + /** Memory ID */ + memoryId: string; +}; +export type SyncMemoryV1 = { + /** Created at */ + createdAt: string; + /** Data */ + data: { + [key: string]: any; + }; + /** Deleted at */ + deletedAt: string | null; + /** Hide at */ + hideAt: string | null; + /** Memory ID */ + id: string; + /** Is saved */ + isSaved: boolean; + /** Memory at */ + memoryAt: string; + /** Owner ID */ + ownerId: string; + /** Seen at */ + seenAt: string | null; + /** Show at */ + showAt: string | null; + "type": MemoryType; + /** Updated at */ + updatedAt: string; +}; +export type SyncPartnerDeleteV1 = { + /** Shared by ID */ + sharedById: string; + /** Shared with ID */ + sharedWithId: string; +}; +export type SyncPartnerV1 = { + /** In timeline */ + inTimeline: boolean; + /** Shared by ID */ + sharedById: string; + /** Shared with ID */ + sharedWithId: string; +}; +export type SyncPersonDeleteV1 = { + /** Person ID */ + personId: string; +}; +export type SyncPersonV1 = { + /** Birth date */ + birthDate: string | null; + /** Color */ + color: string | null; + /** Created at */ + createdAt: string; + /** Face asset ID */ + faceAssetId: string | null; + /** Person ID */ + id: string; + /** Is favorite */ + isFavorite: boolean; + /** Is hidden */ + isHidden: boolean; + /** Person name */ + name: string; + /** Owner ID */ + ownerId: string; + /** Updated at */ + updatedAt: string; +}; +export type SyncResetV1 = {}; +export type SyncStackDeleteV1 = { + /** Stack ID */ + stackId: string; +}; +export type SyncStackV1 = { + /** Created at */ + createdAt: string; + /** Stack ID */ + id: string; + /** Owner ID */ + ownerId: string; + /** Primary asset ID */ + primaryAssetId: string; + /** Updated at */ + updatedAt: string; +}; +export type SyncUserDeleteV1 = { + /** User ID */ + userId: string; +}; +export type SyncUserMetadataDeleteV1 = { + key: UserMetadataKey; + /** User ID */ + userId: string; +}; +export type SyncUserMetadataV1 = { + key: UserMetadataKey; + /** User ID */ + userId: string; + /** User metadata value */ + value: { + [key: string]: any; + }; +}; +export type SyncUserV1 = { + avatarColor?: (UserAvatarColor) | null; + /** User deleted at */ + deletedAt: string | null; + /** User email */ + email: string; + /** User has profile image */ + hasProfileImage: boolean; + /** User ID */ + id: string; + /** User name */ + name: string; + /** User profile changed at */ + profileChangedAt: string; +}; +/** + * List all activities + */ +export declare function getActivities({ albumId, assetId, level, $type, userId }: { + albumId: string; + assetId?: string; + level?: ReactionLevel; + $type?: ReactionType; + userId?: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Create an activity + */ +export declare function createActivity({ activityCreateDto }: { + activityCreateDto: ActivityCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve activity statistics + */ +export declare function getActivityStatistics({ albumId, assetId }: { + albumId: string; + assetId?: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete an activity + */ +export declare function deleteActivity({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Unlink all OAuth accounts + */ +export declare function unlinkAllOAuthAccountsAdmin(opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete database backup + */ +export declare function deleteDatabaseBackup({ databaseBackupDeleteDto }: { + databaseBackupDeleteDto: DatabaseBackupDeleteDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * List database backups + */ +export declare function listDatabaseBackups(opts?: Oazapfts.RequestOpts): Promise; +/** + * Start database backup restore flow + */ +export declare function startDatabaseRestoreFlow(opts?: Oazapfts.RequestOpts): Promise; +/** + * Upload database backup + */ +export declare function uploadDatabaseBackup({ databaseBackupUploadDto }: { + databaseBackupUploadDto: DatabaseBackupUploadDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Download database backup + */ +export declare function downloadDatabaseBackup({ filename }: { + filename: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Set maintenance mode + */ +export declare function setMaintenanceMode({ setMaintenanceModeDto }: { + setMaintenanceModeDto: SetMaintenanceModeDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Detect existing install + */ +export declare function detectPriorInstall(opts?: Oazapfts.RequestOpts): Promise; +/** + * Log into maintenance mode + */ +export declare function maintenanceLogin({ maintenanceLoginDto }: { + maintenanceLoginDto: MaintenanceLoginDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get maintenance mode status + */ +export declare function getMaintenanceStatus(opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a notification + */ +export declare function createNotification({ notificationCreateDto }: { + notificationCreateDto: NotificationCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Render email template + */ +export declare function getNotificationTemplateAdmin({ name, templateDto }: { + name: string; + templateDto: TemplateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Send test email + */ +export declare function sendTestEmailAdmin({ systemConfigSmtpDto }: { + systemConfigSmtpDto: SystemConfigSmtpDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Search users + */ +export declare function searchUsersAdmin({ id, withDeleted }: { + id?: string; + withDeleted?: boolean; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a user + */ +export declare function createUserAdmin({ userAdminCreateDto }: { + userAdminCreateDto: UserAdminCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete a user + */ +export declare function deleteUserAdmin({ id, userAdminDeleteDto }: { + id: string; + userAdminDeleteDto: UserAdminDeleteDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve a user + */ +export declare function getUserAdmin({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update a user + */ +export declare function updateUserAdmin({ id, userAdminUpdateDto }: { + id: string; + userAdminUpdateDto: UserAdminUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve user preferences + */ +export declare function getUserPreferencesAdmin({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update user preferences + */ +export declare function updateUserPreferencesAdmin({ id, userPreferencesUpdateDto }: { + id: string; + userPreferencesUpdateDto: UserPreferencesUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Restore a deleted user + */ +export declare function restoreUserAdmin({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve user sessions + */ +export declare function getUserSessionsAdmin({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve user statistics + */ +export declare function getUserStatisticsAdmin({ id, isFavorite, isTrashed, visibility }: { + id: string; + isFavorite?: boolean; + isTrashed?: boolean; + visibility?: AssetVisibility; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * List all albums + */ +export declare function getAllAlbums({ assetId, shared }: { + assetId?: string; + shared?: boolean; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Create an album + */ +export declare function createAlbum({ createAlbumDto }: { + createAlbumDto: CreateAlbumDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Add assets to albums + */ +export declare function addAssetsToAlbums({ albumsAddAssetsDto }: { + albumsAddAssetsDto: AlbumsAddAssetsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve album statistics + */ +export declare function getAlbumStatistics(opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete an album + */ +export declare function deleteAlbum({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve an album + */ +export declare function getAlbumInfo({ id, key, slug }: { + id: string; + key?: string; + slug?: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update an album + */ +export declare function updateAlbumInfo({ id, updateAlbumDto }: { + id: string; + updateAlbumDto: UpdateAlbumDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Remove assets from an album + */ +export declare function removeAssetFromAlbum({ id, bulkIdsDto }: { + id: string; + bulkIdsDto: BulkIdsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Add assets to an album + */ +export declare function addAssetsToAlbum({ id, bulkIdsDto }: { + id: string; + bulkIdsDto: BulkIdsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve album map markers + */ +export declare function getAlbumMapMarkers({ id, key, slug }: { + id: string; + key?: string; + slug?: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Remove user from album + */ +export declare function removeUserFromAlbum({ id, userId }: { + id: string; + userId: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update user role + */ +export declare function updateAlbumUser({ id, userId, updateAlbumUserDto }: { + id: string; + userId: string; + updateAlbumUserDto: UpdateAlbumUserDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Share album with users + */ +export declare function addUsersToAlbum({ id, addUsersDto }: { + id: string; + addUsersDto: AddUsersDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * List all API keys + */ +export declare function getApiKeys(opts?: Oazapfts.RequestOpts): Promise; +/** + * Create an API key + */ +export declare function createApiKey({ apiKeyCreateDto }: { + apiKeyCreateDto: ApiKeyCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve the current API key + */ +export declare function getMyApiKey(opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete an API key + */ +export declare function deleteApiKey({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve an API key + */ +export declare function getApiKey({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update an API key + */ +export declare function updateApiKey({ id, apiKeyUpdateDto }: { + id: string; + apiKeyUpdateDto: ApiKeyUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete assets + */ +export declare function deleteAssets({ assetBulkDeleteDto }: { + assetBulkDeleteDto: AssetBulkDeleteDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Upload asset + */ +export declare function uploadAsset({ key, slug, xImmichChecksum, assetMediaCreateDto }: { + key?: string; + slug?: string; + xImmichChecksum?: string; + assetMediaCreateDto: AssetMediaCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update assets + */ +export declare function updateAssets({ assetBulkUpdateDto }: { + assetBulkUpdateDto: AssetBulkUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Check bulk upload + */ +export declare function checkBulkUpload({ assetBulkUploadCheckDto }: { + assetBulkUploadCheckDto: AssetBulkUploadCheckDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Copy asset + */ +export declare function copyAsset({ assetCopyDto }: { + assetCopyDto: AssetCopyDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Run an asset job + */ +export declare function runAssetJobs({ assetJobsDto }: { + assetJobsDto: AssetJobsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete asset metadata + */ +export declare function deleteBulkAssetMetadata({ assetMetadataBulkDeleteDto }: { + assetMetadataBulkDeleteDto: AssetMetadataBulkDeleteDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Upsert asset metadata + */ +export declare function updateBulkAssetMetadata({ assetMetadataBulkUpsertDto }: { + assetMetadataBulkUpsertDto: AssetMetadataBulkUpsertDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get asset statistics + */ +export declare function getAssetStatistics({ isFavorite, isTrashed, visibility }: { + isFavorite?: boolean; + isTrashed?: boolean; + visibility?: AssetVisibility; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve an asset + */ +export declare function getAssetInfo({ id, key, slug }: { + id: string; + key?: string; + slug?: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update an asset + */ +export declare function updateAsset({ id, updateAssetDto }: { + id: string; + updateAssetDto: UpdateAssetDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Remove edits from an existing asset + */ +export declare function removeAssetEdits({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve edits for an existing asset + */ +export declare function getAssetEdits({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Apply edits to an existing asset + */ +export declare function editAsset({ id, assetEditsCreateDto }: { + id: string; + assetEditsCreateDto: AssetEditsCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get asset metadata + */ +export declare function getAssetMetadata({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update asset metadata + */ +export declare function updateAssetMetadata({ id, assetMetadataUpsertDto }: { + id: string; + assetMetadataUpsertDto: AssetMetadataUpsertDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete asset metadata by key + */ +export declare function deleteAssetMetadata({ id, key }: { + id: string; + key: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve asset metadata by key + */ +export declare function getAssetMetadataByKey({ id, key }: { + id: string; + key: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve asset OCR data + */ +export declare function getAssetOcr({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Download original asset + */ +export declare function downloadAsset({ edited, id, key, slug }: { + edited?: boolean; + id: string; + key?: string; + slug?: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * View asset thumbnail + */ +export declare function viewAsset({ edited, id, key, size, slug }: { + edited?: boolean; + id: string; + key?: string; + size?: AssetMediaSize; + slug?: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Play asset video + */ +export declare function playAssetVideo({ id, key, slug }: { + id: string; + key?: string; + slug?: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Register admin + */ +export declare function signUpAdmin({ signUpDto }: { + signUpDto: SignUpDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Change password + */ +export declare function changePassword({ changePasswordDto }: { + changePasswordDto: ChangePasswordDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Login + */ +export declare function login({ loginCredentialDto }: { + loginCredentialDto: LoginCredentialDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Logout + */ +export declare function logout(opts?: Oazapfts.RequestOpts): Promise; +/** + * Reset pin code + */ +export declare function resetPinCode({ pinCodeResetDto }: { + pinCodeResetDto: PinCodeResetDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Setup pin code + */ +export declare function setupPinCode({ pinCodeSetupDto }: { + pinCodeSetupDto: PinCodeSetupDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Change pin code + */ +export declare function changePinCode({ pinCodeChangeDto }: { + pinCodeChangeDto: PinCodeChangeDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Lock auth session + */ +export declare function lockAuthSession(opts?: Oazapfts.RequestOpts): Promise; +/** + * Unlock auth session + */ +export declare function unlockAuthSession({ sessionUnlockDto }: { + sessionUnlockDto: SessionUnlockDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve auth status + */ +export declare function getAuthStatus(opts?: Oazapfts.RequestOpts): Promise; +/** + * Validate access token + */ +export declare function validateAccessToken(opts?: Oazapfts.RequestOpts): Promise; +/** + * Download asset archive + */ +export declare function downloadArchive({ key, slug, downloadArchiveDto }: { + key?: string; + slug?: string; + downloadArchiveDto: DownloadArchiveDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve download information + */ +export declare function getDownloadInfo({ key, slug, downloadInfoDto }: { + key?: string; + slug?: string; + downloadInfoDto: DownloadInfoDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete duplicates + */ +export declare function deleteDuplicates({ bulkIdsDto }: { + bulkIdsDto: BulkIdsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve duplicates + */ +export declare function getAssetDuplicates(opts?: Oazapfts.RequestOpts): Promise; +/** + * Resolve duplicate groups + */ +export declare function resolveDuplicates({ duplicateResolveDto }: { + duplicateResolveDto: DuplicateResolveDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Dismiss a duplicate group + */ +export declare function deleteDuplicate({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve faces for asset + */ +export declare function getFaces({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a face + */ +export declare function createFace({ assetFaceCreateDto }: { + assetFaceCreateDto: AssetFaceCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete a face + */ +export declare function deleteFace({ id, assetFaceDeleteDto }: { + id: string; + assetFaceDeleteDto: AssetFaceDeleteDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Re-assign a face to another person + */ +export declare function reassignFacesById({ id, faceDto }: { + id: string; + faceDto: FaceDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve queue counts and status + */ +export declare function getQueuesLegacy(opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a manual job + */ +export declare function createJob({ jobCreateDto }: { + jobCreateDto: JobCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Run jobs + */ +export declare function runQueueCommandLegacy({ name, queueCommandDto }: { + name: QueueName; + queueCommandDto: QueueCommandDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve libraries + */ +export declare function getAllLibraries(opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a library + */ +export declare function createLibrary({ createLibraryDto }: { + createLibraryDto: CreateLibraryDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete a library + */ +export declare function deleteLibrary({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve a library + */ +export declare function getLibrary({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update a library + */ +export declare function updateLibrary({ id, updateLibraryDto }: { + id: string; + updateLibraryDto: UpdateLibraryDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Scan a library + */ +export declare function scanLibrary({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve library statistics + */ +export declare function getLibraryStatistics({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Validate library settings + */ +export declare function validate({ id, validateLibraryDto }: { + id: string; + validateLibraryDto: ValidateLibraryDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve map markers + */ +export declare function getMapMarkers({ fileCreatedAfter, fileCreatedBefore, isArchived, isFavorite, withPartners, withSharedAlbums }: { + fileCreatedAfter?: string; + fileCreatedBefore?: string; + isArchived?: boolean; + isFavorite?: boolean; + withPartners?: boolean; + withSharedAlbums?: boolean; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Reverse geocode coordinates + */ +export declare function reverseGeocode({ lat, lon }: { + lat: number; + lon: number; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve memories + */ +export declare function searchMemories({ $for, isSaved, isTrashed, order, size, $type }: { + $for?: string; + isSaved?: boolean; + isTrashed?: boolean; + order?: MemorySearchOrder; + size?: number; + $type?: MemoryType; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a memory + */ +export declare function createMemory({ memoryCreateDto }: { + memoryCreateDto: MemoryCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve memories statistics + */ +export declare function memoriesStatistics({ $for, isSaved, isTrashed, order, size, $type }: { + $for?: string; + isSaved?: boolean; + isTrashed?: boolean; + order?: MemorySearchOrder; + size?: number; + $type?: MemoryType; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete a memory + */ +export declare function deleteMemory({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve a memory + */ +export declare function getMemory({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update a memory + */ +export declare function updateMemory({ id, memoryUpdateDto }: { + id: string; + memoryUpdateDto: MemoryUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Remove assets from a memory + */ +export declare function removeMemoryAssets({ id, bulkIdsDto }: { + id: string; + bulkIdsDto: BulkIdsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Add assets to a memory + */ +export declare function addMemoryAssets({ id, bulkIdsDto }: { + id: string; + bulkIdsDto: BulkIdsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete notifications + */ +export declare function deleteNotifications({ notificationDeleteAllDto }: { + notificationDeleteAllDto: NotificationDeleteAllDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve notifications + */ +export declare function getNotifications({ id, level, $type, unread }: { + id?: string; + level?: NotificationLevel; + $type?: NotificationType; + unread?: boolean; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update notifications + */ +export declare function updateNotifications({ notificationUpdateAllDto }: { + notificationUpdateAllDto: NotificationUpdateAllDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete a notification + */ +export declare function deleteNotification({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get a notification + */ +export declare function getNotification({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update a notification + */ +export declare function updateNotification({ id, notificationUpdateDto }: { + id: string; + notificationUpdateDto: NotificationUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Start OAuth + */ +export declare function startOAuth({ oAuthConfigDto }: { + oAuthConfigDto: OAuthConfigDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Backchannel OAuth logout + */ +export declare function logoutOAuth({ oAuthBackchannelLogoutDto }: { + oAuthBackchannelLogoutDto: OAuthBackchannelLogoutDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Finish OAuth + */ +export declare function finishOAuth({ oAuthCallbackDto }: { + oAuthCallbackDto: OAuthCallbackDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Link OAuth account + */ +export declare function linkOAuthAccount({ oAuthCallbackDto }: { + oAuthCallbackDto: OAuthCallbackDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Redirect OAuth to mobile + */ +export declare function redirectOAuthToMobile(opts?: Oazapfts.RequestOpts): Promise; +/** + * Unlink OAuth account + */ +export declare function unlinkOAuthAccount(opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve partners + */ +export declare function getPartners({ direction }: { + direction: PartnerDirection; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a partner + */ +export declare function createPartner({ partnerCreateDto }: { + partnerCreateDto: PartnerCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Remove a partner + */ +export declare function removePartner({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a partner + */ +export declare function createPartnerDeprecated({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update a partner + */ +export declare function updatePartner({ id, partnerUpdateDto }: { + id: string; + partnerUpdateDto: PartnerUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete people + */ +export declare function deletePeople({ bulkIdsDto }: { + bulkIdsDto: BulkIdsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get all people + */ +export declare function getAllPeople({ closestAssetId, closestPersonId, page, size, withHidden }: { + closestAssetId?: string; + closestPersonId?: string; + page?: number; + size?: number; + withHidden?: boolean; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a person + */ +export declare function createPerson({ personCreateDto }: { + personCreateDto: PersonCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update people + */ +export declare function updatePeople({ peopleUpdateDto }: { + peopleUpdateDto: PeopleUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete person + */ +export declare function deletePerson({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get a person + */ +export declare function getPerson({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update person + */ +export declare function updatePerson({ id, personUpdateDto }: { + id: string; + personUpdateDto: PersonUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Merge people + */ +export declare function mergePerson({ id, mergePersonDto }: { + id: string; + mergePersonDto: MergePersonDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Reassign faces + */ +export declare function reassignFaces({ id, assetFaceUpdateDto }: { + id: string; + assetFaceUpdateDto: AssetFaceUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get person statistics + */ +export declare function getPersonStatistics({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get person thumbnail + */ +export declare function getPersonThumbnail({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * List all plugins + */ +export declare function getPlugins(opts?: Oazapfts.RequestOpts): Promise; +/** + * List all plugin triggers + */ +export declare function getPluginTriggers(opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve a plugin + */ +export declare function getPlugin({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * List all queues + */ +export declare function getQueues(opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve a queue + */ +export declare function getQueue({ name }: { + name: QueueName; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update a queue + */ +export declare function updateQueue({ name, queueUpdateDto }: { + name: QueueName; + queueUpdateDto: QueueUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Empty a queue + */ +export declare function emptyQueue({ name, queueDeleteDto }: { + name: QueueName; + queueDeleteDto: QueueDeleteDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve queue jobs + */ +export declare function getQueueJobs({ name, status }: { + name: QueueName; + status?: QueueJobStatus[]; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve assets by city + */ +export declare function getAssetsByCity(opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve explore data + */ +export declare function getExploreData(opts?: Oazapfts.RequestOpts): Promise; +/** + * Search large assets + */ +export declare function searchLargeAssets({ albumIds, city, country, createdAfter, createdBefore, isEncoded, isFavorite, isMotion, isNotInAlbum, isOffline, lensModel, libraryId, make, minFileSize, model, ocr, personIds, rating, size, state, tagIds, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, visibility, withDeleted, withExif }: { + albumIds?: string[]; + city?: string | null; + country?: string | null; + createdAfter?: string; + createdBefore?: string; + isEncoded?: boolean; + isFavorite?: boolean; + isMotion?: boolean; + isNotInAlbum?: boolean; + isOffline?: boolean; + lensModel?: string | null; + libraryId?: string | null; + make?: string | null; + minFileSize?: number; + model?: string | null; + ocr?: string; + personIds?: string[]; + rating?: number | null; + size?: number; + state?: string | null; + tagIds?: string[] | null; + takenAfter?: string; + takenBefore?: string; + trashedAfter?: string; + trashedBefore?: string; + $type?: AssetTypeEnum; + updatedAfter?: string; + updatedBefore?: string; + visibility?: AssetVisibility; + withDeleted?: boolean; + withExif?: boolean; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Search assets by metadata + */ +export declare function searchAssets({ metadataSearchDto }: { + metadataSearchDto: MetadataSearchDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Search people + */ +export declare function searchPerson({ name, withHidden }: { + name: string; + withHidden?: boolean; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Search places + */ +export declare function searchPlaces({ name }: { + name: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Search random assets + */ +export declare function searchRandom({ randomSearchDto }: { + randomSearchDto: RandomSearchDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Smart asset search + */ +export declare function searchSmart({ smartSearchDto }: { + smartSearchDto: SmartSearchDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Search asset statistics + */ +export declare function searchAssetStatistics({ statisticsSearchDto }: { + statisticsSearchDto: StatisticsSearchDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve search suggestions + */ +export declare function getSearchSuggestions({ country, includeNull, lensModel, make, model, state, $type }: { + country?: string; + includeNull?: boolean; + lensModel?: string; + make?: string; + model?: string; + state?: string; + $type: SearchSuggestionType; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get server information + */ +export declare function getAboutInfo(opts?: Oazapfts.RequestOpts): Promise; +/** + * Get APK links + */ +export declare function getApkLinks(opts?: Oazapfts.RequestOpts): Promise; +/** + * Get config + */ +export declare function getServerConfig(opts?: Oazapfts.RequestOpts): Promise; +/** + * Get features + */ +export declare function getServerFeatures(opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete server product key + */ +export declare function deleteServerLicense(opts?: Oazapfts.RequestOpts): Promise; +/** + * Get product key + */ +export declare function getServerLicense(opts?: Oazapfts.RequestOpts): Promise; +/** + * Set server product key + */ +export declare function setServerLicense({ licenseKeyDto }: { + licenseKeyDto: LicenseKeyDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get supported media types + */ +export declare function getSupportedMediaTypes(opts?: Oazapfts.RequestOpts): Promise; +/** + * Ping + */ +export declare function pingServer(opts?: Oazapfts.RequestOpts): Promise; +/** + * Get statistics + */ +export declare function getServerStatistics(opts?: Oazapfts.RequestOpts): Promise; +/** + * Get storage + */ +export declare function getStorage(opts?: Oazapfts.RequestOpts): Promise; +/** + * Get server version + */ +export declare function getServerVersion(opts?: Oazapfts.RequestOpts): Promise; +/** + * Get version check status + */ +export declare function getVersionCheck(opts?: Oazapfts.RequestOpts): Promise; +/** + * Get version history + */ +export declare function getVersionHistory(opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete all sessions + */ +export declare function deleteAllSessions(opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve sessions + */ +export declare function getSessions(opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a session + */ +export declare function createSession({ sessionCreateDto }: { + sessionCreateDto: SessionCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete a session + */ +export declare function deleteSession({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update a session + */ +export declare function updateSession({ id, sessionUpdateDto }: { + id: string; + sessionUpdateDto: SessionUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Lock a session + */ +export declare function lockSession({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve all shared links + */ +export declare function getAllSharedLinks({ albumId, id }: { + albumId?: string; + id?: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a shared link + */ +export declare function createSharedLink({ sharedLinkCreateDto }: { + sharedLinkCreateDto: SharedLinkCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Shared link login + */ +export declare function sharedLinkLogin({ key, slug, sharedLinkLoginDto }: { + key?: string; + slug?: string; + sharedLinkLoginDto: SharedLinkLoginDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve current shared link + */ +export declare function getMySharedLink({ key, slug }: { + key?: string; + slug?: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete a shared link + */ +export declare function removeSharedLink({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve a shared link + */ +export declare function getSharedLinkById({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update a shared link + */ +export declare function updateSharedLink({ id, sharedLinkEditDto }: { + id: string; + sharedLinkEditDto: SharedLinkEditDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Remove assets from a shared link + */ +export declare function removeSharedLinkAssets({ id, assetIdsDto }: { + id: string; + assetIdsDto: AssetIdsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Add assets to a shared link + */ +export declare function addSharedLinkAssets({ id, assetIdsDto }: { + id: string; + assetIdsDto: AssetIdsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete stacks + */ +export declare function deleteStacks({ bulkIdsDto }: { + bulkIdsDto: BulkIdsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve stacks + */ +export declare function searchStacks({ primaryAssetId }: { + primaryAssetId?: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a stack + */ +export declare function createStack({ stackCreateDto }: { + stackCreateDto: StackCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete a stack + */ +export declare function deleteStack({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve a stack + */ +export declare function getStack({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update a stack + */ +export declare function updateStack({ id, stackUpdateDto }: { + id: string; + stackUpdateDto: StackUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Remove an asset from a stack + */ +export declare function removeAssetFromStack({ assetId, id }: { + assetId: string; + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete acknowledgements + */ +export declare function deleteSyncAck({ syncAckDeleteDto }: { + syncAckDeleteDto: SyncAckDeleteDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve acknowledgements + */ +export declare function getSyncAck(opts?: Oazapfts.RequestOpts): Promise; +/** + * Acknowledge changes + */ +export declare function sendSyncAck({ syncAckSetDto }: { + syncAckSetDto: SyncAckSetDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Stream sync changes + */ +export declare function getSyncStream({ syncStreamDto }: { + syncStreamDto: SyncStreamDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get system configuration + */ +export declare function getConfig(opts?: Oazapfts.RequestOpts): Promise; +/** + * Update system configuration + */ +export declare function updateConfig({ systemConfigDto }: { + systemConfigDto: SystemConfigDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get system configuration defaults + */ +export declare function getConfigDefaults(opts?: Oazapfts.RequestOpts): Promise; +/** + * Get storage template options + */ +export declare function getStorageTemplateOptions(opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve admin onboarding + */ +export declare function getAdminOnboarding(opts?: Oazapfts.RequestOpts): Promise; +/** + * Update admin onboarding + */ +export declare function updateAdminOnboarding({ adminOnboardingUpdateDto }: { + adminOnboardingUpdateDto: AdminOnboardingUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve reverse geocoding state + */ +export declare function getReverseGeocodingState(opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve version check state + */ +export declare function getVersionCheckState(opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve tags + */ +export declare function getAllTags(opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a tag + */ +export declare function createTag({ tagCreateDto }: { + tagCreateDto: TagCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Upsert tags + */ +export declare function upsertTags({ tagUpsertDto }: { + tagUpsertDto: TagUpsertDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Tag assets + */ +export declare function bulkTagAssets({ tagBulkAssetsDto }: { + tagBulkAssetsDto: TagBulkAssetsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete a tag + */ +export declare function deleteTag({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve a tag + */ +export declare function getTagById({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update a tag + */ +export declare function updateTag({ id, tagUpdateDto }: { + id: string; + tagUpdateDto: TagUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Untag assets + */ +export declare function untagAssets({ id, bulkIdsDto }: { + id: string; + bulkIdsDto: BulkIdsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Tag assets + */ +export declare function tagAssets({ id, bulkIdsDto }: { + id: string; + bulkIdsDto: BulkIdsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get time bucket + */ +export declare function getTimeBucket({ albumId, bbox, isFavorite, isTrashed, key, order, personId, slug, tagId, timeBucket, userId, visibility, withCoordinates, withPartners, withStacked }: { + albumId?: string; + bbox?: string; + isFavorite?: boolean; + isTrashed?: boolean; + key?: string; + order?: AssetOrder; + personId?: string; + slug?: string; + tagId?: string; + timeBucket: string; + userId?: string; + visibility?: AssetVisibility; + withCoordinates?: boolean; + withPartners?: boolean; + withStacked?: boolean; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get time buckets + */ +export declare function getTimeBuckets({ albumId, bbox, isFavorite, isTrashed, key, order, personId, slug, tagId, userId, visibility, withCoordinates, withPartners, withStacked }: { + albumId?: string; + bbox?: string; + isFavorite?: boolean; + isTrashed?: boolean; + key?: string; + order?: AssetOrder; + personId?: string; + slug?: string; + tagId?: string; + userId?: string; + visibility?: AssetVisibility; + withCoordinates?: boolean; + withPartners?: boolean; + withStacked?: boolean; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Empty trash + */ +export declare function emptyTrash(opts?: Oazapfts.RequestOpts): Promise; +/** + * Restore trash + */ +export declare function restoreTrash(opts?: Oazapfts.RequestOpts): Promise; +/** + * Restore assets + */ +export declare function restoreAssets({ bulkIdsDto }: { + bulkIdsDto: BulkIdsDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get all users + */ +export declare function searchUsers(opts?: Oazapfts.RequestOpts): Promise; +/** + * Get current user + */ +export declare function getMyUser(opts?: Oazapfts.RequestOpts): Promise; +/** + * Update current user + */ +export declare function updateMyUser({ userUpdateMeDto }: { + userUpdateMeDto: UserUpdateMeDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete user product key + */ +export declare function deleteUserLicense(opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve user product key + */ +export declare function getUserLicense(opts?: Oazapfts.RequestOpts): Promise; +/** + * Set user product key + */ +export declare function setUserLicense({ licenseKeyDto }: { + licenseKeyDto: LicenseKeyDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete user onboarding + */ +export declare function deleteUserOnboarding(opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve user onboarding + */ +export declare function getUserOnboarding(opts?: Oazapfts.RequestOpts): Promise; +/** + * Update user onboarding + */ +export declare function setUserOnboarding({ onboardingDto }: { + onboardingDto: OnboardingDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Get my preferences + */ +export declare function getMyPreferences(opts?: Oazapfts.RequestOpts): Promise; +/** + * Update my preferences + */ +export declare function updateMyPreferences({ userPreferencesUpdateDto }: { + userPreferencesUpdateDto: UserPreferencesUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete user profile image + */ +export declare function deleteProfileImage(opts?: Oazapfts.RequestOpts): Promise; +/** + * Create user profile image + */ +export declare function createProfileImage({ createProfileImageDto }: { + createProfileImageDto: CreateProfileImageDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve a user + */ +export declare function getUser({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve user profile image + */ +export declare function getProfileImage({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve assets by original path + */ +export declare function getAssetsByOriginalPath({ path }: { + path: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve unique paths + */ +export declare function getUniqueOriginalPaths(opts?: Oazapfts.RequestOpts): Promise; +/** + * List all workflows + */ +export declare function getWorkflows(opts?: Oazapfts.RequestOpts): Promise; +/** + * Create a workflow + */ +export declare function createWorkflow({ workflowCreateDto }: { + workflowCreateDto: WorkflowCreateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Delete a workflow + */ +export declare function deleteWorkflow({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Retrieve a workflow + */ +export declare function getWorkflow({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts): Promise; +/** + * Update a workflow + */ +export declare function updateWorkflow({ id, workflowUpdateDto }: { + id: string; + workflowUpdateDto: WorkflowUpdateDto; +}, opts?: Oazapfts.RequestOpts): Promise; +export declare enum ReactionLevel { + Album = "album", + Asset = "asset" +} +export declare enum ReactionType { + Comment = "comment", + Like = "like" +} +export declare enum UserAvatarColor { + Primary = "primary", + Pink = "pink", + Red = "red", + Yellow = "yellow", + Blue = "blue", + Green = "green", + Purple = "purple", + Orange = "orange", + Gray = "gray", + Amber = "amber" +} +export declare enum MaintenanceAction { + Start = "start", + End = "end", + SelectDatabaseRestore = "select_database_restore", + RestoreDatabase = "restore_database" +} +export declare enum StorageFolder { + EncodedVideo = "encoded-video", + Library = "library", + Upload = "upload", + Profile = "profile", + Thumbs = "thumbs", + Backups = "backups" +} +export declare enum NotificationLevel { + Success = "success", + Error = "error", + Warning = "warning", + Info = "info" +} +export declare enum NotificationType { + JobFailed = "JobFailed", + BackupFailed = "BackupFailed", + SystemMessage = "SystemMessage", + AlbumInvite = "AlbumInvite", + AlbumUpdate = "AlbumUpdate", + Custom = "Custom" +} +export declare enum UserStatus { + Active = "active", + Removing = "removing", + Deleted = "deleted" +} +export declare enum AssetOrder { + Asc = "asc", + Desc = "desc" +} +export declare enum AssetVisibility { + Archive = "archive", + Timeline = "timeline", + Hidden = "hidden", + Locked = "locked" +} +export declare enum AlbumUserRole { + Editor = "editor", + Owner = "owner", + Viewer = "viewer" +} +export declare enum BulkIdErrorReason { + Duplicate = "duplicate", + NoPermission = "no_permission", + NotFound = "not_found", + Unknown = "unknown", + Validation = "validation" +} +export declare enum Permission { + All = "all", + ActivityCreate = "activity.create", + ActivityRead = "activity.read", + ActivityUpdate = "activity.update", + ActivityDelete = "activity.delete", + ActivityStatistics = "activity.statistics", + ApiKeyCreate = "apiKey.create", + ApiKeyRead = "apiKey.read", + ApiKeyUpdate = "apiKey.update", + ApiKeyDelete = "apiKey.delete", + AssetRead = "asset.read", + AssetUpdate = "asset.update", + AssetDelete = "asset.delete", + AssetStatistics = "asset.statistics", + AssetShare = "asset.share", + AssetView = "asset.view", + AssetDownload = "asset.download", + AssetUpload = "asset.upload", + AssetCopy = "asset.copy", + AssetDerive = "asset.derive", + AssetEditGet = "asset.edit.get", + AssetEditCreate = "asset.edit.create", + AssetEditDelete = "asset.edit.delete", + AlbumCreate = "album.create", + AlbumRead = "album.read", + AlbumUpdate = "album.update", + AlbumDelete = "album.delete", + AlbumStatistics = "album.statistics", + AlbumShare = "album.share", + AlbumDownload = "album.download", + AlbumAssetCreate = "albumAsset.create", + AlbumAssetDelete = "albumAsset.delete", + AlbumUserCreate = "albumUser.create", + AlbumUserUpdate = "albumUser.update", + AlbumUserDelete = "albumUser.delete", + AuthChangePassword = "auth.changePassword", + AuthDeviceDelete = "authDevice.delete", + ArchiveRead = "archive.read", + BackupList = "backup.list", + BackupDownload = "backup.download", + BackupUpload = "backup.upload", + BackupDelete = "backup.delete", + DuplicateRead = "duplicate.read", + DuplicateDelete = "duplicate.delete", + FaceCreate = "face.create", + FaceRead = "face.read", + FaceUpdate = "face.update", + FaceDelete = "face.delete", + FolderRead = "folder.read", + JobCreate = "job.create", + JobRead = "job.read", + LibraryCreate = "library.create", + LibraryRead = "library.read", + LibraryUpdate = "library.update", + LibraryDelete = "library.delete", + LibraryStatistics = "library.statistics", + TimelineRead = "timeline.read", + TimelineDownload = "timeline.download", + Maintenance = "maintenance", + MapRead = "map.read", + MapSearch = "map.search", + MemoryCreate = "memory.create", + MemoryRead = "memory.read", + MemoryUpdate = "memory.update", + MemoryDelete = "memory.delete", + MemoryStatistics = "memory.statistics", + MemoryAssetCreate = "memoryAsset.create", + MemoryAssetDelete = "memoryAsset.delete", + NotificationCreate = "notification.create", + NotificationRead = "notification.read", + NotificationUpdate = "notification.update", + NotificationDelete = "notification.delete", + PartnerCreate = "partner.create", + PartnerRead = "partner.read", + PartnerUpdate = "partner.update", + PartnerDelete = "partner.delete", + PersonCreate = "person.create", + PersonRead = "person.read", + PersonUpdate = "person.update", + PersonDelete = "person.delete", + PersonStatistics = "person.statistics", + PersonMerge = "person.merge", + PersonReassign = "person.reassign", + PinCodeCreate = "pinCode.create", + PinCodeUpdate = "pinCode.update", + PinCodeDelete = "pinCode.delete", + PluginCreate = "plugin.create", + PluginRead = "plugin.read", + PluginUpdate = "plugin.update", + PluginDelete = "plugin.delete", + ServerAbout = "server.about", + ServerApkLinks = "server.apkLinks", + ServerStorage = "server.storage", + ServerStatistics = "server.statistics", + ServerVersionCheck = "server.versionCheck", + ServerLicenseRead = "serverLicense.read", + ServerLicenseUpdate = "serverLicense.update", + ServerLicenseDelete = "serverLicense.delete", + SessionCreate = "session.create", + SessionRead = "session.read", + SessionUpdate = "session.update", + SessionDelete = "session.delete", + SessionLock = "session.lock", + SharedLinkCreate = "sharedLink.create", + SharedLinkRead = "sharedLink.read", + SharedLinkUpdate = "sharedLink.update", + SharedLinkDelete = "sharedLink.delete", + StackCreate = "stack.create", + StackRead = "stack.read", + StackUpdate = "stack.update", + StackDelete = "stack.delete", + SyncStream = "sync.stream", + SyncCheckpointRead = "syncCheckpoint.read", + SyncCheckpointUpdate = "syncCheckpoint.update", + SyncCheckpointDelete = "syncCheckpoint.delete", + SystemConfigRead = "systemConfig.read", + SystemConfigUpdate = "systemConfig.update", + SystemMetadataRead = "systemMetadata.read", + SystemMetadataUpdate = "systemMetadata.update", + TagCreate = "tag.create", + TagRead = "tag.read", + TagUpdate = "tag.update", + TagDelete = "tag.delete", + TagAsset = "tag.asset", + UserRead = "user.read", + UserUpdate = "user.update", + UserLicenseCreate = "userLicense.create", + UserLicenseRead = "userLicense.read", + UserLicenseUpdate = "userLicense.update", + UserLicenseDelete = "userLicense.delete", + UserOnboardingRead = "userOnboarding.read", + UserOnboardingUpdate = "userOnboarding.update", + UserOnboardingDelete = "userOnboarding.delete", + UserPreferenceRead = "userPreference.read", + UserPreferenceUpdate = "userPreference.update", + UserProfileImageCreate = "userProfileImage.create", + UserProfileImageRead = "userProfileImage.read", + UserProfileImageUpdate = "userProfileImage.update", + UserProfileImageDelete = "userProfileImage.delete", + QueueRead = "queue.read", + QueueUpdate = "queue.update", + QueueJobCreate = "queueJob.create", + QueueJobRead = "queueJob.read", + QueueJobUpdate = "queueJob.update", + QueueJobDelete = "queueJob.delete", + WorkflowCreate = "workflow.create", + WorkflowRead = "workflow.read", + WorkflowUpdate = "workflow.update", + WorkflowDelete = "workflow.delete", + AdminUserCreate = "adminUser.create", + AdminUserRead = "adminUser.read", + AdminUserUpdate = "adminUser.update", + AdminUserDelete = "adminUser.delete", + AdminSessionRead = "adminSession.read", + AdminAuthUnlinkAll = "adminAuth.unlinkAll" +} +export declare enum AssetMediaStatus { + Created = "created", + Duplicate = "duplicate" +} +export declare enum AssetUploadAction { + Accept = "accept", + Reject = "reject" +} +export declare enum AssetRejectReason { + Duplicate = "duplicate", + UnsupportedFormat = "unsupported-format" +} +export declare enum AssetJobName { + RefreshFaces = "refresh-faces", + RefreshMetadata = "refresh-metadata", + RegenerateThumbnail = "regenerate-thumbnail", + TranscodeVideo = "transcode-video" +} +export declare enum SourceType { + MachineLearning = "machine-learning", + Exif = "exif", + Manual = "manual" +} +export declare enum AssetTypeEnum { + Image = "IMAGE", + Video = "VIDEO", + Audio = "AUDIO", + Other = "OTHER" +} +export declare enum AssetEditAction { + Crop = "crop", + Rotate = "rotate", + Mirror = "mirror" +} +export declare enum MirrorAxis { + Horizontal = "horizontal", + Vertical = "vertical" +} +export declare enum AssetMediaSize { + Original = "original", + Fullsize = "fullsize", + Preview = "preview", + Thumbnail = "thumbnail" +} +export declare enum ManualJobName { + PersonCleanup = "person-cleanup", + TagCleanup = "tag-cleanup", + UserCleanup = "user-cleanup", + MemoryCleanup = "memory-cleanup", + MemoryCreate = "memory-create", + BackupDatabase = "backup-database" +} +export declare enum QueueName { + ThumbnailGeneration = "thumbnailGeneration", + MetadataExtraction = "metadataExtraction", + VideoConversion = "videoConversion", + FaceDetection = "faceDetection", + FacialRecognition = "facialRecognition", + SmartSearch = "smartSearch", + DuplicateDetection = "duplicateDetection", + BackgroundTask = "backgroundTask", + StorageTemplateMigration = "storageTemplateMigration", + Migration = "migration", + Search = "search", + Sidecar = "sidecar", + Library = "library", + Notifications = "notifications", + BackupDatabase = "backupDatabase", + Ocr = "ocr", + Workflow = "workflow", + Editor = "editor" +} +export declare enum QueueCommand { + Start = "start", + Pause = "pause", + Resume = "resume", + Empty = "empty", + ClearFailed = "clear-failed" +} +export declare enum MemorySearchOrder { + Asc = "asc", + Desc = "desc", + Random = "random" +} +export declare enum MemoryType { + OnThisDay = "on_this_day" +} +export declare enum PartnerDirection { + SharedBy = "shared-by", + SharedWith = "shared-with" +} +export declare enum PluginJsonSchemaType { + String = "string", + Number = "number", + Integer = "integer", + Boolean = "boolean", + Object = "object", + Array = "array", + Null = "null" +} +export declare enum PluginContextType { + Asset = "asset", + Album = "album", + Person = "person" +} +export declare enum PluginTriggerType { + AssetCreate = "AssetCreate", + PersonRecognized = "PersonRecognized" +} +export declare enum QueueJobStatus { + Active = "active", + Failed = "failed", + Completed = "completed", + Delayed = "delayed", + Waiting = "waiting", + Paused = "paused" +} +export declare enum JobName { + AssetDelete = "AssetDelete", + AssetDeleteCheck = "AssetDeleteCheck", + AssetDetectFacesQueueAll = "AssetDetectFacesQueueAll", + AssetDetectFaces = "AssetDetectFaces", + AssetDetectDuplicatesQueueAll = "AssetDetectDuplicatesQueueAll", + AssetDetectDuplicates = "AssetDetectDuplicates", + AssetEditThumbnailGeneration = "AssetEditThumbnailGeneration", + AssetEncodeVideoQueueAll = "AssetEncodeVideoQueueAll", + AssetEncodeVideo = "AssetEncodeVideo", + AssetEmptyTrash = "AssetEmptyTrash", + AssetExtractMetadataQueueAll = "AssetExtractMetadataQueueAll", + AssetExtractMetadata = "AssetExtractMetadata", + AssetFileMigration = "AssetFileMigration", + AssetGenerateThumbnailsQueueAll = "AssetGenerateThumbnailsQueueAll", + AssetGenerateThumbnails = "AssetGenerateThumbnails", + AuditTableCleanup = "AuditTableCleanup", + DatabaseBackup = "DatabaseBackup", + FacialRecognitionQueueAll = "FacialRecognitionQueueAll", + FacialRecognition = "FacialRecognition", + FileDelete = "FileDelete", + FileMigrationQueueAll = "FileMigrationQueueAll", + LibraryDeleteCheck = "LibraryDeleteCheck", + LibraryDelete = "LibraryDelete", + LibraryRemoveAsset = "LibraryRemoveAsset", + LibraryScanAssetsQueueAll = "LibraryScanAssetsQueueAll", + LibrarySyncAssets = "LibrarySyncAssets", + LibrarySyncFilesQueueAll = "LibrarySyncFilesQueueAll", + LibrarySyncFiles = "LibrarySyncFiles", + LibraryScanQueueAll = "LibraryScanQueueAll", + MemoryCleanup = "MemoryCleanup", + MemoryGenerate = "MemoryGenerate", + NotificationsCleanup = "NotificationsCleanup", + NotifyUserSignup = "NotifyUserSignup", + NotifyAlbumInvite = "NotifyAlbumInvite", + NotifyAlbumUpdate = "NotifyAlbumUpdate", + UserDelete = "UserDelete", + UserDeleteCheck = "UserDeleteCheck", + UserSyncUsage = "UserSyncUsage", + PersonCleanup = "PersonCleanup", + PersonFileMigration = "PersonFileMigration", + PersonGenerateThumbnail = "PersonGenerateThumbnail", + SessionCleanup = "SessionCleanup", + SendMail = "SendMail", + SidecarQueueAll = "SidecarQueueAll", + SidecarCheck = "SidecarCheck", + SidecarWrite = "SidecarWrite", + SmartSearchQueueAll = "SmartSearchQueueAll", + SmartSearch = "SmartSearch", + StorageTemplateMigration = "StorageTemplateMigration", + StorageTemplateMigrationSingle = "StorageTemplateMigrationSingle", + TagCleanup = "TagCleanup", + VersionCheck = "VersionCheck", + OcrQueueAll = "OcrQueueAll", + Ocr = "Ocr", + WorkflowRun = "WorkflowRun" +} +export declare enum SearchSuggestionType { + Country = "country", + State = "state", + City = "city", + CameraMake = "camera-make", + CameraModel = "camera-model", + CameraLensModel = "camera-lens-model" +} +export declare enum SharedLinkType { + Album = "ALBUM", + Individual = "INDIVIDUAL" +} +export declare enum AssetIdErrorReason { + Duplicate = "duplicate", + NoPermission = "no_permission", + NotFound = "not_found" +} +export declare enum SyncEntityType { + AuthUserV1 = "AuthUserV1", + UserV1 = "UserV1", + UserDeleteV1 = "UserDeleteV1", + AssetV1 = "AssetV1", + AssetV2 = "AssetV2", + AssetDeleteV1 = "AssetDeleteV1", + AssetExifV1 = "AssetExifV1", + AssetEditV1 = "AssetEditV1", + AssetEditDeleteV1 = "AssetEditDeleteV1", + AssetMetadataV1 = "AssetMetadataV1", + AssetMetadataDeleteV1 = "AssetMetadataDeleteV1", + PartnerV1 = "PartnerV1", + PartnerDeleteV1 = "PartnerDeleteV1", + PartnerAssetV1 = "PartnerAssetV1", + PartnerAssetV2 = "PartnerAssetV2", + PartnerAssetBackfillV1 = "PartnerAssetBackfillV1", + PartnerAssetBackfillV2 = "PartnerAssetBackfillV2", + PartnerAssetDeleteV1 = "PartnerAssetDeleteV1", + PartnerAssetExifV1 = "PartnerAssetExifV1", + PartnerAssetExifBackfillV1 = "PartnerAssetExifBackfillV1", + PartnerStackBackfillV1 = "PartnerStackBackfillV1", + PartnerStackDeleteV1 = "PartnerStackDeleteV1", + PartnerStackV1 = "PartnerStackV1", + AlbumV1 = "AlbumV1", + AlbumV2 = "AlbumV2", + AlbumDeleteV1 = "AlbumDeleteV1", + AlbumUserV1 = "AlbumUserV1", + AlbumUserBackfillV1 = "AlbumUserBackfillV1", + AlbumUserDeleteV1 = "AlbumUserDeleteV1", + AlbumAssetCreateV1 = "AlbumAssetCreateV1", + AlbumAssetCreateV2 = "AlbumAssetCreateV2", + AlbumAssetUpdateV1 = "AlbumAssetUpdateV1", + AlbumAssetUpdateV2 = "AlbumAssetUpdateV2", + AlbumAssetBackfillV1 = "AlbumAssetBackfillV1", + AlbumAssetBackfillV2 = "AlbumAssetBackfillV2", + AlbumAssetExifCreateV1 = "AlbumAssetExifCreateV1", + AlbumAssetExifUpdateV1 = "AlbumAssetExifUpdateV1", + AlbumAssetExifBackfillV1 = "AlbumAssetExifBackfillV1", + AlbumToAssetV1 = "AlbumToAssetV1", + AlbumToAssetDeleteV1 = "AlbumToAssetDeleteV1", + AlbumToAssetBackfillV1 = "AlbumToAssetBackfillV1", + MemoryV1 = "MemoryV1", + MemoryDeleteV1 = "MemoryDeleteV1", + MemoryToAssetV1 = "MemoryToAssetV1", + MemoryToAssetDeleteV1 = "MemoryToAssetDeleteV1", + StackV1 = "StackV1", + StackDeleteV1 = "StackDeleteV1", + PersonV1 = "PersonV1", + PersonDeleteV1 = "PersonDeleteV1", + AssetFaceV1 = "AssetFaceV1", + AssetFaceV2 = "AssetFaceV2", + AssetFaceDeleteV1 = "AssetFaceDeleteV1", + UserMetadataV1 = "UserMetadataV1", + UserMetadataDeleteV1 = "UserMetadataDeleteV1", + SyncAckV1 = "SyncAckV1", + SyncResetV1 = "SyncResetV1", + SyncCompleteV1 = "SyncCompleteV1" +} +export declare enum SyncRequestType { + AlbumsV1 = "AlbumsV1", + AlbumsV2 = "AlbumsV2", + AlbumUsersV1 = "AlbumUsersV1", + AlbumToAssetsV1 = "AlbumToAssetsV1", + AlbumAssetsV1 = "AlbumAssetsV1", + AlbumAssetsV2 = "AlbumAssetsV2", + AlbumAssetExifsV1 = "AlbumAssetExifsV1", + AssetsV1 = "AssetsV1", + AssetsV2 = "AssetsV2", + AssetExifsV1 = "AssetExifsV1", + AssetEditsV1 = "AssetEditsV1", + AssetMetadataV1 = "AssetMetadataV1", + AuthUsersV1 = "AuthUsersV1", + MemoriesV1 = "MemoriesV1", + MemoryToAssetsV1 = "MemoryToAssetsV1", + PartnersV1 = "PartnersV1", + PartnerAssetsV1 = "PartnerAssetsV1", + PartnerAssetsV2 = "PartnerAssetsV2", + PartnerAssetExifsV1 = "PartnerAssetExifsV1", + PartnerStacksV1 = "PartnerStacksV1", + StacksV1 = "StacksV1", + UsersV1 = "UsersV1", + PeopleV1 = "PeopleV1", + AssetFacesV1 = "AssetFacesV1", + AssetFacesV2 = "AssetFacesV2", + UserMetadataV1 = "UserMetadataV1" +} +export declare enum TranscodeHWAccel { + Nvenc = "nvenc", + Qsv = "qsv", + Vaapi = "vaapi", + Rkmpp = "rkmpp", + Disabled = "disabled" +} +export declare enum AudioCodec { + Mp3 = "mp3", + Aac = "aac", + Libopus = "libopus", + Opus = "opus", + PcmS16Le = "pcm_s16le" +} +export declare enum VideoContainer { + Mov = "mov", + Mp4 = "mp4", + Ogg = "ogg", + Webm = "webm" +} +export declare enum VideoCodec { + H264 = "h264", + Hevc = "hevc", + Vp9 = "vp9", + Av1 = "av1" +} +export declare enum CQMode { + Auto = "auto", + Cqp = "cqp", + Icq = "icq" +} +export declare enum ToneMapping { + Hable = "hable", + Mobius = "mobius", + Reinhard = "reinhard", + Disabled = "disabled" +} +export declare enum TranscodePolicy { + All = "all", + Optimal = "optimal", + Bitrate = "bitrate", + Required = "required", + Disabled = "disabled" +} +export declare enum Colorspace { + Srgb = "srgb", + P3 = "p3" +} +export declare enum ImageFormat { + Jpeg = "jpeg", + Webp = "webp" +} +export declare enum LogLevel { + Verbose = "verbose", + Debug = "debug", + Log = "log", + Warn = "warn", + Error = "error", + Fatal = "fatal" +} +export declare enum OAuthTokenEndpointAuthMethod { + ClientSecretPost = "client_secret_post", + ClientSecretBasic = "client_secret_basic" +} +export declare enum UserMetadataKey { + Preferences = "preferences", + License = "license", + Onboarding = "onboarding" +} diff --git a/open-api/typescript-sdk/build/fetch-client.js b/open-api/typescript-sdk/build/fetch-client.js new file mode 100644 index 0000000000..bdff88c3a7 --- /dev/null +++ b/open-api/typescript-sdk/build/fetch-client.js @@ -0,0 +1,2999 @@ +/** + * Immich + * 2.7.5 + * DO NOT MODIFY - This file has been generated using oazapfts. + * See https://www.npmjs.com/package/oazapfts + */ +import * as Oazapfts from "@oazapfts/runtime"; +import * as QS from "@oazapfts/runtime/query"; +export const defaults = { + headers: {}, + baseUrl: "/api" +}; +const oazapfts = Oazapfts.runtime(defaults); +export const servers = { + server1: "/api" +}; +/** + * List all activities + */ +export function getActivities({ albumId, assetId, level, $type, userId }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/activities${QS.query(QS.explode({ + albumId, + assetId, + level, + "type": $type, + userId + }))}`, { + ...opts + })); +} +/** + * Create an activity + */ +export function createActivity({ activityCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/activities", oazapfts.json({ + ...opts, + method: "POST", + body: activityCreateDto + }))); +} +/** + * Retrieve activity statistics + */ +export function getActivityStatistics({ albumId, assetId }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/activities/statistics${QS.query(QS.explode({ + albumId, + assetId + }))}`, { + ...opts + })); +} +/** + * Delete an activity + */ +export function deleteActivity({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/activities/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Unlink all OAuth accounts + */ +export function unlinkAllOAuthAccountsAdmin(opts) { + return oazapfts.ok(oazapfts.fetchText("/admin/auth/unlink-all", { + ...opts, + method: "POST" + })); +} +/** + * Delete database backup + */ +export function deleteDatabaseBackup({ databaseBackupDeleteDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/admin/database-backups", oazapfts.json({ + ...opts, + method: "DELETE", + body: databaseBackupDeleteDto + }))); +} +/** + * List database backups + */ +export function listDatabaseBackups(opts) { + return oazapfts.ok(oazapfts.fetchJson("/admin/database-backups", { + ...opts + })); +} +/** + * Start database backup restore flow + */ +export function startDatabaseRestoreFlow(opts) { + return oazapfts.ok(oazapfts.fetchText("/admin/database-backups/start-restore", { + ...opts, + method: "POST" + })); +} +/** + * Upload database backup + */ +export function uploadDatabaseBackup({ databaseBackupUploadDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/admin/database-backups/upload", oazapfts.multipart({ + ...opts, + method: "POST", + body: databaseBackupUploadDto + }))); +} +/** + * Download database backup + */ +export function downloadDatabaseBackup({ filename }, opts) { + return oazapfts.ok(oazapfts.fetchBlob(`/admin/database-backups/${encodeURIComponent(filename)}`, { + ...opts + })); +} +/** + * Set maintenance mode + */ +export function setMaintenanceMode({ setMaintenanceModeDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/admin/maintenance", oazapfts.json({ + ...opts, + method: "POST", + body: setMaintenanceModeDto + }))); +} +/** + * Detect existing install + */ +export function detectPriorInstall(opts) { + return oazapfts.ok(oazapfts.fetchJson("/admin/maintenance/detect-install", { + ...opts + })); +} +/** + * Log into maintenance mode + */ +export function maintenanceLogin({ maintenanceLoginDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/admin/maintenance/login", oazapfts.json({ + ...opts, + method: "POST", + body: maintenanceLoginDto + }))); +} +/** + * Get maintenance mode status + */ +export function getMaintenanceStatus(opts) { + return oazapfts.ok(oazapfts.fetchJson("/admin/maintenance/status", { + ...opts + })); +} +/** + * Create a notification + */ +export function createNotification({ notificationCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/admin/notifications", oazapfts.json({ + ...opts, + method: "POST", + body: notificationCreateDto + }))); +} +/** + * Render email template + */ +export function getNotificationTemplateAdmin({ name, templateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/admin/notifications/templates/${encodeURIComponent(name)}`, oazapfts.json({ + ...opts, + method: "POST", + body: templateDto + }))); +} +/** + * Send test email + */ +export function sendTestEmailAdmin({ systemConfigSmtpDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/admin/notifications/test-email", oazapfts.json({ + ...opts, + method: "POST", + body: systemConfigSmtpDto + }))); +} +/** + * Search users + */ +export function searchUsersAdmin({ id, withDeleted }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/admin/users${QS.query(QS.explode({ + id, + withDeleted + }))}`, { + ...opts + })); +} +/** + * Create a user + */ +export function createUserAdmin({ userAdminCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/admin/users", oazapfts.json({ + ...opts, + method: "POST", + body: userAdminCreateDto + }))); +} +/** + * Delete a user + */ +export function deleteUserAdmin({ id, userAdminDeleteDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/admin/users/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "DELETE", + body: userAdminDeleteDto + }))); +} +/** + * Retrieve a user + */ +export function getUserAdmin({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/admin/users/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * Update a user + */ +export function updateUserAdmin({ id, userAdminUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/admin/users/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: userAdminUpdateDto + }))); +} +/** + * Retrieve user preferences + */ +export function getUserPreferencesAdmin({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/admin/users/${encodeURIComponent(id)}/preferences`, { + ...opts + })); +} +/** + * Update user preferences + */ +export function updateUserPreferencesAdmin({ id, userPreferencesUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/admin/users/${encodeURIComponent(id)}/preferences`, oazapfts.json({ + ...opts, + method: "PUT", + body: userPreferencesUpdateDto + }))); +} +/** + * Restore a deleted user + */ +export function restoreUserAdmin({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/admin/users/${encodeURIComponent(id)}/restore`, { + ...opts, + method: "POST" + })); +} +/** + * Retrieve user sessions + */ +export function getUserSessionsAdmin({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/admin/users/${encodeURIComponent(id)}/sessions`, { + ...opts + })); +} +/** + * Retrieve user statistics + */ +export function getUserStatisticsAdmin({ id, isFavorite, isTrashed, visibility }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/admin/users/${encodeURIComponent(id)}/statistics${QS.query(QS.explode({ + isFavorite, + isTrashed, + visibility + }))}`, { + ...opts + })); +} +/** + * List all albums + */ +export function getAllAlbums({ assetId, shared }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/albums${QS.query(QS.explode({ + assetId, + shared + }))}`, { + ...opts + })); +} +/** + * Create an album + */ +export function createAlbum({ createAlbumDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/albums", oazapfts.json({ + ...opts, + method: "POST", + body: createAlbumDto + }))); +} +/** + * Add assets to albums + */ +export function addAssetsToAlbums({ albumsAddAssetsDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/albums/assets", oazapfts.json({ + ...opts, + method: "PUT", + body: albumsAddAssetsDto + }))); +} +/** + * Retrieve album statistics + */ +export function getAlbumStatistics(opts) { + return oazapfts.ok(oazapfts.fetchJson("/albums/statistics", { + ...opts + })); +} +/** + * Delete an album + */ +export function deleteAlbum({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/albums/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve an album + */ +export function getAlbumInfo({ id, key, slug }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/albums/${encodeURIComponent(id)}${QS.query(QS.explode({ + key, + slug + }))}`, { + ...opts + })); +} +/** + * Update an album + */ +export function updateAlbumInfo({ id, updateAlbumDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/albums/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PATCH", + body: updateAlbumDto + }))); +} +/** + * Remove assets from an album + */ +export function removeAssetFromAlbum({ id, bulkIdsDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/albums/${encodeURIComponent(id)}/assets`, oazapfts.json({ + ...opts, + method: "DELETE", + body: bulkIdsDto + }))); +} +/** + * Add assets to an album + */ +export function addAssetsToAlbum({ id, bulkIdsDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/albums/${encodeURIComponent(id)}/assets`, oazapfts.json({ + ...opts, + method: "PUT", + body: bulkIdsDto + }))); +} +/** + * Retrieve album map markers + */ +export function getAlbumMapMarkers({ id, key, slug }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/albums/${encodeURIComponent(id)}/map-markers${QS.query(QS.explode({ + key, + slug + }))}`, { + ...opts + })); +} +/** + * Remove user from album + */ +export function removeUserFromAlbum({ id, userId }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/albums/${encodeURIComponent(id)}/user/${encodeURIComponent(userId)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Update user role + */ +export function updateAlbumUser({ id, userId, updateAlbumUserDto }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/albums/${encodeURIComponent(id)}/user/${encodeURIComponent(userId)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: updateAlbumUserDto + }))); +} +/** + * Share album with users + */ +export function addUsersToAlbum({ id, addUsersDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/albums/${encodeURIComponent(id)}/users`, oazapfts.json({ + ...opts, + method: "PUT", + body: addUsersDto + }))); +} +/** + * List all API keys + */ +export function getApiKeys(opts) { + return oazapfts.ok(oazapfts.fetchJson("/api-keys", { + ...opts + })); +} +/** + * Create an API key + */ +export function createApiKey({ apiKeyCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/api-keys", oazapfts.json({ + ...opts, + method: "POST", + body: apiKeyCreateDto + }))); +} +/** + * Retrieve the current API key + */ +export function getMyApiKey(opts) { + return oazapfts.ok(oazapfts.fetchJson("/api-keys/me", { + ...opts + })); +} +/** + * Delete an API key + */ +export function deleteApiKey({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/api-keys/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve an API key + */ +export function getApiKey({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/api-keys/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * Update an API key + */ +export function updateApiKey({ id, apiKeyUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/api-keys/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: apiKeyUpdateDto + }))); +} +/** + * Delete assets + */ +export function deleteAssets({ assetBulkDeleteDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/assets", oazapfts.json({ + ...opts, + method: "DELETE", + body: assetBulkDeleteDto + }))); +} +/** + * Upload asset + */ +export function uploadAsset({ key, slug, xImmichChecksum, assetMediaCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/assets${QS.query(QS.explode({ + key, + slug + }))}`, oazapfts.multipart({ + ...opts, + method: "POST", + body: assetMediaCreateDto, + headers: oazapfts.mergeHeaders(opts?.headers, { + "x-immich-checksum": xImmichChecksum + }) + }))); +} +/** + * Update assets + */ +export function updateAssets({ assetBulkUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/assets", oazapfts.json({ + ...opts, + method: "PUT", + body: assetBulkUpdateDto + }))); +} +/** + * Check bulk upload + */ +export function checkBulkUpload({ assetBulkUploadCheckDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/assets/bulk-upload-check", oazapfts.json({ + ...opts, + method: "POST", + body: assetBulkUploadCheckDto + }))); +} +/** + * Copy asset + */ +export function copyAsset({ assetCopyDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/assets/copy", oazapfts.json({ + ...opts, + method: "PUT", + body: assetCopyDto + }))); +} +/** + * Run an asset job + */ +export function runAssetJobs({ assetJobsDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/assets/jobs", oazapfts.json({ + ...opts, + method: "POST", + body: assetJobsDto + }))); +} +/** + * Delete asset metadata + */ +export function deleteBulkAssetMetadata({ assetMetadataBulkDeleteDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/assets/metadata", oazapfts.json({ + ...opts, + method: "DELETE", + body: assetMetadataBulkDeleteDto + }))); +} +/** + * Upsert asset metadata + */ +export function updateBulkAssetMetadata({ assetMetadataBulkUpsertDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/assets/metadata", oazapfts.json({ + ...opts, + method: "PUT", + body: assetMetadataBulkUpsertDto + }))); +} +/** + * Get asset statistics + */ +export function getAssetStatistics({ isFavorite, isTrashed, visibility }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/assets/statistics${QS.query(QS.explode({ + isFavorite, + isTrashed, + visibility + }))}`, { + ...opts + })); +} +/** + * Retrieve an asset + */ +export function getAssetInfo({ id, key, slug }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/assets/${encodeURIComponent(id)}${QS.query(QS.explode({ + key, + slug + }))}`, { + ...opts + })); +} +/** + * Update an asset + */ +export function updateAsset({ id, updateAssetDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/assets/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: updateAssetDto + }))); +} +/** + * Remove edits from an existing asset + */ +export function removeAssetEdits({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/assets/${encodeURIComponent(id)}/edits`, { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve edits for an existing asset + */ +export function getAssetEdits({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/assets/${encodeURIComponent(id)}/edits`, { + ...opts + })); +} +/** + * Apply edits to an existing asset + */ +export function editAsset({ id, assetEditsCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/assets/${encodeURIComponent(id)}/edits`, oazapfts.json({ + ...opts, + method: "PUT", + body: assetEditsCreateDto + }))); +} +/** + * Get asset metadata + */ +export function getAssetMetadata({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/assets/${encodeURIComponent(id)}/metadata`, { + ...opts + })); +} +/** + * Update asset metadata + */ +export function updateAssetMetadata({ id, assetMetadataUpsertDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/assets/${encodeURIComponent(id)}/metadata`, oazapfts.json({ + ...opts, + method: "PUT", + body: assetMetadataUpsertDto + }))); +} +/** + * Delete asset metadata by key + */ +export function deleteAssetMetadata({ id, key }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/assets/${encodeURIComponent(id)}/metadata/${encodeURIComponent(key)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve asset metadata by key + */ +export function getAssetMetadataByKey({ id, key }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/assets/${encodeURIComponent(id)}/metadata/${encodeURIComponent(key)}`, { + ...opts + })); +} +/** + * Retrieve asset OCR data + */ +export function getAssetOcr({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/assets/${encodeURIComponent(id)}/ocr`, { + ...opts + })); +} +/** + * Download original asset + */ +export function downloadAsset({ edited, id, key, slug }, opts) { + return oazapfts.ok(oazapfts.fetchBlob(`/assets/${encodeURIComponent(id)}/original${QS.query(QS.explode({ + edited, + key, + slug + }))}`, { + ...opts + })); +} +/** + * View asset thumbnail + */ +export function viewAsset({ edited, id, key, size, slug }, opts) { + return oazapfts.ok(oazapfts.fetchBlob(`/assets/${encodeURIComponent(id)}/thumbnail${QS.query(QS.explode({ + edited, + key, + size, + slug + }))}`, { + ...opts + })); +} +/** + * Play asset video + */ +export function playAssetVideo({ id, key, slug }, opts) { + return oazapfts.ok(oazapfts.fetchBlob(`/assets/${encodeURIComponent(id)}/video/playback${QS.query(QS.explode({ + key, + slug + }))}`, { + ...opts + })); +} +/** + * Register admin + */ +export function signUpAdmin({ signUpDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/auth/admin-sign-up", oazapfts.json({ + ...opts, + method: "POST", + body: signUpDto + }))); +} +/** + * Change password + */ +export function changePassword({ changePasswordDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/auth/change-password", oazapfts.json({ + ...opts, + method: "POST", + body: changePasswordDto + }))); +} +/** + * Login + */ +export function login({ loginCredentialDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/auth/login", oazapfts.json({ + ...opts, + method: "POST", + body: loginCredentialDto + }))); +} +/** + * Logout + */ +export function logout(opts) { + return oazapfts.ok(oazapfts.fetchJson("/auth/logout", { + ...opts, + method: "POST" + })); +} +/** + * Reset pin code + */ +export function resetPinCode({ pinCodeResetDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/auth/pin-code", oazapfts.json({ + ...opts, + method: "DELETE", + body: pinCodeResetDto + }))); +} +/** + * Setup pin code + */ +export function setupPinCode({ pinCodeSetupDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/auth/pin-code", oazapfts.json({ + ...opts, + method: "POST", + body: pinCodeSetupDto + }))); +} +/** + * Change pin code + */ +export function changePinCode({ pinCodeChangeDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/auth/pin-code", oazapfts.json({ + ...opts, + method: "PUT", + body: pinCodeChangeDto + }))); +} +/** + * Lock auth session + */ +export function lockAuthSession(opts) { + return oazapfts.ok(oazapfts.fetchText("/auth/session/lock", { + ...opts, + method: "POST" + })); +} +/** + * Unlock auth session + */ +export function unlockAuthSession({ sessionUnlockDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/auth/session/unlock", oazapfts.json({ + ...opts, + method: "POST", + body: sessionUnlockDto + }))); +} +/** + * Retrieve auth status + */ +export function getAuthStatus(opts) { + return oazapfts.ok(oazapfts.fetchJson("/auth/status", { + ...opts + })); +} +/** + * Validate access token + */ +export function validateAccessToken(opts) { + return oazapfts.ok(oazapfts.fetchJson("/auth/validateToken", { + ...opts, + method: "POST" + })); +} +/** + * Download asset archive + */ +export function downloadArchive({ key, slug, downloadArchiveDto }, opts) { + return oazapfts.ok(oazapfts.fetchBlob(`/download/archive${QS.query(QS.explode({ + key, + slug + }))}`, oazapfts.json({ + ...opts, + method: "POST", + body: downloadArchiveDto + }))); +} +/** + * Retrieve download information + */ +export function getDownloadInfo({ key, slug, downloadInfoDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/download/info${QS.query(QS.explode({ + key, + slug + }))}`, oazapfts.json({ + ...opts, + method: "POST", + body: downloadInfoDto + }))); +} +/** + * Delete duplicates + */ +export function deleteDuplicates({ bulkIdsDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/duplicates", oazapfts.json({ + ...opts, + method: "DELETE", + body: bulkIdsDto + }))); +} +/** + * Retrieve duplicates + */ +export function getAssetDuplicates(opts) { + return oazapfts.ok(oazapfts.fetchJson("/duplicates", { + ...opts + })); +} +/** + * Resolve duplicate groups + */ +export function resolveDuplicates({ duplicateResolveDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/duplicates/resolve", oazapfts.json({ + ...opts, + method: "POST", + body: duplicateResolveDto + }))); +} +/** + * Dismiss a duplicate group + */ +export function deleteDuplicate({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/duplicates/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve faces for asset + */ +export function getFaces({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/faces${QS.query(QS.explode({ + id + }))}`, { + ...opts + })); +} +/** + * Create a face + */ +export function createFace({ assetFaceCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/faces", oazapfts.json({ + ...opts, + method: "POST", + body: assetFaceCreateDto + }))); +} +/** + * Delete a face + */ +export function deleteFace({ id, assetFaceDeleteDto }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/faces/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "DELETE", + body: assetFaceDeleteDto + }))); +} +/** + * Re-assign a face to another person + */ +export function reassignFacesById({ id, faceDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/faces/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: faceDto + }))); +} +/** + * Retrieve queue counts and status + */ +export function getQueuesLegacy(opts) { + return oazapfts.ok(oazapfts.fetchJson("/jobs", { + ...opts + })); +} +/** + * Create a manual job + */ +export function createJob({ jobCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/jobs", oazapfts.json({ + ...opts, + method: "POST", + body: jobCreateDto + }))); +} +/** + * Run jobs + */ +export function runQueueCommandLegacy({ name, queueCommandDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/jobs/${encodeURIComponent(name)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: queueCommandDto + }))); +} +/** + * Retrieve libraries + */ +export function getAllLibraries(opts) { + return oazapfts.ok(oazapfts.fetchJson("/libraries", { + ...opts + })); +} +/** + * Create a library + */ +export function createLibrary({ createLibraryDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/libraries", oazapfts.json({ + ...opts, + method: "POST", + body: createLibraryDto + }))); +} +/** + * Delete a library + */ +export function deleteLibrary({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/libraries/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve a library + */ +export function getLibrary({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/libraries/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * Update a library + */ +export function updateLibrary({ id, updateLibraryDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/libraries/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: updateLibraryDto + }))); +} +/** + * Scan a library + */ +export function scanLibrary({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/libraries/${encodeURIComponent(id)}/scan`, { + ...opts, + method: "POST" + })); +} +/** + * Retrieve library statistics + */ +export function getLibraryStatistics({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/libraries/${encodeURIComponent(id)}/statistics`, { + ...opts + })); +} +/** + * Validate library settings + */ +export function validate({ id, validateLibraryDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/libraries/${encodeURIComponent(id)}/validate`, oazapfts.json({ + ...opts, + method: "POST", + body: validateLibraryDto + }))); +} +/** + * Retrieve map markers + */ +export function getMapMarkers({ fileCreatedAfter, fileCreatedBefore, isArchived, isFavorite, withPartners, withSharedAlbums }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/map/markers${QS.query(QS.explode({ + fileCreatedAfter, + fileCreatedBefore, + isArchived, + isFavorite, + withPartners, + withSharedAlbums + }))}`, { + ...opts + })); +} +/** + * Reverse geocode coordinates + */ +export function reverseGeocode({ lat, lon }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/map/reverse-geocode${QS.query(QS.explode({ + lat, + lon + }))}`, { + ...opts + })); +} +/** + * Retrieve memories + */ +export function searchMemories({ $for, isSaved, isTrashed, order, size, $type }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/memories${QS.query(QS.explode({ + "for": $for, + isSaved, + isTrashed, + order, + size, + "type": $type + }))}`, { + ...opts + })); +} +/** + * Create a memory + */ +export function createMemory({ memoryCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/memories", oazapfts.json({ + ...opts, + method: "POST", + body: memoryCreateDto + }))); +} +/** + * Retrieve memories statistics + */ +export function memoriesStatistics({ $for, isSaved, isTrashed, order, size, $type }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/memories/statistics${QS.query(QS.explode({ + "for": $for, + isSaved, + isTrashed, + order, + size, + "type": $type + }))}`, { + ...opts + })); +} +/** + * Delete a memory + */ +export function deleteMemory({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/memories/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve a memory + */ +export function getMemory({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/memories/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * Update a memory + */ +export function updateMemory({ id, memoryUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/memories/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: memoryUpdateDto + }))); +} +/** + * Remove assets from a memory + */ +export function removeMemoryAssets({ id, bulkIdsDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/memories/${encodeURIComponent(id)}/assets`, oazapfts.json({ + ...opts, + method: "DELETE", + body: bulkIdsDto + }))); +} +/** + * Add assets to a memory + */ +export function addMemoryAssets({ id, bulkIdsDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/memories/${encodeURIComponent(id)}/assets`, oazapfts.json({ + ...opts, + method: "PUT", + body: bulkIdsDto + }))); +} +/** + * Delete notifications + */ +export function deleteNotifications({ notificationDeleteAllDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/notifications", oazapfts.json({ + ...opts, + method: "DELETE", + body: notificationDeleteAllDto + }))); +} +/** + * Retrieve notifications + */ +export function getNotifications({ id, level, $type, unread }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/notifications${QS.query(QS.explode({ + id, + level, + "type": $type, + unread + }))}`, { + ...opts + })); +} +/** + * Update notifications + */ +export function updateNotifications({ notificationUpdateAllDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/notifications", oazapfts.json({ + ...opts, + method: "PUT", + body: notificationUpdateAllDto + }))); +} +/** + * Delete a notification + */ +export function deleteNotification({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/notifications/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Get a notification + */ +export function getNotification({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/notifications/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * Update a notification + */ +export function updateNotification({ id, notificationUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/notifications/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: notificationUpdateDto + }))); +} +/** + * Start OAuth + */ +export function startOAuth({ oAuthConfigDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/oauth/authorize", oazapfts.json({ + ...opts, + method: "POST", + body: oAuthConfigDto + }))); +} +/** + * Backchannel OAuth logout + */ +export function logoutOAuth({ oAuthBackchannelLogoutDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/oauth/backchannel-logout", oazapfts.form({ + ...opts, + method: "POST", + body: oAuthBackchannelLogoutDto + }))); +} +/** + * Finish OAuth + */ +export function finishOAuth({ oAuthCallbackDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/oauth/callback", oazapfts.json({ + ...opts, + method: "POST", + body: oAuthCallbackDto + }))); +} +/** + * Link OAuth account + */ +export function linkOAuthAccount({ oAuthCallbackDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/oauth/link", oazapfts.json({ + ...opts, + method: "POST", + body: oAuthCallbackDto + }))); +} +/** + * Redirect OAuth to mobile + */ +export function redirectOAuthToMobile(opts) { + return oazapfts.ok(oazapfts.fetchText("/oauth/mobile-redirect", { + ...opts + })); +} +/** + * Unlink OAuth account + */ +export function unlinkOAuthAccount(opts) { + return oazapfts.ok(oazapfts.fetchJson("/oauth/unlink", { + ...opts, + method: "POST" + })); +} +/** + * Retrieve partners + */ +export function getPartners({ direction }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/partners${QS.query(QS.explode({ + direction + }))}`, { + ...opts + })); +} +/** + * Create a partner + */ +export function createPartner({ partnerCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/partners", oazapfts.json({ + ...opts, + method: "POST", + body: partnerCreateDto + }))); +} +/** + * Remove a partner + */ +export function removePartner({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/partners/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Create a partner + */ +export function createPartnerDeprecated({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/partners/${encodeURIComponent(id)}`, { + ...opts, + method: "POST" + })); +} +/** + * Update a partner + */ +export function updatePartner({ id, partnerUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/partners/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: partnerUpdateDto + }))); +} +/** + * Delete people + */ +export function deletePeople({ bulkIdsDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/people", oazapfts.json({ + ...opts, + method: "DELETE", + body: bulkIdsDto + }))); +} +/** + * Get all people + */ +export function getAllPeople({ closestAssetId, closestPersonId, page, size, withHidden }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/people${QS.query(QS.explode({ + closestAssetId, + closestPersonId, + page, + size, + withHidden + }))}`, { + ...opts + })); +} +/** + * Create a person + */ +export function createPerson({ personCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/people", oazapfts.json({ + ...opts, + method: "POST", + body: personCreateDto + }))); +} +/** + * Update people + */ +export function updatePeople({ peopleUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/people", oazapfts.json({ + ...opts, + method: "PUT", + body: peopleUpdateDto + }))); +} +/** + * Delete person + */ +export function deletePerson({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/people/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Get a person + */ +export function getPerson({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/people/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * Update person + */ +export function updatePerson({ id, personUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/people/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: personUpdateDto + }))); +} +/** + * Merge people + */ +export function mergePerson({ id, mergePersonDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/people/${encodeURIComponent(id)}/merge`, oazapfts.json({ + ...opts, + method: "POST", + body: mergePersonDto + }))); +} +/** + * Reassign faces + */ +export function reassignFaces({ id, assetFaceUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/people/${encodeURIComponent(id)}/reassign`, oazapfts.json({ + ...opts, + method: "PUT", + body: assetFaceUpdateDto + }))); +} +/** + * Get person statistics + */ +export function getPersonStatistics({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/people/${encodeURIComponent(id)}/statistics`, { + ...opts + })); +} +/** + * Get person thumbnail + */ +export function getPersonThumbnail({ id }, opts) { + return oazapfts.ok(oazapfts.fetchBlob(`/people/${encodeURIComponent(id)}/thumbnail`, { + ...opts + })); +} +/** + * List all plugins + */ +export function getPlugins(opts) { + return oazapfts.ok(oazapfts.fetchJson("/plugins", { + ...opts + })); +} +/** + * List all plugin triggers + */ +export function getPluginTriggers(opts) { + return oazapfts.ok(oazapfts.fetchJson("/plugins/triggers", { + ...opts + })); +} +/** + * Retrieve a plugin + */ +export function getPlugin({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/plugins/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * List all queues + */ +export function getQueues(opts) { + return oazapfts.ok(oazapfts.fetchJson("/queues", { + ...opts + })); +} +/** + * Retrieve a queue + */ +export function getQueue({ name }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/queues/${encodeURIComponent(name)}`, { + ...opts + })); +} +/** + * Update a queue + */ +export function updateQueue({ name, queueUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/queues/${encodeURIComponent(name)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: queueUpdateDto + }))); +} +/** + * Empty a queue + */ +export function emptyQueue({ name, queueDeleteDto }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/queues/${encodeURIComponent(name)}/jobs`, oazapfts.json({ + ...opts, + method: "DELETE", + body: queueDeleteDto + }))); +} +/** + * Retrieve queue jobs + */ +export function getQueueJobs({ name, status }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/queues/${encodeURIComponent(name)}/jobs${QS.query(QS.explode({ + status + }))}`, { + ...opts + })); +} +/** + * Retrieve assets by city + */ +export function getAssetsByCity(opts) { + return oazapfts.ok(oazapfts.fetchJson("/search/cities", { + ...opts + })); +} +/** + * Retrieve explore data + */ +export function getExploreData(opts) { + return oazapfts.ok(oazapfts.fetchJson("/search/explore", { + ...opts + })); +} +/** + * Search large assets + */ +export function searchLargeAssets({ albumIds, city, country, createdAfter, createdBefore, isEncoded, isFavorite, isMotion, isNotInAlbum, isOffline, lensModel, libraryId, make, minFileSize, model, ocr, personIds, rating, size, state, tagIds, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, visibility, withDeleted, withExif }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/search/large-assets${QS.query(QS.explode({ + albumIds, + city, + country, + createdAfter, + createdBefore, + isEncoded, + isFavorite, + isMotion, + isNotInAlbum, + isOffline, + lensModel, + libraryId, + make, + minFileSize, + model, + ocr, + personIds, + rating, + size, + state, + tagIds, + takenAfter, + takenBefore, + trashedAfter, + trashedBefore, + "type": $type, + updatedAfter, + updatedBefore, + visibility, + withDeleted, + withExif + }))}`, { + ...opts, + method: "POST" + })); +} +/** + * Search assets by metadata + */ +export function searchAssets({ metadataSearchDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/search/metadata", oazapfts.json({ + ...opts, + method: "POST", + body: metadataSearchDto + }))); +} +/** + * Search people + */ +export function searchPerson({ name, withHidden }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/search/person${QS.query(QS.explode({ + name, + withHidden + }))}`, { + ...opts + })); +} +/** + * Search places + */ +export function searchPlaces({ name }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/search/places${QS.query(QS.explode({ + name + }))}`, { + ...opts + })); +} +/** + * Search random assets + */ +export function searchRandom({ randomSearchDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/search/random", oazapfts.json({ + ...opts, + method: "POST", + body: randomSearchDto + }))); +} +/** + * Smart asset search + */ +export function searchSmart({ smartSearchDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/search/smart", oazapfts.json({ + ...opts, + method: "POST", + body: smartSearchDto + }))); +} +/** + * Search asset statistics + */ +export function searchAssetStatistics({ statisticsSearchDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/search/statistics", oazapfts.json({ + ...opts, + method: "POST", + body: statisticsSearchDto + }))); +} +/** + * Retrieve search suggestions + */ +export function getSearchSuggestions({ country, includeNull, lensModel, make, model, state, $type }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/search/suggestions${QS.query(QS.explode({ + country, + includeNull, + lensModel, + make, + model, + state, + "type": $type + }))}`, { + ...opts + })); +} +/** + * Get server information + */ +export function getAboutInfo(opts) { + return oazapfts.ok(oazapfts.fetchJson("/server/about", { + ...opts + })); +} +/** + * Get APK links + */ +export function getApkLinks(opts) { + return oazapfts.ok(oazapfts.fetchJson("/server/apk-links", { + ...opts + })); +} +/** + * Get config + */ +export function getServerConfig(opts) { + return oazapfts.ok(oazapfts.fetchJson("/server/config", { + ...opts + })); +} +/** + * Get features + */ +export function getServerFeatures(opts) { + return oazapfts.ok(oazapfts.fetchJson("/server/features", { + ...opts + })); +} +/** + * Delete server product key + */ +export function deleteServerLicense(opts) { + return oazapfts.ok(oazapfts.fetchText("/server/license", { + ...opts, + method: "DELETE" + })); +} +/** + * Get product key + */ +export function getServerLicense(opts) { + return oazapfts.ok(oazapfts.fetchJson("/server/license", { + ...opts + })); +} +/** + * Set server product key + */ +export function setServerLicense({ licenseKeyDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/server/license", oazapfts.json({ + ...opts, + method: "PUT", + body: licenseKeyDto + }))); +} +/** + * Get supported media types + */ +export function getSupportedMediaTypes(opts) { + return oazapfts.ok(oazapfts.fetchJson("/server/media-types", { + ...opts + })); +} +/** + * Ping + */ +export function pingServer(opts) { + return oazapfts.ok(oazapfts.fetchJson("/server/ping", { + ...opts + })); +} +/** + * Get statistics + */ +export function getServerStatistics(opts) { + return oazapfts.ok(oazapfts.fetchJson("/server/statistics", { + ...opts + })); +} +/** + * Get storage + */ +export function getStorage(opts) { + return oazapfts.ok(oazapfts.fetchJson("/server/storage", { + ...opts + })); +} +/** + * Get server version + */ +export function getServerVersion(opts) { + return oazapfts.ok(oazapfts.fetchJson("/server/version", { + ...opts + })); +} +/** + * Get version check status + */ +export function getVersionCheck(opts) { + return oazapfts.ok(oazapfts.fetchJson("/server/version-check", { + ...opts + })); +} +/** + * Get version history + */ +export function getVersionHistory(opts) { + return oazapfts.ok(oazapfts.fetchJson("/server/version-history", { + ...opts + })); +} +/** + * Delete all sessions + */ +export function deleteAllSessions(opts) { + return oazapfts.ok(oazapfts.fetchText("/sessions", { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve sessions + */ +export function getSessions(opts) { + return oazapfts.ok(oazapfts.fetchJson("/sessions", { + ...opts + })); +} +/** + * Create a session + */ +export function createSession({ sessionCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/sessions", oazapfts.json({ + ...opts, + method: "POST", + body: sessionCreateDto + }))); +} +/** + * Delete a session + */ +export function deleteSession({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/sessions/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Update a session + */ +export function updateSession({ id, sessionUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/sessions/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: sessionUpdateDto + }))); +} +/** + * Lock a session + */ +export function lockSession({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/sessions/${encodeURIComponent(id)}/lock`, { + ...opts, + method: "POST" + })); +} +/** + * Retrieve all shared links + */ +export function getAllSharedLinks({ albumId, id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/shared-links${QS.query(QS.explode({ + albumId, + id + }))}`, { + ...opts + })); +} +/** + * Create a shared link + */ +export function createSharedLink({ sharedLinkCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/shared-links", oazapfts.json({ + ...opts, + method: "POST", + body: sharedLinkCreateDto + }))); +} +/** + * Shared link login + */ +export function sharedLinkLogin({ key, slug, sharedLinkLoginDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/shared-links/login${QS.query(QS.explode({ + key, + slug + }))}`, oazapfts.json({ + ...opts, + method: "POST", + body: sharedLinkLoginDto + }))); +} +/** + * Retrieve current shared link + */ +export function getMySharedLink({ key, slug }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/shared-links/me${QS.query(QS.explode({ + key, + slug + }))}`, { + ...opts + })); +} +/** + * Delete a shared link + */ +export function removeSharedLink({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/shared-links/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve a shared link + */ +export function getSharedLinkById({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/shared-links/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * Update a shared link + */ +export function updateSharedLink({ id, sharedLinkEditDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/shared-links/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PATCH", + body: sharedLinkEditDto + }))); +} +/** + * Remove assets from a shared link + */ +export function removeSharedLinkAssets({ id, assetIdsDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/shared-links/${encodeURIComponent(id)}/assets`, oazapfts.json({ + ...opts, + method: "DELETE", + body: assetIdsDto + }))); +} +/** + * Add assets to a shared link + */ +export function addSharedLinkAssets({ id, assetIdsDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/shared-links/${encodeURIComponent(id)}/assets`, oazapfts.json({ + ...opts, + method: "PUT", + body: assetIdsDto + }))); +} +/** + * Delete stacks + */ +export function deleteStacks({ bulkIdsDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/stacks", oazapfts.json({ + ...opts, + method: "DELETE", + body: bulkIdsDto + }))); +} +/** + * Retrieve stacks + */ +export function searchStacks({ primaryAssetId }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/stacks${QS.query(QS.explode({ + primaryAssetId + }))}`, { + ...opts + })); +} +/** + * Create a stack + */ +export function createStack({ stackCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/stacks", oazapfts.json({ + ...opts, + method: "POST", + body: stackCreateDto + }))); +} +/** + * Delete a stack + */ +export function deleteStack({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/stacks/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve a stack + */ +export function getStack({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/stacks/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * Update a stack + */ +export function updateStack({ id, stackUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/stacks/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: stackUpdateDto + }))); +} +/** + * Remove an asset from a stack + */ +export function removeAssetFromStack({ assetId, id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/stacks/${encodeURIComponent(id)}/assets/${encodeURIComponent(assetId)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Delete acknowledgements + */ +export function deleteSyncAck({ syncAckDeleteDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/sync/ack", oazapfts.json({ + ...opts, + method: "DELETE", + body: syncAckDeleteDto + }))); +} +/** + * Retrieve acknowledgements + */ +export function getSyncAck(opts) { + return oazapfts.ok(oazapfts.fetchJson("/sync/ack", { + ...opts + })); +} +/** + * Acknowledge changes + */ +export function sendSyncAck({ syncAckSetDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/sync/ack", oazapfts.json({ + ...opts, + method: "POST", + body: syncAckSetDto + }))); +} +/** + * Stream sync changes + */ +export function getSyncStream({ syncStreamDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/sync/stream", oazapfts.json({ + ...opts, + method: "POST", + body: syncStreamDto + }))); +} +/** + * Get system configuration + */ +export function getConfig(opts) { + return oazapfts.ok(oazapfts.fetchJson("/system-config", { + ...opts + })); +} +/** + * Update system configuration + */ +export function updateConfig({ systemConfigDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/system-config", oazapfts.json({ + ...opts, + method: "PUT", + body: systemConfigDto + }))); +} +/** + * Get system configuration defaults + */ +export function getConfigDefaults(opts) { + return oazapfts.ok(oazapfts.fetchJson("/system-config/defaults", { + ...opts + })); +} +/** + * Get storage template options + */ +export function getStorageTemplateOptions(opts) { + return oazapfts.ok(oazapfts.fetchJson("/system-config/storage-template-options", { + ...opts + })); +} +/** + * Retrieve admin onboarding + */ +export function getAdminOnboarding(opts) { + return oazapfts.ok(oazapfts.fetchJson("/system-metadata/admin-onboarding", { + ...opts + })); +} +/** + * Update admin onboarding + */ +export function updateAdminOnboarding({ adminOnboardingUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchText("/system-metadata/admin-onboarding", oazapfts.json({ + ...opts, + method: "POST", + body: adminOnboardingUpdateDto + }))); +} +/** + * Retrieve reverse geocoding state + */ +export function getReverseGeocodingState(opts) { + return oazapfts.ok(oazapfts.fetchJson("/system-metadata/reverse-geocoding-state", { + ...opts + })); +} +/** + * Retrieve version check state + */ +export function getVersionCheckState(opts) { + return oazapfts.ok(oazapfts.fetchJson("/system-metadata/version-check-state", { + ...opts + })); +} +/** + * Retrieve tags + */ +export function getAllTags(opts) { + return oazapfts.ok(oazapfts.fetchJson("/tags", { + ...opts + })); +} +/** + * Create a tag + */ +export function createTag({ tagCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/tags", oazapfts.json({ + ...opts, + method: "POST", + body: tagCreateDto + }))); +} +/** + * Upsert tags + */ +export function upsertTags({ tagUpsertDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/tags", oazapfts.json({ + ...opts, + method: "PUT", + body: tagUpsertDto + }))); +} +/** + * Tag assets + */ +export function bulkTagAssets({ tagBulkAssetsDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/tags/assets", oazapfts.json({ + ...opts, + method: "PUT", + body: tagBulkAssetsDto + }))); +} +/** + * Delete a tag + */ +export function deleteTag({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/tags/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve a tag + */ +export function getTagById({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/tags/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * Update a tag + */ +export function updateTag({ id, tagUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/tags/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: tagUpdateDto + }))); +} +/** + * Untag assets + */ +export function untagAssets({ id, bulkIdsDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/tags/${encodeURIComponent(id)}/assets`, oazapfts.json({ + ...opts, + method: "DELETE", + body: bulkIdsDto + }))); +} +/** + * Tag assets + */ +export function tagAssets({ id, bulkIdsDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/tags/${encodeURIComponent(id)}/assets`, oazapfts.json({ + ...opts, + method: "PUT", + body: bulkIdsDto + }))); +} +/** + * Get time bucket + */ +export function getTimeBucket({ albumId, bbox, isFavorite, isTrashed, key, order, personId, slug, tagId, timeBucket, userId, visibility, withCoordinates, withPartners, withStacked }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/timeline/bucket${QS.query(QS.explode({ + albumId, + bbox, + isFavorite, + isTrashed, + key, + order, + personId, + slug, + tagId, + timeBucket, + userId, + visibility, + withCoordinates, + withPartners, + withStacked + }))}`, { + ...opts + })); +} +/** + * Get time buckets + */ +export function getTimeBuckets({ albumId, bbox, isFavorite, isTrashed, key, order, personId, slug, tagId, userId, visibility, withCoordinates, withPartners, withStacked }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/timeline/buckets${QS.query(QS.explode({ + albumId, + bbox, + isFavorite, + isTrashed, + key, + order, + personId, + slug, + tagId, + userId, + visibility, + withCoordinates, + withPartners, + withStacked + }))}`, { + ...opts + })); +} +/** + * Empty trash + */ +export function emptyTrash(opts) { + return oazapfts.ok(oazapfts.fetchJson("/trash/empty", { + ...opts, + method: "POST" + })); +} +/** + * Restore trash + */ +export function restoreTrash(opts) { + return oazapfts.ok(oazapfts.fetchJson("/trash/restore", { + ...opts, + method: "POST" + })); +} +/** + * Restore assets + */ +export function restoreAssets({ bulkIdsDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/trash/restore/assets", oazapfts.json({ + ...opts, + method: "POST", + body: bulkIdsDto + }))); +} +/** + * Get all users + */ +export function searchUsers(opts) { + return oazapfts.ok(oazapfts.fetchJson("/users", { + ...opts + })); +} +/** + * Get current user + */ +export function getMyUser(opts) { + return oazapfts.ok(oazapfts.fetchJson("/users/me", { + ...opts + })); +} +/** + * Update current user + */ +export function updateMyUser({ userUpdateMeDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/users/me", oazapfts.json({ + ...opts, + method: "PUT", + body: userUpdateMeDto + }))); +} +/** + * Delete user product key + */ +export function deleteUserLicense(opts) { + return oazapfts.ok(oazapfts.fetchText("/users/me/license", { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve user product key + */ +export function getUserLicense(opts) { + return oazapfts.ok(oazapfts.fetchJson("/users/me/license", { + ...opts + })); +} +/** + * Set user product key + */ +export function setUserLicense({ licenseKeyDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/users/me/license", oazapfts.json({ + ...opts, + method: "PUT", + body: licenseKeyDto + }))); +} +/** + * Delete user onboarding + */ +export function deleteUserOnboarding(opts) { + return oazapfts.ok(oazapfts.fetchText("/users/me/onboarding", { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve user onboarding + */ +export function getUserOnboarding(opts) { + return oazapfts.ok(oazapfts.fetchJson("/users/me/onboarding", { + ...opts + })); +} +/** + * Update user onboarding + */ +export function setUserOnboarding({ onboardingDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/users/me/onboarding", oazapfts.json({ + ...opts, + method: "PUT", + body: onboardingDto + }))); +} +/** + * Get my preferences + */ +export function getMyPreferences(opts) { + return oazapfts.ok(oazapfts.fetchJson("/users/me/preferences", { + ...opts + })); +} +/** + * Update my preferences + */ +export function updateMyPreferences({ userPreferencesUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/users/me/preferences", oazapfts.json({ + ...opts, + method: "PUT", + body: userPreferencesUpdateDto + }))); +} +/** + * Delete user profile image + */ +export function deleteProfileImage(opts) { + return oazapfts.ok(oazapfts.fetchText("/users/profile-image", { + ...opts, + method: "DELETE" + })); +} +/** + * Create user profile image + */ +export function createProfileImage({ createProfileImageDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/users/profile-image", oazapfts.multipart({ + ...opts, + method: "POST", + body: createProfileImageDto + }))); +} +/** + * Retrieve a user + */ +export function getUser({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/users/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * Retrieve user profile image + */ +export function getProfileImage({ id }, opts) { + return oazapfts.ok(oazapfts.fetchBlob(`/users/${encodeURIComponent(id)}/profile-image`, { + ...opts + })); +} +/** + * Retrieve assets by original path + */ +export function getAssetsByOriginalPath({ path }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/view/folder${QS.query(QS.explode({ + path + }))}`, { + ...opts + })); +} +/** + * Retrieve unique paths + */ +export function getUniqueOriginalPaths(opts) { + return oazapfts.ok(oazapfts.fetchJson("/view/folder/unique-paths", { + ...opts + })); +} +/** + * List all workflows + */ +export function getWorkflows(opts) { + return oazapfts.ok(oazapfts.fetchJson("/workflows", { + ...opts + })); +} +/** + * Create a workflow + */ +export function createWorkflow({ workflowCreateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson("/workflows", oazapfts.json({ + ...opts, + method: "POST", + body: workflowCreateDto + }))); +} +/** + * Delete a workflow + */ +export function deleteWorkflow({ id }, opts) { + return oazapfts.ok(oazapfts.fetchText(`/workflows/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve a workflow + */ +export function getWorkflow({ id }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/workflows/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * Update a workflow + */ +export function updateWorkflow({ id, workflowUpdateDto }, opts) { + return oazapfts.ok(oazapfts.fetchJson(`/workflows/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: workflowUpdateDto + }))); +} +export var ReactionLevel; +(function (ReactionLevel) { + ReactionLevel["Album"] = "album"; + ReactionLevel["Asset"] = "asset"; +})(ReactionLevel || (ReactionLevel = {})); +export var ReactionType; +(function (ReactionType) { + ReactionType["Comment"] = "comment"; + ReactionType["Like"] = "like"; +})(ReactionType || (ReactionType = {})); +export var UserAvatarColor; +(function (UserAvatarColor) { + UserAvatarColor["Primary"] = "primary"; + UserAvatarColor["Pink"] = "pink"; + UserAvatarColor["Red"] = "red"; + UserAvatarColor["Yellow"] = "yellow"; + UserAvatarColor["Blue"] = "blue"; + UserAvatarColor["Green"] = "green"; + UserAvatarColor["Purple"] = "purple"; + UserAvatarColor["Orange"] = "orange"; + UserAvatarColor["Gray"] = "gray"; + UserAvatarColor["Amber"] = "amber"; +})(UserAvatarColor || (UserAvatarColor = {})); +export var MaintenanceAction; +(function (MaintenanceAction) { + MaintenanceAction["Start"] = "start"; + MaintenanceAction["End"] = "end"; + MaintenanceAction["SelectDatabaseRestore"] = "select_database_restore"; + MaintenanceAction["RestoreDatabase"] = "restore_database"; +})(MaintenanceAction || (MaintenanceAction = {})); +export var StorageFolder; +(function (StorageFolder) { + StorageFolder["EncodedVideo"] = "encoded-video"; + StorageFolder["Library"] = "library"; + StorageFolder["Upload"] = "upload"; + StorageFolder["Profile"] = "profile"; + StorageFolder["Thumbs"] = "thumbs"; + StorageFolder["Backups"] = "backups"; +})(StorageFolder || (StorageFolder = {})); +export var NotificationLevel; +(function (NotificationLevel) { + NotificationLevel["Success"] = "success"; + NotificationLevel["Error"] = "error"; + NotificationLevel["Warning"] = "warning"; + NotificationLevel["Info"] = "info"; +})(NotificationLevel || (NotificationLevel = {})); +export var NotificationType; +(function (NotificationType) { + NotificationType["JobFailed"] = "JobFailed"; + NotificationType["BackupFailed"] = "BackupFailed"; + NotificationType["SystemMessage"] = "SystemMessage"; + NotificationType["AlbumInvite"] = "AlbumInvite"; + NotificationType["AlbumUpdate"] = "AlbumUpdate"; + NotificationType["Custom"] = "Custom"; +})(NotificationType || (NotificationType = {})); +export var UserStatus; +(function (UserStatus) { + UserStatus["Active"] = "active"; + UserStatus["Removing"] = "removing"; + UserStatus["Deleted"] = "deleted"; +})(UserStatus || (UserStatus = {})); +export var AssetOrder; +(function (AssetOrder) { + AssetOrder["Asc"] = "asc"; + AssetOrder["Desc"] = "desc"; +})(AssetOrder || (AssetOrder = {})); +export var AssetVisibility; +(function (AssetVisibility) { + AssetVisibility["Archive"] = "archive"; + AssetVisibility["Timeline"] = "timeline"; + AssetVisibility["Hidden"] = "hidden"; + AssetVisibility["Locked"] = "locked"; +})(AssetVisibility || (AssetVisibility = {})); +export var AlbumUserRole; +(function (AlbumUserRole) { + AlbumUserRole["Editor"] = "editor"; + AlbumUserRole["Owner"] = "owner"; + AlbumUserRole["Viewer"] = "viewer"; +})(AlbumUserRole || (AlbumUserRole = {})); +export var BulkIdErrorReason; +(function (BulkIdErrorReason) { + BulkIdErrorReason["Duplicate"] = "duplicate"; + BulkIdErrorReason["NoPermission"] = "no_permission"; + BulkIdErrorReason["NotFound"] = "not_found"; + BulkIdErrorReason["Unknown"] = "unknown"; + BulkIdErrorReason["Validation"] = "validation"; +})(BulkIdErrorReason || (BulkIdErrorReason = {})); +export var Permission; +(function (Permission) { + Permission["All"] = "all"; + Permission["ActivityCreate"] = "activity.create"; + Permission["ActivityRead"] = "activity.read"; + Permission["ActivityUpdate"] = "activity.update"; + Permission["ActivityDelete"] = "activity.delete"; + Permission["ActivityStatistics"] = "activity.statistics"; + Permission["ApiKeyCreate"] = "apiKey.create"; + Permission["ApiKeyRead"] = "apiKey.read"; + Permission["ApiKeyUpdate"] = "apiKey.update"; + Permission["ApiKeyDelete"] = "apiKey.delete"; + Permission["AssetRead"] = "asset.read"; + Permission["AssetUpdate"] = "asset.update"; + Permission["AssetDelete"] = "asset.delete"; + Permission["AssetStatistics"] = "asset.statistics"; + Permission["AssetShare"] = "asset.share"; + Permission["AssetView"] = "asset.view"; + Permission["AssetDownload"] = "asset.download"; + Permission["AssetUpload"] = "asset.upload"; + Permission["AssetCopy"] = "asset.copy"; + Permission["AssetDerive"] = "asset.derive"; + Permission["AssetEditGet"] = "asset.edit.get"; + Permission["AssetEditCreate"] = "asset.edit.create"; + Permission["AssetEditDelete"] = "asset.edit.delete"; + Permission["AlbumCreate"] = "album.create"; + Permission["AlbumRead"] = "album.read"; + Permission["AlbumUpdate"] = "album.update"; + Permission["AlbumDelete"] = "album.delete"; + Permission["AlbumStatistics"] = "album.statistics"; + Permission["AlbumShare"] = "album.share"; + Permission["AlbumDownload"] = "album.download"; + Permission["AlbumAssetCreate"] = "albumAsset.create"; + Permission["AlbumAssetDelete"] = "albumAsset.delete"; + Permission["AlbumUserCreate"] = "albumUser.create"; + Permission["AlbumUserUpdate"] = "albumUser.update"; + Permission["AlbumUserDelete"] = "albumUser.delete"; + Permission["AuthChangePassword"] = "auth.changePassword"; + Permission["AuthDeviceDelete"] = "authDevice.delete"; + Permission["ArchiveRead"] = "archive.read"; + Permission["BackupList"] = "backup.list"; + Permission["BackupDownload"] = "backup.download"; + Permission["BackupUpload"] = "backup.upload"; + Permission["BackupDelete"] = "backup.delete"; + Permission["DuplicateRead"] = "duplicate.read"; + Permission["DuplicateDelete"] = "duplicate.delete"; + Permission["FaceCreate"] = "face.create"; + Permission["FaceRead"] = "face.read"; + Permission["FaceUpdate"] = "face.update"; + Permission["FaceDelete"] = "face.delete"; + Permission["FolderRead"] = "folder.read"; + Permission["JobCreate"] = "job.create"; + Permission["JobRead"] = "job.read"; + Permission["LibraryCreate"] = "library.create"; + Permission["LibraryRead"] = "library.read"; + Permission["LibraryUpdate"] = "library.update"; + Permission["LibraryDelete"] = "library.delete"; + Permission["LibraryStatistics"] = "library.statistics"; + Permission["TimelineRead"] = "timeline.read"; + Permission["TimelineDownload"] = "timeline.download"; + Permission["Maintenance"] = "maintenance"; + Permission["MapRead"] = "map.read"; + Permission["MapSearch"] = "map.search"; + Permission["MemoryCreate"] = "memory.create"; + Permission["MemoryRead"] = "memory.read"; + Permission["MemoryUpdate"] = "memory.update"; + Permission["MemoryDelete"] = "memory.delete"; + Permission["MemoryStatistics"] = "memory.statistics"; + Permission["MemoryAssetCreate"] = "memoryAsset.create"; + Permission["MemoryAssetDelete"] = "memoryAsset.delete"; + Permission["NotificationCreate"] = "notification.create"; + Permission["NotificationRead"] = "notification.read"; + Permission["NotificationUpdate"] = "notification.update"; + Permission["NotificationDelete"] = "notification.delete"; + Permission["PartnerCreate"] = "partner.create"; + Permission["PartnerRead"] = "partner.read"; + Permission["PartnerUpdate"] = "partner.update"; + Permission["PartnerDelete"] = "partner.delete"; + Permission["PersonCreate"] = "person.create"; + Permission["PersonRead"] = "person.read"; + Permission["PersonUpdate"] = "person.update"; + Permission["PersonDelete"] = "person.delete"; + Permission["PersonStatistics"] = "person.statistics"; + Permission["PersonMerge"] = "person.merge"; + Permission["PersonReassign"] = "person.reassign"; + Permission["PinCodeCreate"] = "pinCode.create"; + Permission["PinCodeUpdate"] = "pinCode.update"; + Permission["PinCodeDelete"] = "pinCode.delete"; + Permission["PluginCreate"] = "plugin.create"; + Permission["PluginRead"] = "plugin.read"; + Permission["PluginUpdate"] = "plugin.update"; + Permission["PluginDelete"] = "plugin.delete"; + Permission["ServerAbout"] = "server.about"; + Permission["ServerApkLinks"] = "server.apkLinks"; + Permission["ServerStorage"] = "server.storage"; + Permission["ServerStatistics"] = "server.statistics"; + Permission["ServerVersionCheck"] = "server.versionCheck"; + Permission["ServerLicenseRead"] = "serverLicense.read"; + Permission["ServerLicenseUpdate"] = "serverLicense.update"; + Permission["ServerLicenseDelete"] = "serverLicense.delete"; + Permission["SessionCreate"] = "session.create"; + Permission["SessionRead"] = "session.read"; + Permission["SessionUpdate"] = "session.update"; + Permission["SessionDelete"] = "session.delete"; + Permission["SessionLock"] = "session.lock"; + Permission["SharedLinkCreate"] = "sharedLink.create"; + Permission["SharedLinkRead"] = "sharedLink.read"; + Permission["SharedLinkUpdate"] = "sharedLink.update"; + Permission["SharedLinkDelete"] = "sharedLink.delete"; + Permission["StackCreate"] = "stack.create"; + Permission["StackRead"] = "stack.read"; + Permission["StackUpdate"] = "stack.update"; + Permission["StackDelete"] = "stack.delete"; + Permission["SyncStream"] = "sync.stream"; + Permission["SyncCheckpointRead"] = "syncCheckpoint.read"; + Permission["SyncCheckpointUpdate"] = "syncCheckpoint.update"; + Permission["SyncCheckpointDelete"] = "syncCheckpoint.delete"; + Permission["SystemConfigRead"] = "systemConfig.read"; + Permission["SystemConfigUpdate"] = "systemConfig.update"; + Permission["SystemMetadataRead"] = "systemMetadata.read"; + Permission["SystemMetadataUpdate"] = "systemMetadata.update"; + Permission["TagCreate"] = "tag.create"; + Permission["TagRead"] = "tag.read"; + Permission["TagUpdate"] = "tag.update"; + Permission["TagDelete"] = "tag.delete"; + Permission["TagAsset"] = "tag.asset"; + Permission["UserRead"] = "user.read"; + Permission["UserUpdate"] = "user.update"; + Permission["UserLicenseCreate"] = "userLicense.create"; + Permission["UserLicenseRead"] = "userLicense.read"; + Permission["UserLicenseUpdate"] = "userLicense.update"; + Permission["UserLicenseDelete"] = "userLicense.delete"; + Permission["UserOnboardingRead"] = "userOnboarding.read"; + Permission["UserOnboardingUpdate"] = "userOnboarding.update"; + Permission["UserOnboardingDelete"] = "userOnboarding.delete"; + Permission["UserPreferenceRead"] = "userPreference.read"; + Permission["UserPreferenceUpdate"] = "userPreference.update"; + Permission["UserProfileImageCreate"] = "userProfileImage.create"; + Permission["UserProfileImageRead"] = "userProfileImage.read"; + Permission["UserProfileImageUpdate"] = "userProfileImage.update"; + Permission["UserProfileImageDelete"] = "userProfileImage.delete"; + Permission["QueueRead"] = "queue.read"; + Permission["QueueUpdate"] = "queue.update"; + Permission["QueueJobCreate"] = "queueJob.create"; + Permission["QueueJobRead"] = "queueJob.read"; + Permission["QueueJobUpdate"] = "queueJob.update"; + Permission["QueueJobDelete"] = "queueJob.delete"; + Permission["WorkflowCreate"] = "workflow.create"; + Permission["WorkflowRead"] = "workflow.read"; + Permission["WorkflowUpdate"] = "workflow.update"; + Permission["WorkflowDelete"] = "workflow.delete"; + Permission["AdminUserCreate"] = "adminUser.create"; + Permission["AdminUserRead"] = "adminUser.read"; + Permission["AdminUserUpdate"] = "adminUser.update"; + Permission["AdminUserDelete"] = "adminUser.delete"; + Permission["AdminSessionRead"] = "adminSession.read"; + Permission["AdminAuthUnlinkAll"] = "adminAuth.unlinkAll"; +})(Permission || (Permission = {})); +export var AssetMediaStatus; +(function (AssetMediaStatus) { + AssetMediaStatus["Created"] = "created"; + AssetMediaStatus["Duplicate"] = "duplicate"; +})(AssetMediaStatus || (AssetMediaStatus = {})); +export var AssetUploadAction; +(function (AssetUploadAction) { + AssetUploadAction["Accept"] = "accept"; + AssetUploadAction["Reject"] = "reject"; +})(AssetUploadAction || (AssetUploadAction = {})); +export var AssetRejectReason; +(function (AssetRejectReason) { + AssetRejectReason["Duplicate"] = "duplicate"; + AssetRejectReason["UnsupportedFormat"] = "unsupported-format"; +})(AssetRejectReason || (AssetRejectReason = {})); +export var AssetJobName; +(function (AssetJobName) { + AssetJobName["RefreshFaces"] = "refresh-faces"; + AssetJobName["RefreshMetadata"] = "refresh-metadata"; + AssetJobName["RegenerateThumbnail"] = "regenerate-thumbnail"; + AssetJobName["TranscodeVideo"] = "transcode-video"; +})(AssetJobName || (AssetJobName = {})); +export var SourceType; +(function (SourceType) { + SourceType["MachineLearning"] = "machine-learning"; + SourceType["Exif"] = "exif"; + SourceType["Manual"] = "manual"; +})(SourceType || (SourceType = {})); +export var AssetTypeEnum; +(function (AssetTypeEnum) { + AssetTypeEnum["Image"] = "IMAGE"; + AssetTypeEnum["Video"] = "VIDEO"; + AssetTypeEnum["Audio"] = "AUDIO"; + AssetTypeEnum["Other"] = "OTHER"; +})(AssetTypeEnum || (AssetTypeEnum = {})); +export var AssetEditAction; +(function (AssetEditAction) { + AssetEditAction["Crop"] = "crop"; + AssetEditAction["Rotate"] = "rotate"; + AssetEditAction["Mirror"] = "mirror"; +})(AssetEditAction || (AssetEditAction = {})); +export var MirrorAxis; +(function (MirrorAxis) { + MirrorAxis["Horizontal"] = "horizontal"; + MirrorAxis["Vertical"] = "vertical"; +})(MirrorAxis || (MirrorAxis = {})); +export var AssetMediaSize; +(function (AssetMediaSize) { + AssetMediaSize["Original"] = "original"; + AssetMediaSize["Fullsize"] = "fullsize"; + AssetMediaSize["Preview"] = "preview"; + AssetMediaSize["Thumbnail"] = "thumbnail"; +})(AssetMediaSize || (AssetMediaSize = {})); +export var ManualJobName; +(function (ManualJobName) { + ManualJobName["PersonCleanup"] = "person-cleanup"; + ManualJobName["TagCleanup"] = "tag-cleanup"; + ManualJobName["UserCleanup"] = "user-cleanup"; + ManualJobName["MemoryCleanup"] = "memory-cleanup"; + ManualJobName["MemoryCreate"] = "memory-create"; + ManualJobName["BackupDatabase"] = "backup-database"; +})(ManualJobName || (ManualJobName = {})); +export var QueueName; +(function (QueueName) { + QueueName["ThumbnailGeneration"] = "thumbnailGeneration"; + QueueName["MetadataExtraction"] = "metadataExtraction"; + QueueName["VideoConversion"] = "videoConversion"; + QueueName["FaceDetection"] = "faceDetection"; + QueueName["FacialRecognition"] = "facialRecognition"; + QueueName["SmartSearch"] = "smartSearch"; + QueueName["DuplicateDetection"] = "duplicateDetection"; + QueueName["BackgroundTask"] = "backgroundTask"; + QueueName["StorageTemplateMigration"] = "storageTemplateMigration"; + QueueName["Migration"] = "migration"; + QueueName["Search"] = "search"; + QueueName["Sidecar"] = "sidecar"; + QueueName["Library"] = "library"; + QueueName["Notifications"] = "notifications"; + QueueName["BackupDatabase"] = "backupDatabase"; + QueueName["Ocr"] = "ocr"; + QueueName["Workflow"] = "workflow"; + QueueName["Editor"] = "editor"; +})(QueueName || (QueueName = {})); +export var QueueCommand; +(function (QueueCommand) { + QueueCommand["Start"] = "start"; + QueueCommand["Pause"] = "pause"; + QueueCommand["Resume"] = "resume"; + QueueCommand["Empty"] = "empty"; + QueueCommand["ClearFailed"] = "clear-failed"; +})(QueueCommand || (QueueCommand = {})); +export var MemorySearchOrder; +(function (MemorySearchOrder) { + MemorySearchOrder["Asc"] = "asc"; + MemorySearchOrder["Desc"] = "desc"; + MemorySearchOrder["Random"] = "random"; +})(MemorySearchOrder || (MemorySearchOrder = {})); +export var MemoryType; +(function (MemoryType) { + MemoryType["OnThisDay"] = "on_this_day"; +})(MemoryType || (MemoryType = {})); +export var PartnerDirection; +(function (PartnerDirection) { + PartnerDirection["SharedBy"] = "shared-by"; + PartnerDirection["SharedWith"] = "shared-with"; +})(PartnerDirection || (PartnerDirection = {})); +export var PluginJsonSchemaType; +(function (PluginJsonSchemaType) { + PluginJsonSchemaType["String"] = "string"; + PluginJsonSchemaType["Number"] = "number"; + PluginJsonSchemaType["Integer"] = "integer"; + PluginJsonSchemaType["Boolean"] = "boolean"; + PluginJsonSchemaType["Object"] = "object"; + PluginJsonSchemaType["Array"] = "array"; + PluginJsonSchemaType["Null"] = "null"; +})(PluginJsonSchemaType || (PluginJsonSchemaType = {})); +export var PluginContextType; +(function (PluginContextType) { + PluginContextType["Asset"] = "asset"; + PluginContextType["Album"] = "album"; + PluginContextType["Person"] = "person"; +})(PluginContextType || (PluginContextType = {})); +export var PluginTriggerType; +(function (PluginTriggerType) { + PluginTriggerType["AssetCreate"] = "AssetCreate"; + PluginTriggerType["PersonRecognized"] = "PersonRecognized"; +})(PluginTriggerType || (PluginTriggerType = {})); +export var QueueJobStatus; +(function (QueueJobStatus) { + QueueJobStatus["Active"] = "active"; + QueueJobStatus["Failed"] = "failed"; + QueueJobStatus["Completed"] = "completed"; + QueueJobStatus["Delayed"] = "delayed"; + QueueJobStatus["Waiting"] = "waiting"; + QueueJobStatus["Paused"] = "paused"; +})(QueueJobStatus || (QueueJobStatus = {})); +export var JobName; +(function (JobName) { + JobName["AssetDelete"] = "AssetDelete"; + JobName["AssetDeleteCheck"] = "AssetDeleteCheck"; + JobName["AssetDetectFacesQueueAll"] = "AssetDetectFacesQueueAll"; + JobName["AssetDetectFaces"] = "AssetDetectFaces"; + JobName["AssetDetectDuplicatesQueueAll"] = "AssetDetectDuplicatesQueueAll"; + JobName["AssetDetectDuplicates"] = "AssetDetectDuplicates"; + JobName["AssetEditThumbnailGeneration"] = "AssetEditThumbnailGeneration"; + JobName["AssetEncodeVideoQueueAll"] = "AssetEncodeVideoQueueAll"; + JobName["AssetEncodeVideo"] = "AssetEncodeVideo"; + JobName["AssetEmptyTrash"] = "AssetEmptyTrash"; + JobName["AssetExtractMetadataQueueAll"] = "AssetExtractMetadataQueueAll"; + JobName["AssetExtractMetadata"] = "AssetExtractMetadata"; + JobName["AssetFileMigration"] = "AssetFileMigration"; + JobName["AssetGenerateThumbnailsQueueAll"] = "AssetGenerateThumbnailsQueueAll"; + JobName["AssetGenerateThumbnails"] = "AssetGenerateThumbnails"; + JobName["AuditTableCleanup"] = "AuditTableCleanup"; + JobName["DatabaseBackup"] = "DatabaseBackup"; + JobName["FacialRecognitionQueueAll"] = "FacialRecognitionQueueAll"; + JobName["FacialRecognition"] = "FacialRecognition"; + JobName["FileDelete"] = "FileDelete"; + JobName["FileMigrationQueueAll"] = "FileMigrationQueueAll"; + JobName["LibraryDeleteCheck"] = "LibraryDeleteCheck"; + JobName["LibraryDelete"] = "LibraryDelete"; + JobName["LibraryRemoveAsset"] = "LibraryRemoveAsset"; + JobName["LibraryScanAssetsQueueAll"] = "LibraryScanAssetsQueueAll"; + JobName["LibrarySyncAssets"] = "LibrarySyncAssets"; + JobName["LibrarySyncFilesQueueAll"] = "LibrarySyncFilesQueueAll"; + JobName["LibrarySyncFiles"] = "LibrarySyncFiles"; + JobName["LibraryScanQueueAll"] = "LibraryScanQueueAll"; + JobName["MemoryCleanup"] = "MemoryCleanup"; + JobName["MemoryGenerate"] = "MemoryGenerate"; + JobName["NotificationsCleanup"] = "NotificationsCleanup"; + JobName["NotifyUserSignup"] = "NotifyUserSignup"; + JobName["NotifyAlbumInvite"] = "NotifyAlbumInvite"; + JobName["NotifyAlbumUpdate"] = "NotifyAlbumUpdate"; + JobName["UserDelete"] = "UserDelete"; + JobName["UserDeleteCheck"] = "UserDeleteCheck"; + JobName["UserSyncUsage"] = "UserSyncUsage"; + JobName["PersonCleanup"] = "PersonCleanup"; + JobName["PersonFileMigration"] = "PersonFileMigration"; + JobName["PersonGenerateThumbnail"] = "PersonGenerateThumbnail"; + JobName["SessionCleanup"] = "SessionCleanup"; + JobName["SendMail"] = "SendMail"; + JobName["SidecarQueueAll"] = "SidecarQueueAll"; + JobName["SidecarCheck"] = "SidecarCheck"; + JobName["SidecarWrite"] = "SidecarWrite"; + JobName["SmartSearchQueueAll"] = "SmartSearchQueueAll"; + JobName["SmartSearch"] = "SmartSearch"; + JobName["StorageTemplateMigration"] = "StorageTemplateMigration"; + JobName["StorageTemplateMigrationSingle"] = "StorageTemplateMigrationSingle"; + JobName["TagCleanup"] = "TagCleanup"; + JobName["VersionCheck"] = "VersionCheck"; + JobName["OcrQueueAll"] = "OcrQueueAll"; + JobName["Ocr"] = "Ocr"; + JobName["WorkflowRun"] = "WorkflowRun"; +})(JobName || (JobName = {})); +export var SearchSuggestionType; +(function (SearchSuggestionType) { + SearchSuggestionType["Country"] = "country"; + SearchSuggestionType["State"] = "state"; + SearchSuggestionType["City"] = "city"; + SearchSuggestionType["CameraMake"] = "camera-make"; + SearchSuggestionType["CameraModel"] = "camera-model"; + SearchSuggestionType["CameraLensModel"] = "camera-lens-model"; +})(SearchSuggestionType || (SearchSuggestionType = {})); +export var SharedLinkType; +(function (SharedLinkType) { + SharedLinkType["Album"] = "ALBUM"; + SharedLinkType["Individual"] = "INDIVIDUAL"; +})(SharedLinkType || (SharedLinkType = {})); +export var AssetIdErrorReason; +(function (AssetIdErrorReason) { + AssetIdErrorReason["Duplicate"] = "duplicate"; + AssetIdErrorReason["NoPermission"] = "no_permission"; + AssetIdErrorReason["NotFound"] = "not_found"; +})(AssetIdErrorReason || (AssetIdErrorReason = {})); +export var SyncEntityType; +(function (SyncEntityType) { + SyncEntityType["AuthUserV1"] = "AuthUserV1"; + SyncEntityType["UserV1"] = "UserV1"; + SyncEntityType["UserDeleteV1"] = "UserDeleteV1"; + SyncEntityType["AssetV1"] = "AssetV1"; + SyncEntityType["AssetV2"] = "AssetV2"; + SyncEntityType["AssetDeleteV1"] = "AssetDeleteV1"; + SyncEntityType["AssetExifV1"] = "AssetExifV1"; + SyncEntityType["AssetEditV1"] = "AssetEditV1"; + SyncEntityType["AssetEditDeleteV1"] = "AssetEditDeleteV1"; + SyncEntityType["AssetMetadataV1"] = "AssetMetadataV1"; + SyncEntityType["AssetMetadataDeleteV1"] = "AssetMetadataDeleteV1"; + SyncEntityType["PartnerV1"] = "PartnerV1"; + SyncEntityType["PartnerDeleteV1"] = "PartnerDeleteV1"; + SyncEntityType["PartnerAssetV1"] = "PartnerAssetV1"; + SyncEntityType["PartnerAssetV2"] = "PartnerAssetV2"; + SyncEntityType["PartnerAssetBackfillV1"] = "PartnerAssetBackfillV1"; + SyncEntityType["PartnerAssetBackfillV2"] = "PartnerAssetBackfillV2"; + SyncEntityType["PartnerAssetDeleteV1"] = "PartnerAssetDeleteV1"; + SyncEntityType["PartnerAssetExifV1"] = "PartnerAssetExifV1"; + SyncEntityType["PartnerAssetExifBackfillV1"] = "PartnerAssetExifBackfillV1"; + SyncEntityType["PartnerStackBackfillV1"] = "PartnerStackBackfillV1"; + SyncEntityType["PartnerStackDeleteV1"] = "PartnerStackDeleteV1"; + SyncEntityType["PartnerStackV1"] = "PartnerStackV1"; + SyncEntityType["AlbumV1"] = "AlbumV1"; + SyncEntityType["AlbumV2"] = "AlbumV2"; + SyncEntityType["AlbumDeleteV1"] = "AlbumDeleteV1"; + SyncEntityType["AlbumUserV1"] = "AlbumUserV1"; + SyncEntityType["AlbumUserBackfillV1"] = "AlbumUserBackfillV1"; + SyncEntityType["AlbumUserDeleteV1"] = "AlbumUserDeleteV1"; + SyncEntityType["AlbumAssetCreateV1"] = "AlbumAssetCreateV1"; + SyncEntityType["AlbumAssetCreateV2"] = "AlbumAssetCreateV2"; + SyncEntityType["AlbumAssetUpdateV1"] = "AlbumAssetUpdateV1"; + SyncEntityType["AlbumAssetUpdateV2"] = "AlbumAssetUpdateV2"; + SyncEntityType["AlbumAssetBackfillV1"] = "AlbumAssetBackfillV1"; + SyncEntityType["AlbumAssetBackfillV2"] = "AlbumAssetBackfillV2"; + SyncEntityType["AlbumAssetExifCreateV1"] = "AlbumAssetExifCreateV1"; + SyncEntityType["AlbumAssetExifUpdateV1"] = "AlbumAssetExifUpdateV1"; + SyncEntityType["AlbumAssetExifBackfillV1"] = "AlbumAssetExifBackfillV1"; + SyncEntityType["AlbumToAssetV1"] = "AlbumToAssetV1"; + SyncEntityType["AlbumToAssetDeleteV1"] = "AlbumToAssetDeleteV1"; + SyncEntityType["AlbumToAssetBackfillV1"] = "AlbumToAssetBackfillV1"; + SyncEntityType["MemoryV1"] = "MemoryV1"; + SyncEntityType["MemoryDeleteV1"] = "MemoryDeleteV1"; + SyncEntityType["MemoryToAssetV1"] = "MemoryToAssetV1"; + SyncEntityType["MemoryToAssetDeleteV1"] = "MemoryToAssetDeleteV1"; + SyncEntityType["StackV1"] = "StackV1"; + SyncEntityType["StackDeleteV1"] = "StackDeleteV1"; + SyncEntityType["PersonV1"] = "PersonV1"; + SyncEntityType["PersonDeleteV1"] = "PersonDeleteV1"; + SyncEntityType["AssetFaceV1"] = "AssetFaceV1"; + SyncEntityType["AssetFaceV2"] = "AssetFaceV2"; + SyncEntityType["AssetFaceDeleteV1"] = "AssetFaceDeleteV1"; + SyncEntityType["UserMetadataV1"] = "UserMetadataV1"; + SyncEntityType["UserMetadataDeleteV1"] = "UserMetadataDeleteV1"; + SyncEntityType["SyncAckV1"] = "SyncAckV1"; + SyncEntityType["SyncResetV1"] = "SyncResetV1"; + SyncEntityType["SyncCompleteV1"] = "SyncCompleteV1"; +})(SyncEntityType || (SyncEntityType = {})); +export var SyncRequestType; +(function (SyncRequestType) { + SyncRequestType["AlbumsV1"] = "AlbumsV1"; + SyncRequestType["AlbumsV2"] = "AlbumsV2"; + SyncRequestType["AlbumUsersV1"] = "AlbumUsersV1"; + SyncRequestType["AlbumToAssetsV1"] = "AlbumToAssetsV1"; + SyncRequestType["AlbumAssetsV1"] = "AlbumAssetsV1"; + SyncRequestType["AlbumAssetsV2"] = "AlbumAssetsV2"; + SyncRequestType["AlbumAssetExifsV1"] = "AlbumAssetExifsV1"; + SyncRequestType["AssetsV1"] = "AssetsV1"; + SyncRequestType["AssetsV2"] = "AssetsV2"; + SyncRequestType["AssetExifsV1"] = "AssetExifsV1"; + SyncRequestType["AssetEditsV1"] = "AssetEditsV1"; + SyncRequestType["AssetMetadataV1"] = "AssetMetadataV1"; + SyncRequestType["AuthUsersV1"] = "AuthUsersV1"; + SyncRequestType["MemoriesV1"] = "MemoriesV1"; + SyncRequestType["MemoryToAssetsV1"] = "MemoryToAssetsV1"; + SyncRequestType["PartnersV1"] = "PartnersV1"; + SyncRequestType["PartnerAssetsV1"] = "PartnerAssetsV1"; + SyncRequestType["PartnerAssetsV2"] = "PartnerAssetsV2"; + SyncRequestType["PartnerAssetExifsV1"] = "PartnerAssetExifsV1"; + SyncRequestType["PartnerStacksV1"] = "PartnerStacksV1"; + SyncRequestType["StacksV1"] = "StacksV1"; + SyncRequestType["UsersV1"] = "UsersV1"; + SyncRequestType["PeopleV1"] = "PeopleV1"; + SyncRequestType["AssetFacesV1"] = "AssetFacesV1"; + SyncRequestType["AssetFacesV2"] = "AssetFacesV2"; + SyncRequestType["UserMetadataV1"] = "UserMetadataV1"; +})(SyncRequestType || (SyncRequestType = {})); +export var TranscodeHWAccel; +(function (TranscodeHWAccel) { + TranscodeHWAccel["Nvenc"] = "nvenc"; + TranscodeHWAccel["Qsv"] = "qsv"; + TranscodeHWAccel["Vaapi"] = "vaapi"; + TranscodeHWAccel["Rkmpp"] = "rkmpp"; + TranscodeHWAccel["Disabled"] = "disabled"; +})(TranscodeHWAccel || (TranscodeHWAccel = {})); +export var AudioCodec; +(function (AudioCodec) { + AudioCodec["Mp3"] = "mp3"; + AudioCodec["Aac"] = "aac"; + AudioCodec["Libopus"] = "libopus"; + AudioCodec["Opus"] = "opus"; + AudioCodec["PcmS16Le"] = "pcm_s16le"; +})(AudioCodec || (AudioCodec = {})); +export var VideoContainer; +(function (VideoContainer) { + VideoContainer["Mov"] = "mov"; + VideoContainer["Mp4"] = "mp4"; + VideoContainer["Ogg"] = "ogg"; + VideoContainer["Webm"] = "webm"; +})(VideoContainer || (VideoContainer = {})); +export var VideoCodec; +(function (VideoCodec) { + VideoCodec["H264"] = "h264"; + VideoCodec["Hevc"] = "hevc"; + VideoCodec["Vp9"] = "vp9"; + VideoCodec["Av1"] = "av1"; +})(VideoCodec || (VideoCodec = {})); +export var CQMode; +(function (CQMode) { + CQMode["Auto"] = "auto"; + CQMode["Cqp"] = "cqp"; + CQMode["Icq"] = "icq"; +})(CQMode || (CQMode = {})); +export var ToneMapping; +(function (ToneMapping) { + ToneMapping["Hable"] = "hable"; + ToneMapping["Mobius"] = "mobius"; + ToneMapping["Reinhard"] = "reinhard"; + ToneMapping["Disabled"] = "disabled"; +})(ToneMapping || (ToneMapping = {})); +export var TranscodePolicy; +(function (TranscodePolicy) { + TranscodePolicy["All"] = "all"; + TranscodePolicy["Optimal"] = "optimal"; + TranscodePolicy["Bitrate"] = "bitrate"; + TranscodePolicy["Required"] = "required"; + TranscodePolicy["Disabled"] = "disabled"; +})(TranscodePolicy || (TranscodePolicy = {})); +export var Colorspace; +(function (Colorspace) { + Colorspace["Srgb"] = "srgb"; + Colorspace["P3"] = "p3"; +})(Colorspace || (Colorspace = {})); +export var ImageFormat; +(function (ImageFormat) { + ImageFormat["Jpeg"] = "jpeg"; + ImageFormat["Webp"] = "webp"; +})(ImageFormat || (ImageFormat = {})); +export var LogLevel; +(function (LogLevel) { + LogLevel["Verbose"] = "verbose"; + LogLevel["Debug"] = "debug"; + LogLevel["Log"] = "log"; + LogLevel["Warn"] = "warn"; + LogLevel["Error"] = "error"; + LogLevel["Fatal"] = "fatal"; +})(LogLevel || (LogLevel = {})); +export var OAuthTokenEndpointAuthMethod; +(function (OAuthTokenEndpointAuthMethod) { + OAuthTokenEndpointAuthMethod["ClientSecretPost"] = "client_secret_post"; + OAuthTokenEndpointAuthMethod["ClientSecretBasic"] = "client_secret_basic"; +})(OAuthTokenEndpointAuthMethod || (OAuthTokenEndpointAuthMethod = {})); +export var UserMetadataKey; +(function (UserMetadataKey) { + UserMetadataKey["Preferences"] = "preferences"; + UserMetadataKey["License"] = "license"; + UserMetadataKey["Onboarding"] = "onboarding"; +})(UserMetadataKey || (UserMetadataKey = {})); diff --git a/open-api/typescript-sdk/build/fetch-errors.d.ts b/open-api/typescript-sdk/build/fetch-errors.d.ts new file mode 100644 index 0000000000..e163e675f1 --- /dev/null +++ b/open-api/typescript-sdk/build/fetch-errors.d.ts @@ -0,0 +1,16 @@ +import { HttpError } from '@oazapfts/runtime'; +export interface ApiValidationError { + code: string; + path: (string | number)[]; + message: string; +} +export interface ApiExceptionResponse { + message: string; + error?: string; + statusCode: number; + errors?: ApiValidationError[]; +} +export interface ApiHttpError extends HttpError { + data: ApiExceptionResponse; +} +export declare function isHttpError(error: unknown): error is ApiHttpError; diff --git a/open-api/typescript-sdk/build/fetch-errors.js b/open-api/typescript-sdk/build/fetch-errors.js new file mode 100644 index 0000000000..a71dae67e6 --- /dev/null +++ b/open-api/typescript-sdk/build/fetch-errors.js @@ -0,0 +1,4 @@ +import { HttpError } from '@oazapfts/runtime'; +export function isHttpError(error) { + return error instanceof HttpError; +} diff --git a/open-api/typescript-sdk/build/index.d.ts b/open-api/typescript-sdk/build/index.d.ts new file mode 100644 index 0000000000..c0ded6de7a --- /dev/null +++ b/open-api/typescript-sdk/build/index.d.ts @@ -0,0 +1,18 @@ +export * from './fetch-client.js'; +export * from './fetch-errors.js'; +export interface InitOptions { + baseUrl: string; + apiKey: string; + headers?: Record; +} +export declare const init: ({ baseUrl, apiKey, headers }: InitOptions) => void; +export declare const getBaseUrl: () => string; +export declare const setBaseUrl: (baseUrl: string) => void; +export declare const setApiKey: (apiKey: string) => void; +export declare const setHeader: (key: string, value: string) => void; +export declare const setHeaders: (headers: Record) => void; +export declare const getAssetOriginalPath: (id: string) => string; +export declare const getAssetThumbnailPath: (id: string) => string; +export declare const getAssetPlaybackPath: (id: string) => string; +export declare const getUserProfileImagePath: (userId: string) => string; +export declare const getPeopleThumbnailPath: (personId: string) => string; diff --git a/open-api/typescript-sdk/build/index.js b/open-api/typescript-sdk/build/index.js new file mode 100644 index 0000000000..c3a7a77794 --- /dev/null +++ b/open-api/typescript-sdk/build/index.js @@ -0,0 +1,40 @@ +import { defaults } from './fetch-client.js'; +export * from './fetch-client.js'; +export * from './fetch-errors.js'; +export const init = ({ baseUrl, apiKey, headers }) => { + setBaseUrl(baseUrl); + setApiKey(apiKey); + if (headers) { + setHeaders(headers); + } +}; +export const getBaseUrl = () => defaults.baseUrl; +export const setBaseUrl = (baseUrl) => { + defaults.baseUrl = baseUrl; +}; +export const setApiKey = (apiKey) => { + defaults.headers = defaults.headers || {}; + defaults.headers['x-api-key'] = apiKey; +}; +export const setHeader = (key, value) => { + assertNoApiKey(key); + defaults.headers = defaults.headers || {}; + defaults.headers[key] = value; +}; +export const setHeaders = (headers) => { + defaults.headers = defaults.headers || {}; + for (const [key, value] of Object.entries(headers)) { + assertNoApiKey(key); + defaults.headers[key] = value; + } +}; +const assertNoApiKey = (headerKey) => { + if (headerKey.toLowerCase() === 'x-api-key') { + throw new Error('The API key header can only be set using setApiKey().'); + } +}; +export const getAssetOriginalPath = (id) => `/assets/${id}/original`; +export const getAssetThumbnailPath = (id) => `/assets/${id}/thumbnail`; +export const getAssetPlaybackPath = (id) => `/assets/${id}/video/playback`; +export const getUserProfileImagePath = (userId) => `/users/${userId}/profile-image`; +export const getPeopleThumbnailPath = (personId) => `/people/${personId}/thumbnail`; diff --git a/package.json b/package.json index abe9e6b44f..4d361b329d 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,16 @@ "version": "2.7.5", "description": "Monorepo for Immich", "private": true, + "scripts": { + "format": "prettier --cache --check i18n/", + "format:fix": "prettier --cache --write --list-different i18n" + }, "packageManager": "pnpm@10.33.1+sha512.05ba3c1d5d1c18f68df06470d74055e62d41fc110a0c660db1b2dfb2785327f04cf0f68345d4609bc52089e7fa0343c31593b2f9594e2c5d5da426230acc9820", "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 100% rename from cli/mise.toml rename to packages/cli/mise.toml diff --git a/cli/package.json b/packages/cli/package.json similarity index 98% rename from cli/package.json rename to packages/cli/package.json index 8035639a2a..534b213f2b 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": { @@ -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" }, 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 100% rename from cli/src/commands/asset.ts rename to packages/cli/src/commands/asset.ts 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 94% rename from e2e-auth-server/package.json rename to packages/e2e-auth-server/package.json index 8aa24c04ec..0e1928d34c 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": { 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/plugins/.gitignore b/packages/plugins/.gitignore similarity index 100% rename from plugins/.gitignore rename to packages/plugins/.gitignore diff --git a/plugins/LICENSE b/packages/plugins/LICENSE similarity index 100% rename from plugins/LICENSE rename to packages/plugins/LICENSE diff --git a/plugins/esbuild.js b/packages/plugins/esbuild.js similarity index 100% rename from plugins/esbuild.js rename to packages/plugins/esbuild.js diff --git a/plugins/manifest.json b/packages/plugins/manifest.json similarity index 100% rename from plugins/manifest.json rename to packages/plugins/manifest.json diff --git a/plugins/mise.toml b/packages/plugins/mise.toml similarity index 100% rename from plugins/mise.toml rename to packages/plugins/mise.toml diff --git a/plugins/package-lock.json b/packages/plugins/package-lock.json similarity index 100% rename from plugins/package-lock.json rename to packages/plugins/package-lock.json diff --git a/plugins/package.json b/packages/plugins/package.json similarity index 100% rename from plugins/package.json rename to packages/plugins/package.json diff --git a/plugins/src/index.d.ts b/packages/plugins/src/index.d.ts similarity index 100% rename from plugins/src/index.d.ts rename to packages/plugins/src/index.d.ts diff --git a/plugins/src/index.ts b/packages/plugins/src/index.ts similarity index 100% rename from plugins/src/index.ts rename to packages/plugins/src/index.ts diff --git a/plugins/tsconfig.json b/packages/plugins/tsconfig.json similarity index 100% rename from plugins/tsconfig.json rename to packages/plugins/tsconfig.json 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 93% rename from open-api/typescript-sdk/package.json rename to packages/sdk/package.json index eca4bab946..926a30ba88 100644 --- a/open-api/typescript-sdk/package.json +++ b/packages/sdk/package.json @@ -5,7 +5,7 @@ "repository": { "type": "git", "url": "git+https://github.com/immich-app/immich.git", - "directory": "open-api/typescript-sdk" + "directory": "packages/sdk" }, "type": "module", "main": "./build/index.js", diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/packages/sdk/src/fetch-client.ts similarity index 99% rename from open-api/typescript-sdk/src/fetch-client.ts rename to packages/sdk/src/fetch-client.ts index 6b3e9fe26f..6157154bec 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/packages/sdk/src/fetch-client.ts @@ -787,29 +787,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 +874,7 @@ export type AssetResponseDto = { owner?: UserResponseDto; /** Owner user ID */ ownerId: string; - people?: PersonWithFacesResponseDto[]; + people?: PersonResponseDto[]; /** Is resized */ resized?: boolean; stack?: (AssetStackResponseDto) | null; @@ -900,7 +882,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 +1117,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; @@ -2673,6 +2636,8 @@ export type TimeBucketAssetResponseDto = { city: (string | null)[]; /** Array of country names extracted from EXIF GPS data */ 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 */ @@ -3040,6 +3005,8 @@ export type SyncAssetMetadataV1 = { export type SyncAssetV1 = { /** Checksum */ checksum: string; + /** Uploaded to Immich at */ + createdAt: string | null; /** Deleted at */ deletedAt: string | null; /** Duration */ @@ -3078,6 +3045,8 @@ export type SyncAssetV1 = { export type SyncAssetV2 = { /** Checksum */ checksum: string; + /** Uploaded to Immich at */ + createdAt: string | null; /** Deleted at */ deletedAt: string | null; /** Duration */ @@ -6326,13 +6295,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; @@ -6353,6 +6323,7 @@ export function getTimeBucket({ albumId, bbox, isFavorite, isTrashed, key, order isTrashed, key, order, + orderBy, personId, slug, tagId, @@ -6369,13 +6340,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; @@ -6395,6 +6367,7 @@ export function getTimeBuckets({ albumId, bbox, isFavorite, isTrashed, key, orde isTrashed, key, order, + orderBy, personId, slug, tagId, @@ -6971,11 +6944,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", @@ -6997,6 +6965,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", @@ -7241,7 +7214,6 @@ export enum TranscodeHWAccel { export enum AudioCodec { Mp3 = "mp3", Aac = "aac", - Libopus = "libopus", Opus = "opus", PcmS16Le = "pcm_s16le" } @@ -7295,6 +7267,10 @@ export enum OAuthTokenEndpointAuthMethod { ClientSecretPost = "client_secret_post", ClientSecretBasic = "client_secret_basic" } +export enum AssetOrderBy { + TakenAt = "takenAt", + CreatedAt = "createdAt" +} 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/pnpm-lock.yaml b/pnpm-lock.yaml index d831c2d195..b405a1090d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: injectWorkspacePackages: true overrides: - canvas: 2.11.2 + canvas: 3.2.3 sharp: ^0.34.5 webpackbar: ^7.0.0 @@ -16,7 +16,14 @@ 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,103 +31,6 @@ 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': @@ -201,13 +111,13 @@ importers: 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 @@ -246,7 +156,7 @@ importers: version: 64.0.0(eslint@10.2.1(jiti@2.6.1)) exiftool-vendored: specifier: ^35.0.0 - version: 35.19.0 + version: 35.20.0 globals: specifier: ^17.0.0 version: 17.5.0 @@ -288,9 +198,106 @@ importers: 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)) 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.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@3.2.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)) - 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.2.1(jiti@2.6.1)) + '@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.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@3.2.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)) + vitest-fetch-mock: + specifier: ^0.4.0 + version: 0.4.5(vitest@4.1.5) + yaml: + specifier: ^2.3.1 + version: 2.8.3 + + packages/e2e-auth-server: devDependencies: '@types/oidc-provider': specifier: ^9.0.0 @@ -305,16 +312,19 @@ importers: specifier: ^4.20.6 version: 4.21.0 - i18n: + packages/plugins: 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 + esbuild: + specifier: ^0.28.0 + version: 0.28.0 + typescript: + specifier: ^6.0.0 + version: 6.0.3 - open-api/typescript-sdk: + packages/sdk: dependencies: '@oazapfts/runtime': specifier: ^1.0.2 @@ -327,18 +337,6 @@ importers: 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 - typescript: - specifier: ^6.0.0 - version: 6.0.3 - server: dependencies: '@extism/extism': @@ -376,10 +374,10 @@ importers: 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.217.0 + version: 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-http': specifier: ^0.215.0 version: 0.215.0(@opentelemetry/api@1.9.1) @@ -394,13 +392,13 @@ importers: version: 0.67.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.217.0 + version: 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': specifier: ^1.34.0 version: 1.40.0 @@ -480,11 +478,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 @@ -505,7 +503,7 @@ importers: 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) 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.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(kysely@0.28.17)(reflect-metadata@0.2.2) nestjs-otel: specifier: ^8.0.0 version: 8.0.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) @@ -665,7 +663,7 @@ importers: 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)) + 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@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) eslint: specifier: ^10.0.0 version: 10.2.1(jiti@2.6.1) @@ -719,7 +717,7 @@ importers: 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)) 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.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) web: dependencies: @@ -731,10 +729,10 @@ importers: 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.77.0 + version: 0.77.3(@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) '@mapbox/mapbox-gl-rtl-text': specifier: 0.4.0 version: 0.4.0 @@ -975,7 +973,7 @@ importers: 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) 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.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@3.2.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)) packages: @@ -3029,14 +3027,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.77.3': + resolution: {integrity: sha512-h3jrYE3JyGDOwXF7A4tVUHenP0L7TsDV22FyFInBTdwlWjjXoknwE1HWeTvvLxLeMuO5SHCZ9Q2D2al84xVjNw==} peerDependencies: + '@sveltejs/kit': ^2.13.0 svelte: ^5.0.0 '@inquirer/ansi@1.0.2': @@ -3693,6 +3687,10 @@ packages: resolution: {integrity: sha512-xrFlqhdhUyO8wSRn6DjE0145/HPWSJ5Nm0C7vWua6TdL/FSEAZvEyvdsa9CRXuxo9ebb7j/NEPhEcO62IJ0qUA==} engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.217.0': + resolution: {integrity: sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -3701,14 +3699,14 @@ packages: 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.217.0': + resolution: {integrity: sha512-xCtrYOhBqdy6ZOMfe0Oa73ZKF+2LMhoOv4L5vmwAHVvOXUg+V3fvKuEIr9ZyD0Ow+vxllEjWO6PV1wd0DOtyvw==} 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' @@ -3719,68 +3717,74 @@ packages: 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/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.217.0': + resolution: {integrity: sha512-vC5S0Dc+noxD86CVtNu1+awCHPA5Kewi1Sg23ps+9lh4YifwsKXh3pe4XTNEKtUJiAcjpJ5dqStGakLbrSE+YQ==} 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.217.0': + resolution: {integrity: sha512-KfLAdt1uilVE+3FxbgVnp2ZrzqbIawzcesnRoi+Kh9ckB5Ld5D8btUgoBvwTbdmuNx1j6b132Wsh72azq+pPNQ==} 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.217.0': + resolution: {integrity: sha512-Se0GG/ZO24mQTlQj7zprR4pNI0nKe4lPDPBsuJmi6508b9TlZEuUd3EfyuHk6oJxzL7fGyDFYAbxNigQvRP2ZQ==} 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.217.0': + resolution: {integrity: sha512-0GpJKnCoVaVA1rKBMVPHziznfOQlXgH72S9ktjBAF1AnAVPzX7vVEBGrhwiSxxHDAiefXk+J8znApsMb/K6Z3w==} 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.217.0': + resolution: {integrity: sha512-1zkMzzhiNJdVmLxuwkltqWGw4fOOam47bqRxmuQNjyKJe/9NmY5cIrZ4kiQV7sVGxoOgT0ZvGUfLcjvtpC/b9Q==} 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.217.0': + resolution: {integrity: sha512-nfxt/KxVGFkjkO/M+58y1ugHu/dwPtxG4eYq0KApcQ7xk5CHzhdn+IuLZfDSvNDrJ3Uy5q++Fj/wbK7i8yryfQ==} 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.217.0': + resolution: {integrity: sha512-U9MCXxJu0sBCh5aEkylYRR4xVIL8D1CW6dGwvYXbfFr0qveSorfD0XJchCAWoW6QfAAIcY/yxjf4Dj8OgkHBPw==} 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.217.0': + resolution: {integrity: sha512-fPZs2fw7veLH3pEKu8vSepUa2fQpAE2P7al6qU10aH9GrEJJ8YaPgsd5xON7by5rbcEVS71FOU2aWyK6nzB7VQ==} 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.217.0': + resolution: {integrity: sha512-38YQoqtYjglz2GV94LGUN/djLvxtvGIQO68o6qAFPVshjmwSdX1F2i0c7vn3lEl1L5B/YqjB/bgKXaVx7KO+RQ==} 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.217.0': + resolution: {integrity: sha512-nPV8gKHUiSuTZpQcnZU3/pBlK7crSyEGpZuh5MtWySB0vv6NNG0QvvfKitQt+Fc2Mc6qfyU54KlZcurwoTbrVg==} 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 @@ -3821,32 +3825,38 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-exporter-base@0.215.0': - resolution: {integrity: sha512-lHrfbmeLSmesGSkkHiqDwOzfaEMSWXdc7q6UoLfbW8byONCb+bE/zkAr0kapN4US1baT/2nbpNT7Cn9XoB96Vg==} + '@opentelemetry/instrumentation@0.217.0': + resolution: {integrity: sha512-24ucQMjz7Y34Kw3trbxL2ZrssbtgWnR+Clpaa+YdeWuuyH3Cvk23Q03PcQvqiZrDvt8AmQmjgg9v6Y9PHoxG7w==} 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-exporter-base@0.217.0': + resolution: {integrity: sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ==} 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-grpc-exporter-base@0.217.0': + resolution: {integrity: sha512-7RTAdZuOsCDnsyqTCG4+bDzrfnsWdzkRs7z0AVi/V3tEQx0oKeyc+OuRWYxnRsmaJXgxcmB8vb/lfxn58Dj6Ag==} 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/otlp-transformer@0.217.0': + resolution: {integrity: sha512-MKK8UHKFUOGAvbZRWh90MhwHG+Fxm6OROBdjKPCF+HQobjuJ/Kuf8Chs8CR45X1aqotxrMj7OxTdsXe8sXuGVA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@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,38 +3865,38 @@ 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.217.0': + resolution: {integrity: sha512-BB+PcHItcZDL63dPMW+mJvwN9rk37wuIDjRxbVlg6pPDvDR/7GL7UJHbGsllgoggOoTimsKgENaWPoGch/oE1A==} 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.217.0': + resolution: {integrity: sha512-K/60pSv42+NQiZKy1pAH18nYDkxltsDV4O3SJ233J0E9raU1ksyL9gsKuS8p30bYBb4AMPCfDuutHQaHYpcv0Q==} 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' @@ -6172,9 +6182,9 @@ packages: caniuse-lite@1.0.30001790: resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} - 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==} @@ -6954,10 +6964,6 @@ packages: 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'} - decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -7544,32 +7550,23 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - exiftool-vendored.exe@13.57.0: - resolution: {integrity: sha512-9ENCWzUiFy6F/O4jSX50ygSGrTOtvoqJFWE0zAOl7VL/EFooLWNF0LkaNSox0ibbIsQz5rWSKi0TPlEbF4qBIw==} - os: [win32] - 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==} - os: ['!win32'] - hasBin: true - exiftool-vendored.pl@13.58.0: resolution: {integrity: sha512-+Z2xhZrYLMu/anO/s14AaS/K5HMJ5Cw9C3KefIeYNpkZRN4RRBJHm7R34yjj9Pv+elqYRZrQV9NcqvkBLn/68w==} os: ['!win32'] hasBin: true - exiftool-vendored@35.19.0: - resolution: {integrity: sha512-c7/W5VA0f2XKllRgFSBOoTSRMlRVn13QT/TudOS3RN64VRfcj51vQQcRwtmdwYuRrzRf7lsh6gCQpmm3vr8Lww==} - engines: {node: '>=20.0.0'} - 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'} @@ -7777,9 +7774,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==} @@ -7872,6 +7866,9 @@ packages: get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + 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==} @@ -8597,7 +8594,7 @@ packages: resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} engines: {node: '>=18'} peerDependencies: - canvas: 2.11.2 + canvas: 3.2.3 peerDependenciesMeta: canvas: optional: true @@ -8723,8 +8720,8 @@ 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: @@ -9030,11 +9027,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'} @@ -9319,10 +9311,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'} @@ -9490,6 +9478,9 @@ packages: 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==} @@ -9563,6 +9554,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==} @@ -10510,6 +10505,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'} @@ -11187,8 +11188,8 @@ 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==} @@ -11897,6 +11898,9 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} @@ -15399,23 +15403,16 @@ 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) + kysely: 0.28.17 + kysely-postgres-js: 3.0.0(kysely@0.28.17)(postgres@3.4.9) pg-connection-string: 2.12.0 postgres: 3.4.9 - '@immich/svelte-markdown-preprocess@0.4.1(svelte@5.55.2)': + '@immich/ui@0.77.3(@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: - 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 '@mdi/js': 7.4.47 + '@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)) 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) luxon: 3.7.2 simple-icons: 16.17.0 @@ -15424,8 +15421,6 @@ snapshots: 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' '@inquirer/ansi@1.0.2': {} @@ -15819,22 +15814,6 @@ snapshots: '@mapbox/mapbox-gl-rtl-text@0.4.0': {} - '@mapbox/node-pre-gyp@1.0.11': - dependencies: - detect-libc: 2.1.2 - https-proxy-agent: 5.0.1 - make-dir: 3.1.0 - node-fetch: 2.7.0 - 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 @@ -16184,17 +16163,21 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs@0.217.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.217.0(@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) yaml: 2.8.3 - '@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 @@ -16203,115 +16186,120 @@ snapshots: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/exporter-logs-otlp-grpc@0.215.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/exporter-logs-otlp-http@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/core@2.7.1(@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/exporter-logs-otlp-proto@0.215.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/exporter-metrics-otlp-grpc@0.215.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/exporter-metrics-otlp-http@0.215.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/exporter-metrics-otlp-proto@0.215.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/exporter-prometheus@0.215.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/exporter-trace-otlp-grpc@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-grpc@0.217.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.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-http@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-http@0.217.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/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-proto@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-proto@0.217.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/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@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-metrics-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.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.217.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-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.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.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.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.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.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@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-metrics': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/host-metrics@0.38.3(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-trace-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.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.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.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.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.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.1(@opentelemetry/api@1.9.1)': + dependencies: + '@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-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/host-metrics@0.38.3(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 systeminformation: 5.31.5 '@opentelemetry/instrumentation-http@0.215.0(@opentelemetry/api@1.9.1)': @@ -16344,7 +16332,7 @@ snapshots: '@opentelemetry/instrumentation-pg@0.67.0(@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/instrumentation': 0.215.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.1) @@ -16362,114 +16350,123 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/otlp-exporter-base@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation@0.217.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/api-logs': 0.217.0 + import-in-the-middle: 3.0.0 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color - '@opentelemetry/otlp-grpc-exporter-base@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-exporter-base@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-grpc-exporter-base@0.217.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.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-transformer@0.217.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) + '@opentelemetry/api-logs': 0.217.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.217.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) protobufjs: 8.0.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/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/sdk-logs@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-logs@0.217.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/api-logs': 0.217.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.40.0 - '@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.217.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/api-logs': 0.217.0 + '@opentelemetry/configuration': 0.217.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.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-prometheus': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-zipkin': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.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.217.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.40.0 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/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@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/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': {} @@ -17250,7 +17247,7 @@ snapshots: svelte: 5.55.2 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)) + 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@3.2.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)) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': dependencies: @@ -17966,7 +17963,7 @@ snapshots: '@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))': + '@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@3.2.3))(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 @@ -17981,7 +17978,7 @@ snapshots: 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) + 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@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -17997,7 +17994,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - 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.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@3.2.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)) '@vitest/expect@3.2.4': dependencies: @@ -18764,24 +18761,10 @@ snapshots: caniuse-lite@1.0.30001790: {} - 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: {} @@ -19584,11 +19567,6 @@ snapshots: 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 @@ -20317,27 +20295,11 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - exiftool-vendored.exe@13.57.0: - optional: true - exiftool-vendored.exe@13.58.0: optional: true - exiftool-vendored.pl@13.57.0: {} - exiftool-vendored.pl@13.58.0: {} - exiftool-vendored@35.19.0: - dependencies: - '@photostructure/tz-lookup': 11.5.0 - '@types/luxon': 3.7.1 - batch-cluster: 17.3.1 - exiftool-vendored.pl: 13.57.0 - he: 1.2.0 - luxon: 3.7.2 - optionalDependencies: - exiftool-vendored.exe: 13.57.0 - exiftool-vendored@35.20.0: dependencies: '@photostructure/tz-lookup': 11.5.0 @@ -20349,6 +20311,9 @@ snapshots: optionalDependencies: exiftool-vendored.exe: 13.58.0 + expand-template@2.0.3: + optional: true + expect-type@1.3.0: {} exponential-backoff@3.1.3: {} @@ -20434,11 +20399,10 @@ snapshots: fabric@7.3.1: 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 @@ -20633,10 +20597,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: @@ -20734,6 +20694,9 @@ snapshots: 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: {} @@ -21555,7 +21518,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 @@ -21578,37 +21541,7 @@ snapshots: ws: 8.20.0 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 @@ -21730,13 +21663,13 @@ snapshots: type-is: 2.0.1 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: {} + kysely@0.28.17: {} langium@3.3.1: dependencies: @@ -22012,8 +21945,6 @@ snapshots: marked@16.4.2: {} - marked@17.0.6: {} - math-intrinsics@1.1.0: {} mdast-util-directive@3.1.0: @@ -22613,9 +22544,6 @@ snapshots: mimic-function@5.0.1: {} - mimic-response@2.1.0: - optional: true - mimic-response@3.1.0: {} mimic-response@4.0.0: {} @@ -22769,6 +22697,9 @@ snapshots: nanoid@5.1.9: {} + napi-build-utils@2.0.0: + optional: true + natural-compare-lite@1.4.0: {} natural-compare@1.4.0: {} @@ -22809,11 +22740,11 @@ snapshots: 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.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(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 + kysely: 0.28.17 reflect-metadata: 0.2.2 tslib: 2.8.1 @@ -22821,8 +22752,8 @@ snapshots: 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) - '@opentelemetry/api': 1.9.0 - '@opentelemetry/host-metrics': 0.38.3(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@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): @@ -22841,6 +22772,11 @@ snapshots: lower-case: 2.0.2 tslib: 2.8.1 + node-abi@3.92.0: + dependencies: + semver: 7.7.4 + optional: true + node-abort-controller@3.1.1: {} node-addon-api@4.3.0: {} @@ -22861,11 +22797,6 @@ snapshots: emojilib: 2.4.0 skin-tone: 2.0.0 - 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 @@ -23832,6 +23763,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: @@ -24755,9 +24702,9 @@ 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 @@ -25599,6 +25546,11 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + optional: true + tweetnacl@0.14.5: {} type-check@0.4.0: @@ -26008,9 +25960,9 @@ snapshots: vitest-fetch-mock@0.4.5(vitest@4.1.5): 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.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@3.2.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)) - 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.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 @@ -26039,7 +25991,7 @@ snapshots: '@types/debug': 4.1.12 '@types/node': 24.12.2 happy-dom: 20.9.0 - jsdom: 26.1.0(canvas@2.11.2) + jsdom: 26.1.0(canvas@3.2.3) transitivePeerDependencies: - jiti - less @@ -26054,7 +26006,7 @@ 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.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@3.2.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)): 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)) @@ -26081,42 +26033,11 @@ snapshots: '@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(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)): + 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@3.2.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)): 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)) @@ -26143,7 +26064,7 @@ snapshots: '@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) + jsdom: 26.1.0(canvas@3.2.3) transitivePeerDependencies: - msw diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d9ac5ddcee..57aeb9c7bf 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,10 +1,8 @@ packages: - - cli + - packages/** - docs - e2e - - e2e-auth-server - i18n - - open-api/typescript-sdk - server - plugins - web @@ -30,7 +28,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 diff --git a/server/Dockerfile b/server/Dockerfile index 50e8b9714c..d35d029958 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:202605051129@sha256:d07d8fcdb7e9f3ac22a811e87761ebf341ed0bb91956b89097540c2ed3fb9ca3 AS builder ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ CI=1 \ COREPACK_HOME=/tmp \ @@ -29,7 +29,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 +40,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 \ @@ -58,13 +57,13 @@ ARG TARGETPLATFORM COPY --from=ghcr.io/jdx/mise:2026.3.12@sha256:0210678cbf58413806531a27adb2c7daf1c37238e56e8f7ea381d73521571775 /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 +COPY ./packages/plugins/mise.toml ./packages/plugins/ +ENV MISE_TRUSTED_CONFIG_PATHS=/usr/src/app/packages/plugins/mise.toml ENV MISE_DATA_DIR=/buildcache/mise RUN --mount=type=cache,id=mise-tools-${TARGETPLATFORM},target=/buildcache/mise \ - mise install --cd plugins + mise install --cd packages/plugins -COPY ./plugins ./plugins/ +COPY ./packages/plugins ./packages/plugins/ # Build plugins RUN --mount=type=cache,id=pnpm-plugins,target=/buildcache/pnpm-store \ --mount=type=bind,source=package.json,target=package.json \ @@ -72,9 +71,9 @@ RUN --mount=type=cache,id=pnpm-plugins,target=/buildcache/pnpm-store \ --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 + cd packages/plugins && mise run build -FROM ghcr.io/immich-app/base-server-prod:202604141125@sha256:3b05219afcda09cebfb8513743fc92cec1a3ae262249bfe0de6f90da21326991 +FROM ghcr.io/immich-app/base-server-prod:202605051129@sha256:50f7ffe4ed31e360c90c4905bd5f6658f2a121297544e3fe9368e338b3f76bcd WORKDIR /usr/src/app ENV NODE_ENV=production \ @@ -84,8 +83,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 /usr/src/app/packages/plugins/dist /build/corePlugin/dist +COPY --from=plugins /usr/src/app/packages/plugins/manifest.json /build/corePlugin/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..0b2cc0beec 100644 --- a/server/Dockerfile.dev +++ b/server/Dockerfile.dev @@ -1,5 +1,5 @@ # dev build -FROM ghcr.io/immich-app/base-server-dev:202604141125@sha256:9338c216fb0fef4172cf53cd8e4ff607c6635d576dcc1366151f13d69bbb45ef AS dev +FROM ghcr.io/immich-app/base-server-dev:202605051129@sha256:d07d8fcdb7e9f3ac22a811e87761ebf341ed0bb91956b89097540c2ed3fb9ca3 AS dev ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ CI=1 \ diff --git a/server/mise.toml b/server/mise.toml index 95230b6d04..b8236c60c6 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] diff --git a/server/package.json b/server/package.json index 904fda4637..904cb3c6c8 100644 --- a/server/package.json +++ b/server/package.json @@ -33,8 +33,6 @@ "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": { @@ -50,14 +48,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/exporter-prometheus": "^0.217.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/resources": "^2.0.1", "@opentelemetry/sdk-metrics": "^2.0.1", - "@opentelemetry/sdk-node": "^0.215.0", + "@opentelemetry/sdk-node": "^0.217.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@react-email/components": "^1.0.0", "@react-email/render": "^2.0.0", @@ -84,7 +82,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", diff --git a/server/src/config.ts b/server/src/config.ts index 476b0eb160..999e1e45bc 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -223,7 +223,7 @@ export const defaults = Object.freeze({ transcode: TranscodePolicy.Required, tonemap: ToneMapping.Hable, accel: TranscodeHardwareAcceleration.Disabled, - accelDecode: false, + accelDecode: true, }, job: { [QueueName.BackgroundTask]: { concurrency: 5 }, diff --git a/server/src/constants.ts b/server/src/constants.ts index c771c1a995..9f8cdbefdb 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -199,7 +199,6 @@ 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', }; diff --git a/server/src/database.ts b/server/src/database.ts index 3f1eb60a07..934854e5c4 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -382,6 +382,7 @@ export const columns = { 'asset.checksum', 'asset.fileCreatedAt', 'asset.fileModifiedAt', + 'asset.createdAt', 'asset.localDateTime', 'asset.type', 'asset.deletedAt', @@ -404,6 +405,7 @@ export const columns = { 'asset.fileCreatedAt', 'asset.fileModifiedAt', 'asset.localDateTime', + 'asset.createdAt', 'asset.type', 'asset.deletedAt', 'asset.visibility', 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..6d72fd971a 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,8 +16,7 @@ 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 { mimeTypes } from 'src/utils/mime-types'; @@ -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 }) => { @@ -230,8 +209,6 @@ 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), @@ -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/person.dto.ts b/server/src/dtos/person.dto.ts index 8cbbe6df78..f39cfd1c88 100644 --- a/server/src/dtos/person.dto.ts +++ b/server/src/dtos/person.dto.ts @@ -56,7 +56,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 +91,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 +101,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) {} @@ -193,11 +182,11 @@ export function mapPerson(person: MaybeDehydrated): PersonResponseDto { }; } -export function mapFacesWithoutPerson( +function mapFacesWithoutPerson( face: MaybeDehydrated>, edits?: AssetEditActionItem[], assetDimensions?: ImageDimensions, -): AssetFaceWithoutPersonResponseDto { +) { return { id: face.id, ...transformFaceBoundingBox( diff --git a/server/src/dtos/sync.dto.ts b/server/src/dtos/sync.dto.ts index c7333f6dea..35ef874dfa 100644 --- a/server/src/dtos/sync.dto.ts +++ b/server/src/dtos/sync.dto.ts @@ -75,6 +75,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 +100,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..94c1aa36b0 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'), diff --git a/server/src/dtos/time-bucket.dto.ts b/server/src/dtos/time-bucket.dto.ts index 88a352e20e..f63860a1df 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()) diff --git a/server/src/enum.ts b/server/src/enum.ts index 74f1142fec..636db8adab 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -74,6 +74,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', @@ -454,8 +461,6 @@ export enum VideoSegmentCodec { export enum AudioCodec { Mp3 = 'mp3', Aac = 'aac', - /** @deprecated Use `Opus` instead */ - Libopus = 'libopus', Opus = 'opus', PcmS16le = 'pcm_s16le', } diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index ebc2de90e1..4d90bbf0d5 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -382,6 +382,7 @@ 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", @@ -442,6 +443,7 @@ 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", @@ -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/sync.repository.sql b/server/src/queries/sync.repository.sql index 8ac7fcbc5a..1404a24ba7 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", @@ -737,6 +741,7 @@ select "asset"."fileCreatedAt", "asset"."fileModifiedAt", "asset"."localDateTime", + "asset"."createdAt", "asset"."type", "asset"."deletedAt", "asset"."visibility", @@ -789,6 +794,7 @@ select "asset"."fileCreatedAt", "asset"."fileModifiedAt", "asset"."localDateTime", + "asset"."createdAt", "asset"."type", "asset"."deletedAt", "asset"."visibility", diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index 0b706cacf9..b144666773 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,6 +784,7 @@ 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', @@ -815,7 +817,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,7 +863,12 @@ 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) => @@ -880,6 +887,7 @@ 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'), @@ -929,6 +937,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/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/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/media.service.spec.ts b/server/src/services/media.service.spec.ts index 1524d96336..994ba436ad 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -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/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..c03f7bacaa 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 { diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts index fb07eb1438..c9a8492b5d 100644 --- a/server/src/services/system-config.service.spec.ts +++ b/server/src/services/system-config.service.spec.ts @@ -70,7 +70,7 @@ const updatedConfig = Object.freeze({ preferredHwDevice: 'auto', transcode: TranscodePolicy.Required, accel: TranscodeHardwareAcceleration.Disabled, - accelDecode: false, + accelDecode: true, tonemap: ToneMapping.Hable, }, logging: { diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts index bc530f2b03..fbf32c0ac2 100644 --- a/server/src/utils/database.ts +++ b/server/src/utils/database.ts @@ -17,7 +17,7 @@ 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'; @@ -298,8 +298,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/test/medium/specs/services/timeline.service.spec.ts b/server/test/medium/specs/services/timeline.service.spec.ts index eaca4dcc14..092a523010 100644 --- a/server/test/medium/specs/services/timeline.service.spec.ts +++ b/server/test/medium/specs/services/timeline.service.spec.ts @@ -118,6 +118,7 @@ describe(TimelineService.name, () => { expect(response).toEqual({ city: [], country: [], + createdAt: [], duration: [], id: [], visibility: [], 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/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 { await refreshStack(); ocrManager.clear(); + faceManager.clear(); if (!sharedLink) { if (previewStackedAsset) { await ocrManager.getAssetOcr(previewStackedAsset.id); + await faceManager.getAssetFaces(previewStackedAsset.id); } await ocrManager.getAssetOcr(asset.id); + await faceManager.getAssetFaces(asset.id); } }; @@ -639,6 +645,10 @@ {/if} + + {#if $slideshowState !== SlideshowState.None} + + {/if} {#if $slideshowState === SlideshowState.None && showNavigation && !assetViewerManager.isShowEditor && !assetViewerManager.isFaceEditMode && nextAsset} diff --git a/web/src/lib/components/asset-viewer/DetailPanelPeople.svelte b/web/src/lib/components/asset-viewer/DetailPanelPeople.svelte index 9a7c6215ec..78265f9ea2 100644 --- a/web/src/lib/components/asset-viewer/DetailPanelPeople.svelte +++ b/web/src/lib/components/asset-viewer/DetailPanelPeople.svelte @@ -3,6 +3,7 @@ import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte'; import { Route } from '$lib/route'; + import { faceManager } from '$lib/stores/face.svelte'; import { locale } from '$lib/stores/preferences.store'; import { getPeopleThumbnailUrl } from '$lib/utils'; import { type AssetResponseDto } from '@immich/sdk'; @@ -19,8 +20,7 @@ const { asset, isOwner, previousRoute }: Props = $props(); - const unassignedFaces = $derived(asset.unassignedFaces || []); - const people = $derived(asset.people || []); + const people = $derived(Array.from(faceManager.people)); const visiblePeople = $derived( people .filter((p) => assetViewerManager.isShowingHiddenPeople || !p.isHidden) @@ -82,7 +82,7 @@ onclick={() => assetViewerManager.toggleFaceEditMode()} /> - {#if people.length > 0 || unassignedFaces.length > 0} + {#if faceManager.data.length > 0} {#each visiblePeople as person (person.id)} - {@const isHighlighted = person.faces.some((f) => - assetViewerManager.highlightedFaces.some((b) => b.id === f.id), - )} + {@const personFaces = faceManager.facesByPersonId.get(person.id) ?? []} + {@const isHighlighted = personFaces.some((f) => assetViewerManager.highlightedFaces.some((b) => b.id === f.id))} assetViewerManager.setHighlightedFaces(person.faces)} + onfocus={() => assetViewerManager.setHighlightedFaces(personFaces)} onblur={() => assetViewerManager.clearHighlightedFaces()} - onpointerenter={() => assetViewerManager.setHighlightedFaces(person.faces)} + onpointerenter={() => assetViewerManager.setHighlightedFaces(personFaces)} onpointerleave={() => assetViewerManager.clearHighlightedFaces()} > { // eslint-disable-next-line svelte/prefer-svelte-reactivity const map = new Map(); - for (const person of asset.people ?? []) { - if (person.isHidden && !assetViewerManager.isShowingHiddenPeople) { + for (const face of faceManager.data) { + if (!face.person) { continue; } - for (const face of person.faces ?? []) { - map.set(face, person.name); + if (face.person.isHidden && !assetViewerManager.isShowingHiddenPeople) { + continue; } + map.set(face, face.person.name); } return map; }); diff --git a/web/src/lib/components/asset-viewer/SlideshowMetadataOverlay.svelte b/web/src/lib/components/asset-viewer/SlideshowMetadataOverlay.svelte new file mode 100644 index 0000000000..e1865e2c14 --- /dev/null +++ b/web/src/lib/components/asset-viewer/SlideshowMetadataOverlay.svelte @@ -0,0 +1,65 @@ + + +{#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/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 @@ -
+
- +
+ + + {$t('birthdate_set_description')} + {#if person.birthDate}
-
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/route.ts b/web/src/lib/route.ts index ac38f09dc6..5a374af5c0 100644 --- a/web/src/lib/route.ts +++ b/web/src/lib/route.ts @@ -105,6 +105,7 @@ export const Route = { locked: () => '/locked', trash: () => '/trash', viewTrashedAsset: ({ id }: { id: string }) => `/trash/photos/${id}`, + recentlyAdded: () => '/recently-added', // search search: (dto?: MetadataSearchDto | SmartSearchDto) => { diff --git a/web/src/lib/stores/face.svelte.ts b/web/src/lib/stores/face.svelte.ts new file mode 100644 index 0000000000..cfbffe0441 --- /dev/null +++ b/web/src/lib/stores/face.svelte.ts @@ -0,0 +1,74 @@ +import type { AssetFaceResponseDto, PersonResponseDto } from '@immich/sdk'; +import { SvelteMap, SvelteSet } from 'svelte/reactivity'; +import { assetCacheManager } from '$lib/managers/AssetCacheManager.svelte'; +import type { Faces } from '$lib/managers/asset-viewer-manager.svelte'; +import { CancellableTask } from '$lib/utils/cancellable-task'; + +class FaceManager { + #data = $state([]); + #faceLoader = new CancellableTask(); + #cleared = false; + + readonly faceNames = $derived.by(() => { + // eslint-disable-next-line svelte/prefer-svelte-reactivity + const map = new Map(); + + for (const face of this.data) { + if (!face.person) { + continue; + } + map.set(face, face.person.name); + } + + return map; + }); + + readonly people = $derived.by(() => { + const people = new SvelteSet(); + + for (const face of this.data) { + if (face.person) { + people.add(face.person); + } + } + + return people; + }); + + readonly facesByPersonId = $derived.by(() => { + const map = new SvelteMap(); + for (const face of faceManager.data) { + if (!face.person) { + continue; + } + const existing = map.get(face.person.id); + if (existing) { + existing.push(face); + } else { + map.set(face.person.id, [face]); + } + } + return map; + }); + + get data() { + return this.#data; + } + + async getAssetFaces(id: string) { + if (this.#cleared) { + await this.#faceLoader.reset(); + this.#cleared = false; + } + await this.#faceLoader.execute(async () => { + this.#data = await assetCacheManager.getAssetFaces(id); + }, false); + } + + clear() { + this.#cleared = true; + this.#data = []; + } +} + +export const faceManager = new FaceManager(); diff --git a/web/src/lib/stores/ocr.svelte.spec.ts b/web/src/lib/stores/ocr.svelte.spec.ts index 3e5bd0f0cc..79c4b8d0ea 100644 --- a/web/src/lib/stores/ocr.svelte.spec.ts +++ b/web/src/lib/stores/ocr.svelte.spec.ts @@ -7,6 +7,7 @@ import { ocrManager, type OcrBoundingBox } from '$lib/stores/ocr.svelte'; vi.mock('@immich/sdk', () => ({ getAssetInfo: vi.fn(), getAssetOcr: vi.fn(), + getFaces: vi.fn(), })); const createMockOcrData = (overrides?: Partial): OcrBoundingBox[] => [ diff --git a/web/src/lib/stores/preferences.store.ts b/web/src/lib/stores/preferences.store.ts index 3c31bef856..016942c572 100644 --- a/web/src/lib/stores/preferences.store.ts +++ b/web/src/lib/stores/preferences.store.ts @@ -1,3 +1,4 @@ +import type { DateTime } from 'luxon'; import { persisted } from 'svelte-persisted-store'; import { browser } from '$app/environment'; import { defaultLang } from '$lib/constants'; @@ -26,8 +27,8 @@ export interface MapSettings { withPartners: boolean; withSharedAlbums: boolean; relativeDate: string; - dateAfter: string; - dateBefore: string; + dateAfter?: DateTime; + dateBefore?: DateTime; } const defaultMapSettings = { @@ -37,8 +38,6 @@ const defaultMapSettings = { withPartners: false, withSharedAlbums: false, relativeDate: '', - dateAfter: '', - dateBefore: '', }; const persistedObject = (key: string, defaults: T) => diff --git a/web/src/lib/stores/slideshow.store.ts b/web/src/lib/stores/slideshow.store.ts index 4b8fd83369..a576d8f37b 100644 --- a/web/src/lib/stores/slideshow.store.ts +++ b/web/src/lib/stores/slideshow.store.ts @@ -19,6 +19,11 @@ export enum SlideshowLook { BlurredBackground = 'blurred-background', } +export enum SlideshowMetadataOverlayMode { + DescriptionOnly = 'description-only', + Full = 'full', +} + export const slideshowLookCssMapping: Record = { [SlideshowLook.Contain]: 'object-contain', [SlideshowLook.Cover]: 'object-cover', @@ -41,6 +46,11 @@ function createSlideshowStore() { const slideshowTransition = persisted('slideshow-transition', true); const slideshowAutoplay = persisted('slideshow-autoplay', true, {}); const slideshowRepeat = persisted('slideshow-repeat', false); + const slideshowShowMetadataOverlay = persisted('slideshow-show-metadata-overlay', false); + const slideshowMetadataOverlayMode = persisted( + 'slideshow-metadata-overlay-mode', + SlideshowMetadataOverlayMode.Full, + ); return { restartProgress: { @@ -73,6 +83,8 @@ function createSlideshowStore() { slideshowTransition, slideshowAutoplay, slideshowRepeat, + slideshowShowMetadataOverlay, + slideshowMetadataOverlayMode, }; } diff --git a/web/src/lib/utils/thumbnail-util.spec.ts b/web/src/lib/utils/thumbnail-util.spec.ts index 7089ddd020..769f1541ae 100644 --- a/web/src/lib/utils/thumbnail-util.spec.ts +++ b/web/src/lib/utils/thumbnail-util.spec.ts @@ -71,6 +71,15 @@ describe('getAltText', () => { second: testDate.getUTCSeconds(), millisecond: testDate.getUTCMilliseconds(), }, + createdAt: { + year: testDate.getUTCFullYear(), + month: testDate.getUTCMonth() + 1, // Note: getMonth() is 0-based + day: testDate.getUTCDate(), + hour: testDate.getUTCHours(), + minute: testDate.getUTCMinutes(), + second: testDate.getUTCSeconds(), + millisecond: testDate.getUTCMilliseconds(), + }, localDateTime: { year: testDate.getUTCFullYear(), month: testDate.getUTCMonth() + 1, // Note: getMonth() is 0-based diff --git a/web/src/lib/utils/timeline-util.ts b/web/src/lib/utils/timeline-util.ts index 36f5b18d8d..5e8082c536 100644 --- a/web/src/lib/utils/timeline-util.ts +++ b/web/src/lib/utils/timeline-util.ts @@ -1,4 +1,4 @@ -import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk'; +import { AssetTypeEnum, AssetOrderBy, type AssetResponseDto } from '@immich/sdk'; import { DateTime, type LocaleOptions } from 'luxon'; import { SvelteSet } from 'svelte/reactivity'; import { get } from 'svelte/store'; @@ -166,6 +166,7 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): const localDateTime = fromISODateTimeUTCToObject(assetResponse.localDateTime); const fileCreatedAt = fromISODateTimeToObject(assetResponse.fileCreatedAt, assetResponse.exifInfo?.timeZone ?? 'UTC'); + const createdAt = fromISODateTimeUTCToObject(assetResponse.createdAt); return { id: assetResponse.id, @@ -174,6 +175,7 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): ratio, thumbhash: assetResponse.thumbhash, localDateTime, + createdAt, fileCreatedAt, isFavorite: assetResponse.isFavorite, visibility: assetResponse.visibility, @@ -236,3 +238,6 @@ export function setDifference(setA: Set, setB: Set): SvelteSet { } return result; } + +export const getOrderingDate = (asset: TimelineAsset, order: AssetOrderBy) => + order === AssetOrderBy.CreatedAt ? asset.createdAt : asset.localDateTime; diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte index df2a6c0bfa..df4c9a9eb5 100644 --- a/web/src/routes/(user)/explore/+page.svelte +++ b/web/src/routes/(user)/explore/+page.svelte @@ -11,6 +11,8 @@ import { mdiHeart } from '@mdi/js'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; + import { toTimelineAsset } from '$lib/utils/timeline-util'; + import { getAltText } from '$lib/utils/thumbnail-util'; interface Props { data: PageData; @@ -24,6 +26,9 @@ }; let places = $derived(getFieldItems(data.items, 'exifInfo.city')); + let recents = $derived( + getFieldItems(data.items, 'createdAt').sort((a, b) => new Date(b.value).getTime() - new Date(a.value).getTime()), + ); let people = $state(data.response.people); let hasPeople = $derived(data.response.total > 0); @@ -107,7 +112,31 @@
{/if} - {#if !hasPeople && places.length === 0} + {#if recents.length > 0} +
+ +
+ {#each recents as item (item.data.id)} + + {$getAltText(toTimelineAsset(item.data))} + + {/each} +
+
+ {/if} + + {#if !hasPeople && places.length === 0 && recents.length === 0} {/if} diff --git a/web/src/routes/(user)/recently-added/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/recently-added/[[photos=photos]]/[[assetId=id]]/+page.svelte new file mode 100644 index 0000000000..e84f32bed5 --- /dev/null +++ b/web/src/routes/(user)/recently-added/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -0,0 +1,165 @@ + + + + + {#snippet empty()} + openFileUploadDialog()} class="mx-auto mt-10" /> + {/snippet} + + + +{#if assetMultiSelectManager.selectionActive} + + {@const Actions = getAssetBulkActions($t)} + + + + + + + {#if assetMultiSelectManager.isAllUserOwned} + timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))} + /> + + + + {#if assetMultiSelectManager.assets.length > 1 || isAssetStackSelected} + updateStackedAssetInTimeline(timelineManager, result)} + onUnstack={(assets) => updateUnstackedAssetInTimeline(timelineManager, assets)} + /> + {/if} + {#if isLinkActionAvailable} + + {/if} + + + + timelineManager.update(ids, (asset) => (asset.visibility = visibility))} + /> + {#if authManager.preferences.tags.enabled} + + {/if} + timelineManager.removeAssets(assetIds)} + onUndoDelete={(assets) => timelineManager.upsertAssets(assets)} + /> + +
+ + + +
+ {:else} + + {/if} +
+{/if} diff --git a/web/src/routes/(user)/recently-added/[[photos=photos]]/[[assetId=id]]/+page.ts b/web/src/routes/(user)/recently-added/[[photos=photos]]/[[assetId=id]]/+page.ts new file mode 100644 index 0000000000..ba75f540d4 --- /dev/null +++ b/web/src/routes/(user)/recently-added/[[photos=photos]]/[[assetId=id]]/+page.ts @@ -0,0 +1,14 @@ +import { authenticate } from '$lib/utils/auth'; +import { getFormatter } from '$lib/utils/i18n'; +import type { PageLoad } from './$types'; + +export const load = (async ({ url }) => { + await authenticate(url); + const $t = await getFormatter(); + + return { + meta: { + title: $t('recently_added_page_title'), + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte index f32b74efa2..2f79b1143f 100644 --- a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -42,7 +42,7 @@ type SmartSearchDto, } from '@immich/sdk'; import { ActionButton, CommandPaletteDefaultProvider, Icon, IconButton, LoadingSpinner } from '@immich/ui'; - import { mdiArrowLeft, mdiDotsVertical, mdiImageOffOutline, mdiSelectAll } from '@mdi/js'; + import { mdiArrowLeft, mdiClose, mdiDotsVertical, mdiImageOffOutline, mdiSelectAll } from '@mdi/js'; import { tick, untrack } from 'svelte'; import { t } from 'svelte-i18n'; @@ -65,6 +65,7 @@ let searchQuery = $derived(page.url.searchParams.get(QueryParameter.QUERY)); let smartSearchEnabled = $derived(featureFlagsManager.value.smartSearch); let terms = $derived(searchQuery ? JSON.parse(searchQuery) : {}); + let searchTermKeys = $derived(getObjectKeys(terms)); $effect(() => { // we want this to *only* be reactive on `terms` @@ -235,50 +236,65 @@ function getObjectKeys(obj: T): (keyof T)[] { return Object.keys(obj) as (keyof T)[]; } + + function removeFilter(key: keyof SearchTerms) { + delete terms[key]; + void goto(Route.search(terms)); + } -{#if terms} -
- {#each getObjectKeys(terms) as searchKey (searchKey)} - {@const value = terms[searchKey]} -
+{#if searchTermKeys.length > 0} +
+
+ {#each searchTermKeys as searchKey (searchKey)} + {@const value = terms[searchKey]}
- {getHumanReadableSearchKey(searchKey as keyof SearchTerms)} -
+ + {getHumanReadableSearchKey(searchKey as keyof SearchTerms)} + - {#if value !== true} -
- {#if (searchKey === 'takenAfter' || searchKey === 'takenBefore') && typeof value === 'string'} - {getHumanReadableDate(value)} - {:else if searchKey === 'personIds' && Array.isArray(value)} - {#await getPersonName(value) then personName} - {personName} - {/await} - {:else if searchKey === 'tagIds' && (Array.isArray(value) || value === null)} - {#await getTagNames(value) then tagNames} - {tagNames} - {/await} - {:else if searchKey === 'rating'} - {$t('rating_count', { values: { count: value ?? 0 } })} - {:else if value === null || value === ''} - {$t('unknown')} - {:else} - {value} - {/if} -
- {/if} -
- {/each} + {#if value !== true} + + {#if (searchKey === 'takenAfter' || searchKey === 'takenBefore') && typeof value === 'string'} + {getHumanReadableDate(value)} + {:else if searchKey === 'personIds' && Array.isArray(value)} + {#await getPersonName(value) then personName} + {personName} + {/await} + {:else if searchKey === 'tagIds' && (Array.isArray(value) || value === null)} + {#await getTagNames(value) then tagNames} + {tagNames} + {/await} + {:else if searchKey === 'rating'} + {$t('rating_count', { values: { count: value ?? 0 } })} + {:else if value === null || value === ''} + {$t('unknown')} + {:else} + {value} + {/if} + + {/if} + + +
+ {/each} +
{/if} diff --git a/web/src/test-data/factories/asset-factory.ts b/web/src/test-data/factories/asset-factory.ts index 00a686e6c9..17b53c7a80 100644 --- a/web/src/test-data/factories/asset-factory.ts +++ b/web/src/test-data/factories/asset-factory.ts @@ -38,6 +38,7 @@ export const timelineAssetFactory = Sync.makeFactory({ tags: [], thumbhash: Sync.each(() => faker.string.alphanumeric(28)), localDateTime: Sync.each(() => fromISODateTimeUTCToObject(faker.date.past().toISOString())), + createdAt: Sync.each(() => fromISODateTimeUTCToObject(faker.date.past().toISOString())), fileCreatedAt: Sync.each(() => fromISODateTimeUTCToObject(faker.date.past().toISOString())), isFavorite: Sync.each(() => faker.datatype.boolean()), visibility: AssetVisibility.Timeline, @@ -66,6 +67,7 @@ export const toResponseDto = (...timelineAsset: TimelineAsset[]) => { livePhotoVideoId: [], fileCreatedAt: [], localOffsetHours: [], + createdAt: [], ownerId: [], projectionType: [], ratio: [],