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
+
+
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.

@@ -123,4 +124,67 @@ In this example, the IP addresses mismatch and the firewall rule needs to be edi

+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.
+
+
+## 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