mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
Merge branch 'main' into feature/load-previews
This commit is contained in:
@@ -51,14 +51,14 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3
|
uses: immich-app/devtools/actions/pre-job@91f342bb4477c4bc10c576ae739da875d85aa164 # pre-job-action-v2.0.4
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
@@ -79,9 +79,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -210,7 +210,7 @@ jobs:
|
|||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0
|
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
|
||||||
with:
|
with:
|
||||||
ruby-version: '3.3'
|
ruby-version: '3.3'
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ jobs:
|
|||||||
actions: write
|
actions: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Check for breaking API changes
|
- name: Check for breaking API changes
|
||||||
uses: oasdiff/oasdiff-action/breaking@f8cb9308b42121e793f835bd14c0b8090420430c # v0.0.39
|
uses: oasdiff/oasdiff-action/breaking@37bf9ff785c7315df88216660826e71be4cc03da # v0.0.44
|
||||||
with:
|
with:
|
||||||
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
||||||
revision: open-api/immich-openapi-specs.json
|
revision: open-api/immich-openapi-specs.json
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ jobs:
|
|||||||
working-directory: ./cli
|
working-directory: ./cli
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './cli/.nvmrc'
|
node-version-file: './cli/.nvmrc'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
@@ -71,9 +71,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
needs: [get_body, should_run]
|
needs: [get_body, should_run]
|
||||||
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/immich-app/mdq:main@sha256:557cca601891b8b7d78b940071d35aaf7aaeb9b327d19b22cf282118edbc5272
|
image: ghcr.io/immich-app/mdq:main@sha256:32abe582452b12dff55055e1d6bc24508a8f17164f9d1831db7bb70953c014c6
|
||||||
outputs:
|
outputs:
|
||||||
checked: ${{ steps.get_checkbox.outputs.checked }}
|
checked: ${{ steps.get_checkbox.outputs.checked }}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3
|
uses: immich-app/devtools/actions/pre-job@91f342bb4477c4bc10c576ae739da875d85aa164 # pre-job-action-v2.0.4
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
@@ -132,7 +132,7 @@ jobs:
|
|||||||
suffixes: '-rocm'
|
suffixes: '-rocm'
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
runner-mapping: '{"linux/amd64": "pokedex-large"}'
|
runner-mapping: '{"linux/amd64": "pokedex-large"}'
|
||||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@61a0fc2b41524edcc7c9fffb8bb178e6b0ccf21d # multi-runner-build-workflow-v2.3.0
|
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@5813c7c4f7016c748ae7ac5d5f684846649d4d20 # multi-runner-build-workflow-v2.4.0
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
@@ -155,7 +155,7 @@ jobs:
|
|||||||
name: Build and Push Server
|
name: Build and Push Server
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
|
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
|
||||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@61a0fc2b41524edcc7c9fffb8bb178e6b0ccf21d # multi-runner-build-workflow-v2.3.0
|
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@5813c7c4f7016c748ae7ac5d5f684846649d4d20 # multi-runner-build-workflow-v2.4.0
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
@@ -178,7 +178,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
- uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5
|
- uses: immich-app/devtools/actions/success-check@81113db03f6d743efee81e0058c0b43f6cd6f36d # success-check-action-v0.0.6
|
||||||
with:
|
with:
|
||||||
needs: ${{ toJSON(needs) }}
|
needs: ${{ toJSON(needs) }}
|
||||||
|
|
||||||
@@ -189,6 +189,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
- uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5
|
- uses: immich-app/devtools/actions/success-check@81113db03f6d743efee81e0058c0b43f6cd6f36d # success-check-action-v0.0.6
|
||||||
with:
|
with:
|
||||||
needs: ${{ toJSON(needs) }}
|
needs: ${{ toJSON(needs) }}
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3
|
uses: immich-app/devtools/actions/pre-job@91f342bb4477c4bc10c576ae739da875d85aa164 # pre-job-action-v2.0.4
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
@@ -54,9 +54,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './docs/.nvmrc'
|
node-version-file: './docs/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ jobs:
|
|||||||
artifact: ${{ steps.get-artifact.outputs.result }}
|
artifact: ${{ steps.get-artifact.outputs.result }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- if: ${{ github.event.workflow_run.conclusion != 'success' }}
|
- if: ${{ github.event.workflow_run.conclusion != 'success' }}
|
||||||
@@ -119,9 +119,9 @@ jobs:
|
|||||||
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
|
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -131,7 +131,9 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@035e80a7d4355d5f087ffb95db9e4a0944c04e56 # use-mise-action-v1.1.3
|
uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0
|
||||||
|
with:
|
||||||
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Load parameters
|
- name: Load parameters
|
||||||
id: parameters
|
id: parameters
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -29,7 +29,9 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@035e80a7d4355d5f087ffb95db9e4a0944c04e56 # use-mise-action-v1.1.3
|
uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0
|
||||||
|
with:
|
||||||
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Destroy Docs Subdomain
|
- name: Destroy Docs Subdomain
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: 'Checkout'
|
- name: 'Checkout'
|
||||||
@@ -29,10 +29,10 @@ jobs:
|
|||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb # v6.0.0
|
uses: pnpm/action-setup@26f6d4f2c533a43e6b5da0b4a5dd983f98f7b49a # v6.0.4
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
secrets:
|
secrets:
|
||||||
PUSH_O_MATIC_APP_ID:
|
PUSH_O_MATIC_APP_CLIENT_ID:
|
||||||
required: true
|
required: true
|
||||||
PUSH_O_MATIC_APP_KEY:
|
PUSH_O_MATIC_APP_KEY:
|
||||||
required: true
|
required: true
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
if: ${{ inputs.skip != true }}
|
if: ${{ inputs.skip != true }}
|
||||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Find translation PR
|
- name: Find translation PR
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Require PR to have a changelog label
|
- name: Require PR to have a changelog label
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
secrets:
|
secrets:
|
||||||
PUSH_O_MATIC_APP_ID: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
PUSH_O_MATIC_APP_CLIENT_ID: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
PUSH_O_MATIC_APP_KEY: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
PUSH_O_MATIC_APP_KEY: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }}
|
WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -63,13 +63,13 @@ jobs:
|
|||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb # v6.0.0
|
uses: pnpm/action-setup@26f6d4f2c533a43e6b5da0b4a5dd983f98f7b49a # v6.0.4
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -126,7 +126,7 @@ jobs:
|
|||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: mshick/add-pr-comment@64b8e914979889d746c99dea15a76e77ef64580a # v3.10.0
|
- uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
message-id: 'preview-status'
|
message-id: 'preview-status'
|
||||||
@@ -32,9 +32,9 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
@@ -48,14 +48,14 @@ jobs:
|
|||||||
name: 'preview'
|
name: 'preview'
|
||||||
})
|
})
|
||||||
|
|
||||||
- uses: mshick/add-pr-comment@64b8e914979889d746c99dea15a76e77ef64580a # v3.10.0
|
- uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
||||||
if: ${{ github.event.pull_request.head.repo.fork }}
|
if: ${{ github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
message-id: 'preview-status'
|
message-id: 'preview-status'
|
||||||
message: 'PRs from forks cannot have preview environments.'
|
message: 'PRs from forks cannot have preview environments.'
|
||||||
|
|
||||||
- uses: mshick/add-pr-comment@64b8e914979889d746c99dea15a76e77ef64580a # v3.10.0
|
- uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ jobs:
|
|||||||
working-directory: ./open-api/typescript-sdk
|
working-directory: ./open-api/typescript-sdk
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
|
|
||||||
# Setup .npmrc file to publish to npm
|
# Setup .npmrc file to publish to npm
|
||||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './open-api/typescript-sdk/.nvmrc'
|
node-version-file: './open-api/typescript-sdk/.nvmrc'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3
|
uses: immich-app/devtools/actions/pre-job@91f342bb4477c4bc10c576ae739da875d85aa164 # pre-job-action-v2.0.4
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
@@ -49,9 +49,9 @@ jobs:
|
|||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|||||||
+74
-65
@@ -17,14 +17,14 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3
|
uses: immich-app/devtools/actions/pre-job@91f342bb4477c4bc10c576ae739da875d85aa164 # pre-job-action-v2.0.4
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
@@ -34,13 +34,17 @@ jobs:
|
|||||||
- 'web/**'
|
- 'web/**'
|
||||||
- 'i18n/**'
|
- 'i18n/**'
|
||||||
- 'open-api/typescript-sdk/**'
|
- 'open-api/typescript-sdk/**'
|
||||||
|
- 'pnpm-lock.yaml'
|
||||||
server:
|
server:
|
||||||
- 'server/**'
|
- 'server/**'
|
||||||
|
- 'pnpm-lock.yaml'
|
||||||
cli:
|
cli:
|
||||||
- 'cli/**'
|
- 'cli/**'
|
||||||
- 'open-api/typescript-sdk/**'
|
- 'open-api/typescript-sdk/**'
|
||||||
|
- 'pnpm-lock.yaml'
|
||||||
e2e:
|
e2e:
|
||||||
- 'e2e/**'
|
- 'e2e/**'
|
||||||
|
- 'pnpm-lock.yaml'
|
||||||
mobile:
|
mobile:
|
||||||
- 'mobile/**'
|
- 'mobile/**'
|
||||||
machine-learning:
|
machine-learning:
|
||||||
@@ -63,9 +67,9 @@ jobs:
|
|||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -77,7 +81,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -108,9 +112,9 @@ jobs:
|
|||||||
working-directory: ./cli
|
working-directory: ./cli
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -121,7 +125,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './cli/.nvmrc'
|
node-version-file: './cli/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -155,9 +159,9 @@ jobs:
|
|||||||
working-directory: ./cli
|
working-directory: ./cli
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -168,7 +172,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './cli/.nvmrc'
|
node-version-file: './cli/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -197,9 +201,9 @@ jobs:
|
|||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -210,7 +214,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './web/.nvmrc'
|
node-version-file: './web/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -241,9 +245,9 @@ jobs:
|
|||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -254,7 +258,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './web/.nvmrc'
|
node-version-file: './web/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -279,9 +283,9 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -292,7 +296,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './web/.nvmrc'
|
node-version-file: './web/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -327,9 +331,9 @@ jobs:
|
|||||||
working-directory: ./e2e
|
working-directory: ./e2e
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -340,7 +344,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './e2e/.nvmrc'
|
node-version-file: './e2e/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -373,9 +377,9 @@ jobs:
|
|||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -387,11 +391,15 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- name: Setup Mise
|
||||||
|
uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0
|
||||||
|
with:
|
||||||
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
- name: Run pnpm install
|
- name: Run pnpm install
|
||||||
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
|
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
|
||||||
- name: Run medium tests
|
- name: Run medium tests
|
||||||
@@ -412,9 +420,9 @@ jobs:
|
|||||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -426,7 +434,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './e2e/.nvmrc'
|
node-version-file: './e2e/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -484,9 +492,9 @@ jobs:
|
|||||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -498,7 +506,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './e2e/.nvmrc'
|
node-version-file: './e2e/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -566,7 +574,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
- uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5
|
- uses: immich-app/devtools/actions/success-check@81113db03f6d743efee81e0058c0b43f6cd6f36d # success-check-action-v0.0.6
|
||||||
with:
|
with:
|
||||||
needs: ${{ toJSON(needs) }}
|
needs: ${{ toJSON(needs) }}
|
||||||
mobile-unit-tests:
|
mobile-unit-tests:
|
||||||
@@ -578,9 +586,9 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -605,39 +613,40 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
env:
|
||||||
|
MISE_CEILING_PATHS: ${{ github.workspace }}
|
||||||
|
MISE_TRUSTED_CONFIG_PATHS: ${{ github.workspace }}/machine-learning/mise.toml
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./machine-learning
|
working-directory: ./machine-learning
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Install uv
|
|
||||||
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
- name: Setup Mise
|
||||||
|
uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
working_directory: ./machine-learning
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: mise run install --extra cpu
|
||||||
uv sync --extra cpu
|
- name: Lint code
|
||||||
- name: Lint with ruff
|
run: mise run lint --output-format=github
|
||||||
run: |
|
- name: Format code
|
||||||
uv run ruff check --output-format=github immich_ml
|
run: mise run format
|
||||||
- name: Format with ruff
|
- name: Run type checking
|
||||||
run: |
|
run: mise run check
|
||||||
uv run ruff format --check immich_ml
|
|
||||||
- name: Run mypy type checking
|
|
||||||
run: |
|
|
||||||
uv run mypy --strict immich_ml/
|
|
||||||
- name: Run tests and coverage
|
- name: Run tests and coverage
|
||||||
run: |
|
run: mise run test
|
||||||
uv run pytest --cov=immich_ml --cov-report term-missing
|
|
||||||
github-files-formatting:
|
github-files-formatting:
|
||||||
name: .github Files Formatting
|
name: .github Files Formatting
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -650,9 +659,9 @@ jobs:
|
|||||||
working-directory: ./.github
|
working-directory: ./.github
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -663,7 +672,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './.github/.nvmrc'
|
node-version-file: './.github/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -680,9 +689,9 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -701,9 +710,9 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -714,7 +723,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -763,9 +772,9 @@ jobs:
|
|||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -776,7 +785,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3
|
uses: immich-app/devtools/actions/pre-job@91f342bb4477c4bc10c576ae739da875d85aa164 # pre-job-action-v2.0.4
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
@@ -47,9 +47,9 @@ jobs:
|
|||||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
|
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Bot review status
|
- name: Bot review status
|
||||||
@@ -68,6 +68,6 @@ jobs:
|
|||||||
permissions: {}
|
permissions: {}
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
- uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5
|
- uses: immich-app/devtools/actions/success-check@81113db03f6d743efee81e0058c0b43f6cd6f36d # success-check-action-v0.0.6
|
||||||
with:
|
with:
|
||||||
needs: ${{ toJSON(needs) }}
|
needs: ${{ toJSON(needs) }}
|
||||||
|
|||||||
Vendored
+5
-1
@@ -26,7 +26,11 @@
|
|||||||
},
|
},
|
||||||
"[svelte]": {
|
"[svelte]": {
|
||||||
"editor.defaultFormatter": "svelte.svelte-vscode",
|
"editor.defaultFormatter": "svelte.svelte-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true,
|
||||||
|
"tailwindCSS.lint.suggestCanonicalClasses": "ignore"
|
||||||
|
},
|
||||||
|
"svelte.plugin.svelte.compilerWarnings": {
|
||||||
|
"state_referenced_locally": "ignore"
|
||||||
},
|
},
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[tools]
|
[tools]
|
||||||
terragrunt = "1.0.1"
|
terragrunt = "1.0.3"
|
||||||
opentofu = "1.11.6"
|
opentofu = "1.11.6"
|
||||||
|
|
||||||
[tasks."tg:fmt"]
|
[tasks."tg:fmt"]
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ services:
|
|||||||
container_name: immich_prometheus
|
container_name: immich_prometheus
|
||||||
ports:
|
ports:
|
||||||
- 9090:9090
|
- 9090:9090
|
||||||
image: prom/prometheus@sha256:5550dc63da361dc30f6fe02ac0e4dfc736ededfef3c8d12a634db04a67824d78
|
image: prom/prometheus@sha256:e4254400b85610324913f0dc4acf92603d9984e7519414c5a12811aa6146acc3
|
||||||
volumes:
|
volumes:
|
||||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
- prometheus-data:/prometheus
|
- prometheus-data:/prometheus
|
||||||
@@ -97,7 +97,7 @@ services:
|
|||||||
command: ['./run.sh', '-disable-reporting']
|
command: ['./run.sh', '-disable-reporting']
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
image: grafana/grafana:12.4.2-ubuntu@sha256:78839fe49e1425c02416fa8072591533a72bd9598e563b54a07d78f9e27fb5d3
|
image: grafana/grafana:12.4.3-ubuntu@sha256:ca3f764fdc48cebdf22dd206f33ecb0795a9a7210eacd1b5c02204aebd78b223
|
||||||
volumes:
|
volumes:
|
||||||
- grafana-data:/var/lib/grafana
|
- grafana-data:/var/lib/grafana
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ VectorChord is the successor extension to pgvecto.rs, allowing for higher perfor
|
|||||||
|
|
||||||
### Migrating from pgvecto.rs
|
### Migrating from pgvecto.rs
|
||||||
|
|
||||||
Support for pgvecto.rs will be dropped in a later release, hence we recommend all users currently using pgvecto.rs to migrate to VectorChord at their convenience. There are two primary approaches to do so.
|
Support for pgvecto.rs has been dropped as of 3.0, hence all users currently using pgvecto.rs should migrate to VectorChord. There are two primary approaches to do so.
|
||||||
|
|
||||||
The easiest option is to have both extensions installed during the migration:
|
The easiest option is to have both extensions installed during the migration:
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ Some basic examples:
|
|||||||
- `**/Raw/**` will exclude all files in any directory named `Raw`
|
- `**/Raw/**` will exclude all files in any directory named `Raw`
|
||||||
- `**/*.{tif,jpg}` will exclude all files with the extension `.tif` or `.jpg`
|
- `**/*.{tif,jpg}` will exclude all files with the extension `.tif` or `.jpg`
|
||||||
|
|
||||||
|
Note that `*` is a wildcard matching zero or more characters (i.e., withinin a filename or single directory name). `**` matches zero or more subdirectories, recursively. It also includes any/all files within a subdirectory, i.e., when used at the end of a pattern. For example, `**/exclude_me/**` will exclude all files in any directory named `exclude_me`, as well as all files in any subdirectories of `exclude_me`, recursively.
|
||||||
|
|
||||||
Special characters such as @ should be escaped, for instance:
|
Special characters such as @ should be escaped, for instance:
|
||||||
|
|
||||||
- `**/\@eaDir/**` will exclude all files in any directory named `@eaDir`
|
- `**/\@eaDir/**` will exclude all files in any directory named `@eaDir`
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele
|
|||||||
|
|
||||||
#### ROCm
|
#### ROCm
|
||||||
|
|
||||||
|
- On Linux, The [AMDGPU driver module](https://rocm.docs.amd.com/projects/install-on-linux/en/latest/how-to/docker.html) needs to be installed on the server and, if secure boot is used, the signing key of DKMS [needs to be enrolled in UEFI BIOS](https://wiki.debian.org/SecureBoot)
|
||||||
- The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=<a supported version, e.g. 10.3.0>`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`.
|
- The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=<a supported version, e.g. 10.3.0>`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`.
|
||||||
- The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached.
|
- The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached.
|
||||||
- This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/install/environment-variables) setting).
|
- This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/install/environment-variables) setting).
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ You can search the following types of content:
|
|||||||
| People | Faces that are recognized in your photos/videos. |
|
| People | Faces that are recognized in your photos/videos. |
|
||||||
| Contextual | Content of the photos and videos. |
|
| Contextual | Content of the photos and videos. |
|
||||||
| File name or extension | Full or partial file's name, or file's extension |
|
| File name or extension | Full or partial file's name, or file's extension |
|
||||||
|
| Full path or folder | Full or partial folder names from the original path. |
|
||||||
| Description | Description added to assets. |
|
| Description | Description added to assets. |
|
||||||
| Optical Character Recognition (OCR) | Text in images |
|
| Optical Character Recognition (OCR) | Text in images |
|
||||||
| Locations | Cities, states, and countries from reverse geocoding. |
|
| Locations | Cities, states, and countries from reverse geocoding. |
|
||||||
@@ -30,6 +31,12 @@ You can search the following types of content:
|
|||||||
|
|
||||||
<img src={require('./img/advanced-search-filters.webp').default} width="70%" title='Advanced search filters' />
|
<img src={require('./img/advanced-search-filters.webp').default} width="70%" title='Advanced search filters' />
|
||||||
|
|
||||||
|
### Full path or folder
|
||||||
|
|
||||||
|
Use this mode when you know a folder name or part of the original asset path.
|
||||||
|
|
||||||
|
Example: for /John/Projects/3D_Printing/2026-07-01/IMG_0001.jpg, searches like Projects, 3D, Printing, or 2026 match the asset.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Navigating to `Administration > Settings > Machine Learning Settings > Smart Search` will show the options available.
|
Navigating to `Administration > Settings > Machine Learning Settings > Smart Search` will show the options available.
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ You can learn how to set up Tailscale together with Immich with the [tutorial vi
|
|||||||
### Cons
|
### Cons
|
||||||
|
|
||||||
- The Tailscale client usually needs to run as root on your devices and it increases the attack surface slightly compared to a minimal Wireguard server. e.g., an [RCE vulnerability](https://github.com/tailscale/tailscale/security/advisories/GHSA-vqp6-rc3h-83cp) was discovered in the Windows Tailscale client in November 2022.
|
- The Tailscale client usually needs to run as root on your devices and it increases the attack surface slightly compared to a minimal Wireguard server. e.g., an [RCE vulnerability](https://github.com/tailscale/tailscale/security/advisories/GHSA-vqp6-rc3h-83cp) was discovered in the Windows Tailscale client in November 2022.
|
||||||
- Tailscale is a paid service. However, there is a generous [free tier](https://tailscale.com/pricing/) that permits up to 3 users and up to 100 devices.
|
- Tailscale is a paid service. However, there is a generous [free tier](https://tailscale.com/pricing/) suitable for personal use.
|
||||||
- Tailscale needs to be installed and running on both server-side and client-side.
|
- Tailscale needs to be installed and running on both server-side and client-side.
|
||||||
|
|
||||||
## Option 3: Reverse Proxy
|
## Option 3: Reverse Proxy
|
||||||
|
|||||||
@@ -29,29 +29,31 @@ These environment variables are used by the `docker-compose.yml` file and do **N
|
|||||||
|
|
||||||
## General
|
## General
|
||||||
|
|
||||||
| Variable | Description | Default | Containers | Workers |
|
| Variable | Description | Default | Containers | Workers |
|
||||||
| :---------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
|
| :---------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
|
||||||
| `TZ` | Timezone | <sup>\*1</sup> | server | microservices |
|
| `TZ` | Timezone | <sup>\*1</sup> | server | microservices |
|
||||||
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
|
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
|
||||||
| `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
|
| `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
|
||||||
| `IMMICH_LOG_FORMAT` | Log output format (`console`, `json`) | `console` | server | api, microservices |
|
| `IMMICH_LOG_FORMAT` | Log output format (`console`, `json`) | `console` | server | api, microservices |
|
||||||
| `IMMICH_MEDIA_LOCATION` | Media location inside the container ⚠️**You probably shouldn't set this**<sup>\*2</sup>⚠️ | `/data` | server | api, microservices |
|
| `IMMICH_MEDIA_LOCATION` | Media location inside the container ⚠️**You probably shouldn't set this**<sup>\*2</sup>⚠️ | `/data` | server | api, microservices |
|
||||||
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
|
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
|
||||||
| `IMMICH_HELMET_FILE` | Path to a json file with [helmet](https://www.npmjs.com/package/helmet) options. Set to `false` to disable. Set to `true` to use `server/helmet.json`. | `false` | server | api |
|
| `IMMICH_HELMET_FILE` | Path to a json file with [helmet](https://www.npmjs.com/package/helmet) options. Set to `false` to disable. Set to `true` to use `server/helmet.json`<sup>\*3</sup>. | `false` | server | api |
|
||||||
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
||||||
| `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | |
|
| `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | |
|
||||||
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
|
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
|
||||||
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
|
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
|
||||||
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
|
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
|
||||||
| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api |
|
| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api |
|
||||||
| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices |
|
| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices |
|
||||||
| `IMMICH_ALLOW_SETUP` | When `false` disables the `/auth/admin-sign-up` endpoint | `true` | server | api |
|
| `IMMICH_ALLOW_SETUP` | When `false` disables the `/auth/admin-sign-up` endpoint | `true` | server | api |
|
||||||
|
|
||||||
\*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`.
|
\*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`.
|
||||||
`TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution.
|
`TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution.
|
||||||
|
|
||||||
\*2: This path is where the Immich code looks for the files, which is internal to the docker container. Setting it to a path on your host will certainly break things, you should use the `UPLOAD_LOCATION` variable instead.
|
\*2: This path is where the Immich code looks for the files, which is internal to the docker container. Setting it to a path on your host will certainly break things, you should use the `UPLOAD_LOCATION` variable instead.
|
||||||
|
|
||||||
|
\*3: The [default configuration](https://helmetjs.github.io/#content-security-policy) sets `upgrade-insecure-requests`, which tells the browser to upgrade all requests to HTTPS. This breaks on HTTP-only deployments. If you cannot use HTTPS, you should use a custom helmet config file with `"upgrade-insecure-requests": null`.
|
||||||
|
|
||||||
## Workers
|
## Workers
|
||||||
|
|
||||||
| Variable | Description | Default | Containers |
|
| Variable | Description | Default | Containers |
|
||||||
@@ -81,7 +83,7 @@ Information on the current workers can be found [here](/administration/jobs-work
|
|||||||
| `DB_PASSWORD` | Database password | `postgres` | server, database<sup>\*1</sup> |
|
| `DB_PASSWORD` | Database password | `postgres` | server, database<sup>\*1</sup> |
|
||||||
| `DB_DATABASE_NAME` | Database name | `immich` | server, database<sup>\*1</sup> |
|
| `DB_DATABASE_NAME` | Database name | `immich` | server, database<sup>\*1</sup> |
|
||||||
| `DB_SSL_MODE` | Database SSL mode | | server |
|
| `DB_SSL_MODE` | Database SSL mode | | server |
|
||||||
| `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`vectorchord`, `pgvector`, `pgvecto.rs`]) | | server |
|
| `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`vectorchord`, `pgvector`]) | | server |
|
||||||
| `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server |
|
| `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server |
|
||||||
| `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])<sup>\*3</sup> | `SSD` | database |
|
| `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])<sup>\*3</sup> | `SSD` | database |
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,3 @@ These storage mediums have different performance characteristics. As a result, t
|
|||||||
#### Can I use the new database image as a general PostgreSQL image outside of Immich?
|
#### Can I use the new database image as a general PostgreSQL image outside of Immich?
|
||||||
|
|
||||||
It’s a standard PostgreSQL container image that additionally contains the VectorChord, pgvector, and (optionally) pgvecto.rs extensions. If you were using the previous pgvecto.rs image for other purposes, you can similarly do so with this image.
|
It’s a standard PostgreSQL container image that additionally contains the VectorChord, pgvector, and (optionally) pgvecto.rs extensions. If you were using the previous pgvecto.rs image for other purposes, you can similarly do so with this image.
|
||||||
|
|
||||||
#### If pgvecto.rs and pgvector still work, why should I switch to VectorChord?
|
|
||||||
|
|
||||||
VectorChord is faster, more stable, uses less RAM, and (with the settings Immich uses) offers higher-quality results than pgvector and pgvecto.rs. This translates to better search and facial recognition experiences. In addition, pgvecto.rs support will be dropped in the future, so changing it sooner will avoid disruption.
|
|
||||||
|
|||||||
+4
-42
@@ -2,82 +2,44 @@ import { expect } from 'vitest';
|
|||||||
|
|
||||||
export const errorDto = {
|
export const errorDto = {
|
||||||
unauthorized: {
|
unauthorized: {
|
||||||
error: 'Unauthorized',
|
|
||||||
statusCode: 401,
|
|
||||||
message: 'Authentication required',
|
message: 'Authentication required',
|
||||||
correlationId: expect.any(String),
|
|
||||||
},
|
},
|
||||||
unauthorizedWithMessage: (message: string) => ({
|
unauthorizedWithMessage: (message: string) => ({
|
||||||
error: 'Unauthorized',
|
|
||||||
statusCode: 401,
|
|
||||||
message,
|
message,
|
||||||
correlationId: expect.any(String),
|
|
||||||
}),
|
}),
|
||||||
forbidden: {
|
forbidden: {
|
||||||
error: 'Forbidden',
|
|
||||||
statusCode: 403,
|
|
||||||
message: expect.any(String),
|
message: expect.any(String),
|
||||||
correlationId: expect.any(String),
|
|
||||||
},
|
},
|
||||||
missingPermission: (permission: string) => ({
|
missingPermission: (permission: string) => ({
|
||||||
error: 'Forbidden',
|
|
||||||
statusCode: 403,
|
|
||||||
message: `Missing required permission: ${permission}`,
|
message: `Missing required permission: ${permission}`,
|
||||||
correlationId: expect.any(String),
|
|
||||||
}),
|
}),
|
||||||
wrongPassword: {
|
wrongPassword: {
|
||||||
error: 'Bad Request',
|
|
||||||
statusCode: 400,
|
|
||||||
message: 'Wrong password',
|
message: 'Wrong password',
|
||||||
correlationId: expect.any(String),
|
|
||||||
},
|
},
|
||||||
invalidToken: {
|
invalidToken: {
|
||||||
error: 'Unauthorized',
|
|
||||||
statusCode: 401,
|
|
||||||
message: 'Invalid user token',
|
message: 'Invalid user token',
|
||||||
correlationId: expect.any(String),
|
|
||||||
},
|
},
|
||||||
invalidShareKey: {
|
invalidShareKey: {
|
||||||
error: 'Unauthorized',
|
|
||||||
statusCode: 401,
|
|
||||||
message: 'Invalid share key',
|
message: 'Invalid share key',
|
||||||
correlationId: expect.any(String),
|
|
||||||
},
|
},
|
||||||
passwordRequired: {
|
passwordRequired: {
|
||||||
error: 'Unauthorized',
|
|
||||||
statusCode: 401,
|
|
||||||
message: 'Password required',
|
message: 'Password required',
|
||||||
correlationId: expect.any(String),
|
|
||||||
},
|
},
|
||||||
badRequest: (message: any = null) => ({
|
badRequest: (message: any = null) => ({
|
||||||
error: 'Bad Request',
|
|
||||||
statusCode: 400,
|
|
||||||
message: message ?? expect.anything(),
|
message: message ?? expect.anything(),
|
||||||
correlationId: expect.any(String),
|
}),
|
||||||
|
validationError: (errors?: ReadonlyArray<{ path: ReadonlyArray<string | number>; message: string }>) => ({
|
||||||
|
message: 'Validation failed',
|
||||||
|
errors: errors ? expect.arrayContaining(errors.map((e) => expect.objectContaining(e))) : expect.any(Array),
|
||||||
}),
|
}),
|
||||||
noPermission: {
|
noPermission: {
|
||||||
error: 'Bad Request',
|
|
||||||
statusCode: 400,
|
|
||||||
message: expect.stringContaining('Not found or no'),
|
message: expect.stringContaining('Not found or no'),
|
||||||
correlationId: expect.any(String),
|
|
||||||
},
|
},
|
||||||
incorrectLogin: {
|
incorrectLogin: {
|
||||||
error: 'Unauthorized',
|
|
||||||
statusCode: 401,
|
|
||||||
message: 'Incorrect email or password',
|
message: 'Incorrect email or password',
|
||||||
correlationId: expect.any(String),
|
|
||||||
},
|
},
|
||||||
alreadyHasAdmin: {
|
alreadyHasAdmin: {
|
||||||
error: 'Bad Request',
|
|
||||||
statusCode: 400,
|
|
||||||
message: 'The server already has an admin',
|
message: 'The server already has an admin',
|
||||||
correlationId: expect.any(String),
|
|
||||||
},
|
|
||||||
invalidEmail: {
|
|
||||||
error: 'Bad Request',
|
|
||||||
statusCode: 400,
|
|
||||||
message: ['email must be an email'],
|
|
||||||
correlationId: expect.any(String),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -154,23 +154,31 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedLink,
|
albumName: user1SharedLink,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedEditorUser,
|
albumName: user1SharedEditorUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedViewerUser,
|
albumName: user1SharedViewerUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user2.userId,
|
|
||||||
albumName: user2SharedUser,
|
albumName: user2SharedUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -184,23 +192,31 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedEditorUser,
|
albumName: user1SharedEditorUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedViewerUser,
|
albumName: user1SharedViewerUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedLink,
|
albumName: user1SharedLink,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1NotShared,
|
albumName: user1NotShared,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: false,
|
shared: false,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -216,23 +232,31 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedEditorUser,
|
albumName: user1SharedEditorUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedViewerUser,
|
albumName: user1SharedViewerUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedLink,
|
albumName: user1SharedLink,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user2.userId,
|
|
||||||
albumName: user2SharedUser,
|
albumName: user2SharedUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -248,8 +272,10 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1NotShared,
|
albumName: user1NotShared,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: false,
|
shared: false,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -286,13 +312,17 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user4.userId,
|
|
||||||
albumName: user4DeletedAsset,
|
albumName: user4DeletedAsset,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) },
|
||||||
|
]),
|
||||||
shared: false,
|
shared: false,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user4.userId,
|
|
||||||
albumName: user4Empty,
|
albumName: user4Empty,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) },
|
||||||
|
]),
|
||||||
shared: false,
|
shared: false,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -362,16 +392,17 @@ describe('/albums', () => {
|
|||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual(
|
||||||
...user2Albums[0],
|
expect.objectContaining({
|
||||||
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
assetCount: 1,
|
assetCount: 1,
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
endDate: expect.any(String),
|
endDate: expect.any(String),
|
||||||
startDate: expect.any(String),
|
startDate: expect.any(String),
|
||||||
albumUsers: expect.any(Array),
|
albumUsers: expect.any(Array),
|
||||||
shared: true,
|
shared: true,
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -397,15 +428,13 @@ describe('/albums', () => {
|
|||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
updatedAt: expect.any(String),
|
updatedAt: expect.any(String),
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: 'New album',
|
albumName: 'New album',
|
||||||
description: '',
|
description: '',
|
||||||
albumThumbnailAssetId: null,
|
albumThumbnailAssetId: null,
|
||||||
shared: false,
|
shared: false,
|
||||||
albumUsers: [],
|
albumUsers: [{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }],
|
||||||
hasSharedLink: false,
|
hasSharedLink: false,
|
||||||
assetCount: 0,
|
assetCount: 0,
|
||||||
owner: expect.objectContaining({ email: user1.userEmail }),
|
|
||||||
isActivityEnabled: true,
|
isActivityEnabled: true,
|
||||||
order: AssetOrder.Desc,
|
order: AssetOrder.Desc,
|
||||||
});
|
});
|
||||||
@@ -621,11 +650,11 @@ describe('/albums', () => {
|
|||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
albumUsers: [
|
albumUsers: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
user: expect.objectContaining({ id: user2.userId }),
|
user: expect.objectContaining({ id: user2.userId }),
|
||||||
}),
|
}),
|
||||||
],
|
]),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -637,7 +666,7 @@ describe('/albums', () => {
|
|||||||
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
|
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest('Cannot be shared with owner'));
|
expect(body).toEqual(errorDto.badRequest('User already added'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be able to add existing user to shared album', async () => {
|
it('should not be able to add existing user to shared album', async () => {
|
||||||
@@ -663,7 +692,7 @@ describe('/albums', () => {
|
|||||||
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
|
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
|
expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer);
|
||||||
|
|
||||||
const { status } = await request(app)
|
const { status } = await request(app)
|
||||||
.put(`/albums/${album.id}/user/${user2.userId}`)
|
.put(`/albums/${album.id}/user/${user2.userId}`)
|
||||||
@@ -678,7 +707,10 @@ describe('/albums', () => {
|
|||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
albumUsers: [expect.objectContaining({ role: AlbumUserRole.Editor })],
|
albumUsers: [
|
||||||
|
expect.objectContaining({ role: AlbumUserRole.Owner }),
|
||||||
|
expect.objectContaining({ role: AlbumUserRole.Editor }),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -689,7 +721,7 @@ describe('/albums', () => {
|
|||||||
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
|
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
|
expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer);
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.put(`/albums/${album.id}/user/${user2.userId}`)
|
.put(`/albums/${album.id}/user/${user2.userId}`)
|
||||||
|
|||||||
@@ -110,7 +110,9 @@ describe('/libraries', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[importPaths] Array must have unique items']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['importPaths'], message: 'Array must have unique items' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not create an external library with duplicate exclusion patterns', async () => {
|
it('should not create an external library with duplicate exclusion patterns', async () => {
|
||||||
@@ -125,7 +127,9 @@ describe('/libraries', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array must have unique items']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['exclusionPatterns'], message: 'Array must have unique items' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -157,7 +161,9 @@ describe('/libraries', () => {
|
|||||||
.send({ name: '' });
|
.send({ name: '' });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[name] Too small: expected string to have >=1 characters']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['name'], message: 'Too small: expected string to have >=1 characters' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should change the import paths', async () => {
|
it('should change the import paths', async () => {
|
||||||
@@ -181,7 +187,9 @@ describe('/libraries', () => {
|
|||||||
.send({ importPaths: [''] });
|
.send({ importPaths: [''] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[importPaths] Array items must not be empty']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['importPaths'], message: 'Array items must not be empty' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject duplicate import paths', async () => {
|
it('should reject duplicate import paths', async () => {
|
||||||
@@ -191,7 +199,9 @@ describe('/libraries', () => {
|
|||||||
.send({ importPaths: ['/path', '/path'] });
|
.send({ importPaths: ['/path', '/path'] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[importPaths] Array must have unique items']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['importPaths'], message: 'Array must have unique items' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should change the exclusion pattern', async () => {
|
it('should change the exclusion pattern', async () => {
|
||||||
@@ -215,7 +225,9 @@ describe('/libraries', () => {
|
|||||||
.send({ exclusionPatterns: ['**/*.jpg', '**/*.jpg'] });
|
.send({ exclusionPatterns: ['**/*.jpg', '**/*.jpg'] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array must have unique items']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['exclusionPatterns'], message: 'Array must have unique items' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject an empty exclusion pattern', async () => {
|
it('should reject an empty exclusion pattern', async () => {
|
||||||
@@ -225,7 +237,9 @@ describe('/libraries', () => {
|
|||||||
.send({ exclusionPatterns: [''] });
|
.send({ exclusionPatterns: [''] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array items must not be empty']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['exclusionPatterns'], message: 'Array items must not be empty' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,9 @@ describe('/map', () => {
|
|||||||
.get('/map/reverse-geocode?lon=123')
|
.get('/map/reverse-geocode?lon=123')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[lat] Invalid input: expected number, received NaN']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['lat'], message: 'Invalid input: expected number, received NaN' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if a lat is not a number', async () => {
|
it('should throw an error if a lat is not a number', async () => {
|
||||||
@@ -117,7 +119,9 @@ describe('/map', () => {
|
|||||||
.get('/map/reverse-geocode?lat=abc&lon=123.456')
|
.get('/map/reverse-geocode?lat=abc&lon=123.456')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[lat] Invalid input: expected number, received NaN']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['lat'], message: 'Invalid input: expected number, received NaN' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if a lat is out of range', async () => {
|
it('should throw an error if a lat is out of range', async () => {
|
||||||
@@ -125,7 +129,9 @@ describe('/map', () => {
|
|||||||
.get('/map/reverse-geocode?lat=91&lon=123.456')
|
.get('/map/reverse-geocode?lat=91&lon=123.456')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[lat] Too big: expected number to be <=90']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['lat'], message: 'Too big: expected number to be <=90' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if a lon is not provided', async () => {
|
it('should throw an error if a lon is not provided', async () => {
|
||||||
@@ -133,7 +139,9 @@ describe('/map', () => {
|
|||||||
.get('/map/reverse-geocode?lat=75')
|
.get('/map/reverse-geocode?lat=75')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[lon] Invalid input: expected number, received NaN']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['lon'], message: 'Invalid input: expected number, received NaN' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const reverseGeocodeTestCases = [
|
const reverseGeocodeTestCases = [
|
||||||
|
|||||||
@@ -105,7 +105,11 @@ describe(`/oauth`, () => {
|
|||||||
it(`should throw an error if a redirect uri is not provided`, async () => {
|
it(`should throw an error if a redirect uri is not provided`, async () => {
|
||||||
const { status, body } = await request(app).post('/oauth/authorize').send({});
|
const { status, body } = await request(app).post('/oauth/authorize').send({});
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[redirectUri] Invalid input: expected string, received undefined']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([
|
||||||
|
{ path: ['redirectUri'], message: 'Invalid input: expected string, received undefined' },
|
||||||
|
]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a redirect uri', async () => {
|
it('should return a redirect uri', async () => {
|
||||||
@@ -164,13 +168,17 @@ describe(`/oauth`, () => {
|
|||||||
it(`should throw an error if a url is not provided`, async () => {
|
it(`should throw an error if a url is not provided`, async () => {
|
||||||
const { status, body } = await request(app).post('/oauth/callback').send({});
|
const { status, body } = await request(app).post('/oauth/callback').send({});
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[url] Invalid input: expected string, received undefined']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['url'], message: 'Invalid input: expected string, received undefined' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should throw an error if the url is empty`, async () => {
|
it(`should throw an error if the url is empty`, async () => {
|
||||||
const { status, body } = await request(app).post('/oauth/callback').send({ url: '' });
|
const { status, body } = await request(app).post('/oauth/callback').send({ url: '' });
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[url] Too small: expected string to have >=1 characters']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['url'], message: 'Too small: expected string to have >=1 characters' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should throw an error if the state is not provided`, async () => {
|
it(`should throw an error if the state is not provided`, async () => {
|
||||||
@@ -332,9 +340,7 @@ describe(`/oauth`, () => {
|
|||||||
const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
|
const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
|
||||||
expect(status).toBe(500);
|
expect(status).toBe(500);
|
||||||
expect(body).toMatchObject({
|
expect(body).toMatchObject({
|
||||||
error: 'Internal Server Error',
|
|
||||||
message: 'Failed to finish oauth',
|
message: 'Failed to finish oauth',
|
||||||
statusCode: 500,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -353,7 +359,7 @@ describe(`/oauth`, () => {
|
|||||||
const callbackParams = await loginWithOAuth('oauth-no-auto-register');
|
const callbackParams = await loginWithOAuth('oauth-no-auto-register');
|
||||||
const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
|
const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest('User does not exist and auto registering is disabled.'));
|
expect(body).toEqual(errorDto.badRequest('OAuth authentication failed'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should link to an existing user by email', async () => {
|
it('should link to an existing user by email', async () => {
|
||||||
@@ -377,7 +383,11 @@ describe(`/oauth`, () => {
|
|||||||
it(`should throw an error if the logout_token is not provided`, async () => {
|
it(`should throw an error if the logout_token is not provided`, async () => {
|
||||||
const { status, body } = await request(app).post('/oauth/backchannel-logout').send({});
|
const { status, body } = await request(app).post('/oauth/backchannel-logout').send({});
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[logout_token] Invalid input: expected string, received undefined']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([
|
||||||
|
{ path: ['logout_token'], message: 'Invalid input: expected string, received undefined' },
|
||||||
|
]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should throw an error if an invalid logout token is provided`, async () => {
|
it(`should throw an error if an invalid logout token is provided`, async () => {
|
||||||
@@ -495,11 +505,10 @@ describe(`/oauth`, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should reject OAuth discovery over HTTP', async () => {
|
it('should reject OAuth discovery over HTTP', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status } = await request(app)
|
||||||
.post('/oauth/authorize')
|
.post('/oauth/authorize')
|
||||||
.send({ redirectUri: 'http://127.0.0.1:2285/auth/login' });
|
.send({ redirectUri: 'http://127.0.0.1:2285/auth/login' });
|
||||||
expect(status).toBe(500);
|
expect(status).toBe(500);
|
||||||
expect(body).toMatchObject({ statusCode: 500 });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -341,7 +341,9 @@ describe('/shared-links', () => {
|
|||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest());
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: [], message: 'Invalid input: expected object, received undefined' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require an asset/album id', async () => {
|
it('should require an asset/album id', async () => {
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ describe('/stacks', () => {
|
|||||||
.send({ assetIds: [asset.id] });
|
.send({ assetIds: [asset.id] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest());
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['assetIds'], message: 'Too small: expected array to have >=2 items' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require a valid id', async () => {
|
it('should require a valid id', async () => {
|
||||||
@@ -51,7 +53,12 @@ describe('/stacks', () => {
|
|||||||
.send({ assetIds: [uuidDto.invalid, uuidDto.invalid] });
|
.send({ assetIds: [uuidDto.invalid, uuidDto.invalid] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest());
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([
|
||||||
|
{ path: ['assetIds', 0], message: 'Invalid UUID' },
|
||||||
|
{ path: ['assetIds', 1], message: 'Invalid UUID' },
|
||||||
|
]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require access', async () => {
|
it('should require access', async () => {
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ describe('/tags', () => {
|
|||||||
.get(`/tags/${uuidDto.invalid}`)
|
.get(`/tags/${uuidDto.invalid}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[id] Invalid UUID']));
|
expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get tag details', async () => {
|
it('should get tag details', async () => {
|
||||||
@@ -427,7 +427,7 @@ describe('/tags', () => {
|
|||||||
.delete(`/tags/${uuidDto.invalid}`)
|
.delete(`/tags/${uuidDto.invalid}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[id] Invalid UUID']));
|
expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete a tag', async () => {
|
it('should delete a tag', async () => {
|
||||||
|
|||||||
@@ -108,14 +108,20 @@ describe('/admin/users', () => {
|
|||||||
expect(body).toEqual(errorDto.forbidden);
|
expect(body).toEqual(errorDto.forbidden);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const key of ['password', 'email', 'name', 'quotaSizeInBytes', 'shouldChangePassword', 'notify']) {
|
for (const [key, message] of [
|
||||||
|
['password', 'Invalid input: expected string, received null'],
|
||||||
|
['email', 'Invalid input: expected email, received object'],
|
||||||
|
['name', 'Invalid input: expected string, received null'],
|
||||||
|
['shouldChangePassword', 'Invalid input: expected boolean, received null'],
|
||||||
|
['notify', 'Invalid input: expected boolean, received null'],
|
||||||
|
] as const) {
|
||||||
it(`should not allow null ${key}`, async () => {
|
it(`should not allow null ${key}`, async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.post(`/admin/users`)
|
.post(`/admin/users`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ ...createUserDto.user1, [key]: null });
|
.send({ ...createUserDto.user1, [key]: null });
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest());
|
expect(body).toEqual(errorDto.validationError([{ path: [key], message }]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,14 +159,19 @@ describe('/admin/users', () => {
|
|||||||
expect(body).toEqual(errorDto.forbidden);
|
expect(body).toEqual(errorDto.forbidden);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const key of ['password', 'email', 'name', 'shouldChangePassword']) {
|
for (const [key, message] of [
|
||||||
|
['password', 'Invalid input: expected string, received null'],
|
||||||
|
['email', 'Invalid input: expected email, received object'],
|
||||||
|
['name', 'Invalid input: expected string, received null'],
|
||||||
|
['shouldChangePassword', 'Invalid input: expected boolean, received null'],
|
||||||
|
] as const) {
|
||||||
it(`should not allow null ${key}`, async () => {
|
it(`should not allow null ${key}`, async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.put(`/admin/users/${uuidDto.notFound}`)
|
.put(`/admin/users/${uuidDto.notFound}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ [key]: null });
|
.send({ [key]: null });
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest());
|
expect(body).toEqual(errorDto.validationError([{ path: [key], message }]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ describe('/users', () => {
|
|||||||
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
|
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toMatchObject(errorDto.badRequest('Email already in use by another account'));
|
expect(body).toMatchObject(errorDto.badRequest('Email is not available'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update my email', async () => {
|
it('should update my email', async () => {
|
||||||
@@ -179,7 +179,9 @@ describe('/users', () => {
|
|||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
errorDto.badRequest(['[download.archiveSize] Invalid input: expected int, received number']),
|
errorDto.validationError([
|
||||||
|
{ path: ['download', 'archiveSize'], message: 'Invalid input: expected int, received number' },
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -207,7 +209,9 @@ describe('/users', () => {
|
|||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
errorDto.badRequest(['[download.includeEmbeddedVideos] Invalid input: expected boolean, received number']),
|
errorDto.validationError([
|
||||||
|
{ path: ['download', 'includeEmbeddedVideos'], message: 'Invalid input: expected boolean, received number' },
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,12 @@ export function generateThumbhash(rng: SeededRandom): string {
|
|||||||
return Array.from({ length: 10 }, () => rng.nextInt(0, 256).toString(16).padStart(2, '0')).join('');
|
return Array.from({ length: 10 }, () => rng.nextInt(0, 256).toString(16).padStart(2, '0')).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateDuration(rng: SeededRandom): string {
|
export function generateDuration(rng: SeededRandom): number {
|
||||||
return `${rng.nextInt(GENERATION_CONSTANTS.MIN_VIDEO_DURATION_SECONDS, GENERATION_CONSTANTS.MAX_VIDEO_DURATION_SECONDS)}.${rng.nextInt(0, 1000).toString().padStart(3, '0')}`;
|
return (
|
||||||
|
rng.nextInt(GENERATION_CONSTANTS.MIN_VIDEO_DURATION_SECONDS, GENERATION_CONSTANTS.MAX_VIDEO_DURATION_SECONDS) *
|
||||||
|
1000 +
|
||||||
|
rng.nextInt(0, 1000)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateUUID(): string {
|
export function generateUUID(): string {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AlbumUserRole,
|
||||||
AssetTypeEnum,
|
AssetTypeEnum,
|
||||||
AssetVisibility,
|
AssetVisibility,
|
||||||
UserAvatarColor,
|
UserAvatarColor,
|
||||||
@@ -420,9 +421,7 @@ export function getAlbum(
|
|||||||
albumThumbnailAssetId: album.thumbnailAssetId,
|
albumThumbnailAssetId: album.thumbnailAssetId,
|
||||||
createdAt: album.createdAt,
|
createdAt: album.createdAt,
|
||||||
updatedAt: album.updatedAt,
|
updatedAt: album.updatedAt,
|
||||||
ownerId: albumOwner.id,
|
albumUsers: [{ user: albumOwner, role: AlbumUserRole.Owner }],
|
||||||
owner: albumOwner,
|
|
||||||
albumUsers: [], // Empty array for non-shared album
|
|
||||||
shared: false,
|
shared: false,
|
||||||
hasSharedLink: false,
|
hasSharedLink: false,
|
||||||
isActivityEnabled: true,
|
isActivityEnabled: true,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export type MockTimelineAsset = {
|
|||||||
isTrashed: boolean;
|
isTrashed: boolean;
|
||||||
isVideo: boolean;
|
isVideo: boolean;
|
||||||
isImage: boolean;
|
isImage: boolean;
|
||||||
duration: string | null;
|
duration: number | null;
|
||||||
projectionType: string | null;
|
projectionType: string | null;
|
||||||
livePhotoVideoId: string | null;
|
livePhotoVideoId: string | null;
|
||||||
city: string | null;
|
city: string | null;
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ test.describe('Timeline', () => {
|
|||||||
await page.keyboard.down('Shift');
|
await page.keyboard.down('Shift');
|
||||||
await thumbnailUtils.withAssetId(page, assets[2].id).hover();
|
await thumbnailUtils.withAssetId(page, assets[2].id).hover();
|
||||||
await expect(
|
await expect(
|
||||||
thumbnailUtils.locator(page).locator('.absolute.top-0.h-full.w-full.bg-immich-primary.opacity-40'),
|
thumbnailUtils.locator(page).locator('.absolute.top-0.size-full.bg-immich-primary.opacity-40'),
|
||||||
).toHaveCount(3);
|
).toHaveCount(3);
|
||||||
await thumbnailUtils.selectButton(page, assets[2].id).click();
|
await thumbnailUtils.selectButton(page, assets[2].id).click();
|
||||||
await page.keyboard.up('Shift');
|
await page.keyboard.up('Shift');
|
||||||
|
|||||||
+1
-1
Submodule e2e/test-assets updated: 0eac5a3738...6742055402
@@ -1240,6 +1240,7 @@
|
|||||||
"free_up_space_description": "Move backed-up photos and videos to your device's trash to free up space. Your copies on the server remain safe.",
|
"free_up_space_description": "Move backed-up photos and videos to your device's trash to free up space. Your copies on the server remain safe.",
|
||||||
"free_up_space_settings_subtitle": "Free up device storage",
|
"free_up_space_settings_subtitle": "Free up device storage",
|
||||||
"full_path": "Full path: {path}",
|
"full_path": "Full path: {path}",
|
||||||
|
"full_path_or_folder": "Full path or folder",
|
||||||
"gcast_enabled": "Google Cast",
|
"gcast_enabled": "Google Cast",
|
||||||
"gcast_enabled_description": "This feature loads external resources from Google in order to work.",
|
"gcast_enabled_description": "This feature loads external resources from Google in order to work.",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
@@ -1523,6 +1524,38 @@
|
|||||||
"marked_all_as_read": "Marked all as read",
|
"marked_all_as_read": "Marked all as read",
|
||||||
"matches": "Matches",
|
"matches": "Matches",
|
||||||
"matching_assets": "Matching Assets",
|
"matching_assets": "Matching Assets",
|
||||||
|
"media_chrome": {
|
||||||
|
"auto": "Auto",
|
||||||
|
"captions": "Captions",
|
||||||
|
"captions_off": "Off",
|
||||||
|
"closed_captions": "closed captions",
|
||||||
|
"decode_error": "Decode error",
|
||||||
|
"disable_captions": "Disable captions",
|
||||||
|
"enable_captions": "Enable captions",
|
||||||
|
"enter_fullscreen_mode": "Enter fullscreen mode",
|
||||||
|
"exit_fullscreen_mode": "Exit fullscreen mode",
|
||||||
|
"loop": "Loop",
|
||||||
|
"media_error_description": "A media error caused playback to be aborted. The media could be corrupt or your browser does not support this format.",
|
||||||
|
"media_loading": "media loading",
|
||||||
|
"mute": "Mute",
|
||||||
|
"network_error": "Network error",
|
||||||
|
"network_error_description": "A network error caused the media download to fail.",
|
||||||
|
"not_supported_error": "Source Not Supported",
|
||||||
|
"playback_rate": "Playback rate",
|
||||||
|
"playback_rate_current": "current playback rate",
|
||||||
|
"playback_rate_value": "Playback rate {playbackRate}",
|
||||||
|
"playback_time": "playback time",
|
||||||
|
"quality": "Quality",
|
||||||
|
"second": "second",
|
||||||
|
"seconds": "seconds",
|
||||||
|
"time_value_of_total_time": "{currentTime} of {totalTime}",
|
||||||
|
"time_value_remaining": "{time} remaining",
|
||||||
|
"unmute": "Unmute",
|
||||||
|
"unsupported_error_description": "An unsupported error occurred. The server or network failed, or your browser does not support this format.",
|
||||||
|
"video_not_loaded_unknown_time": "video not loaded, unknown time.",
|
||||||
|
"video_player": "video player",
|
||||||
|
"volume": "volume"
|
||||||
|
},
|
||||||
"media_type": "Media type",
|
"media_type": "Media type",
|
||||||
"memories": "Memories",
|
"memories": "Memories",
|
||||||
"memories_all_caught_up": "All caught up",
|
"memories_all_caught_up": "All caught up",
|
||||||
@@ -1942,6 +1975,8 @@
|
|||||||
"search_by_description_example": "Hiking day in Sapa",
|
"search_by_description_example": "Hiking day in Sapa",
|
||||||
"search_by_filename": "Search by file name or extension",
|
"search_by_filename": "Search by file name or extension",
|
||||||
"search_by_filename_example": "i.e. IMG_1234.JPG or PNG",
|
"search_by_filename_example": "i.e. IMG_1234.JPG or PNG",
|
||||||
|
"search_by_full_path": "Search by full path or folder",
|
||||||
|
"search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - you can search for Projects, 3D, Printing, 2026 etc.",
|
||||||
"search_by_ocr": "Search by OCR",
|
"search_by_ocr": "Search by OCR",
|
||||||
"search_by_ocr_example": "Latte",
|
"search_by_ocr_example": "Latte",
|
||||||
"search_camera_lens_model": "Search lens model...",
|
"search_camera_lens_model": "Search lens model...",
|
||||||
@@ -2436,6 +2471,7 @@
|
|||||||
"workflows": "Workflows",
|
"workflows": "Workflows",
|
||||||
"workflows_help_text": "Workflows automate actions on your assets based on triggers and filters",
|
"workflows_help_text": "Workflows automate actions on your assets based on triggers and filters",
|
||||||
"wrong_pin_code": "Wrong PIN code",
|
"wrong_pin_code": "Wrong PIN code",
|
||||||
|
"x_of_total": "{x}/{total}",
|
||||||
"year": "Year",
|
"year": "Year",
|
||||||
"years_ago": "{years, plural, one {# year} other {# years}} ago",
|
"years_ago": "{years, plural, one {# year} other {# years}} ago",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
|
|||||||
@@ -48,14 +48,14 @@ FROM python:3.13-slim-trixie@sha256:d168b8d9eb761f4d3fe305ebd04aeb7e7f2de0297cec
|
|||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
|
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-core-2_2.28.4+20760_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.32.7/intel-igc-core-2_2.32.7+21184_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-opencl-2_2.28.4+20760_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.32.7/intel-igc-opencl-2_2.32.7+21184_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/intel-opencl-icd_26.05.37020.3-0_amd64.deb && \
|
wget -nv https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/intel-opencl-icd_26.14.37833.4-0_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \
|
wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \
|
||||||
# TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file
|
# TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file
|
||||||
wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/libigdgmm12_22.9.0_amd64.deb && \
|
wget -nv https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/libigdgmm12_22.9.0_amd64.deb && \
|
||||||
dpkg -i *.deb && \
|
dpkg -i *.deb && \
|
||||||
rm *.deb && \
|
rm *.deb && \
|
||||||
apt-get remove wget -yqq && \
|
apt-get remove wget -yqq && \
|
||||||
@@ -68,7 +68,7 @@ ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
|
|||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
# Pascal support was dropped in 9.11
|
# Pascal support was dropped in 9.11
|
||||||
apt-get install --no-install-recommends -yqq libcudnn9-cuda-12=9.10.2.21-1 && \
|
apt-get install --no-install-recommends -yqq libcudnn9-cuda-12=9.10.2.21-1 tzdata && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ ARG RKNN_TOOLKIT_VERSION="v2.3.0"
|
|||||||
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
|
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
|
||||||
MACHINE_LEARNING_MODEL_ARENA=false
|
MACHINE_LEARNING_MODEL_ARENA=false
|
||||||
|
|
||||||
ADD --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 "https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/${RKNN_TOOLKIT_VERSION}/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" /usr/lib/
|
ADD --chmod=644 --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 "https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/${RKNN_TOOLKIT_VERSION}/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" /usr/lib/
|
||||||
|
|
||||||
FROM prod-${DEVICE} AS prod
|
FROM prod-${DEVICE} AS prod
|
||||||
|
|
||||||
|
|||||||
@@ -183,7 +183,10 @@ async def predict(
|
|||||||
text: str | None = Form(default=None),
|
text: str | None = Form(default=None),
|
||||||
) -> Any:
|
) -> Any:
|
||||||
if image is not None:
|
if image is not None:
|
||||||
inputs: Image | str = await run(lambda: decode_pil(image))
|
decoded = await run(lambda: decode_pil(image))
|
||||||
|
if decoded.width == 0 or decoded.height == 0:
|
||||||
|
raise HTTPException(400, "Image has zero width or height")
|
||||||
|
inputs: Image | str = decoded
|
||||||
elif text is not None:
|
elif text is not None:
|
||||||
inputs = text
|
inputs = text
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[tools]
|
||||||
|
python = "3.11"
|
||||||
|
uv = "0.8.15"
|
||||||
|
|
||||||
|
[tasks.install]
|
||||||
|
run = "uv sync --locked"
|
||||||
|
|
||||||
|
[tasks.lint]
|
||||||
|
run = "uv run ruff check immich_ml"
|
||||||
|
|
||||||
|
[tasks.test]
|
||||||
|
run = "uv run pytest --cov=immich_ml --cov-report term-missing"
|
||||||
|
|
||||||
|
[tasks.format]
|
||||||
|
run = "uv run ruff format immich_ml"
|
||||||
|
|
||||||
|
[tasks.check]
|
||||||
|
run = "uv run mypy --strict immich_ml/"
|
||||||
@@ -9,12 +9,12 @@ dependencies = [
|
|||||||
"aiocache>=0.12.1,<1.0",
|
"aiocache>=0.12.1,<1.0",
|
||||||
"fastapi>=0.95.2,<1.0",
|
"fastapi>=0.95.2,<1.0",
|
||||||
"gunicorn>=21.1.0",
|
"gunicorn>=21.1.0",
|
||||||
"huggingface-hub>=0.20.1,<1.0",
|
"huggingface-hub>=1.0,<2.0",
|
||||||
"insightface>=0.7.3,<1.0",
|
"insightface>=0.7.3,<1.0",
|
||||||
"numpy<2.4.0",
|
"numpy<2.4.0",
|
||||||
"opencv-python-headless>=4.7.0.72,<5.0",
|
"opencv-python-headless>=4.7.0.72,<5.0",
|
||||||
"orjson>=3.9.5",
|
"orjson>=3.9.5",
|
||||||
"pillow>=12.2,<12.3",
|
"pillow>=12.2,<13",
|
||||||
"pydantic>=2.0.0,<3",
|
"pydantic>=2.0.0,<3",
|
||||||
"pydantic-settings>=2.5.2,<3",
|
"pydantic-settings>=2.5.2,<3",
|
||||||
"python-multipart>=0.0.6,<1.0",
|
"python-multipart>=0.0.6,<1.0",
|
||||||
|
|||||||
@@ -1198,6 +1198,19 @@ class TestLoad:
|
|||||||
mock_model.model_format = ModelFormat.ONNX
|
mock_model.model_format = ModelFormat.ONNX
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("size", [(0, 100), (100, 0), (0, 0)])
|
||||||
|
def test_predict_rejects_empty_image(size: tuple[int, int], deployed_app: TestClient) -> None:
|
||||||
|
with mock.patch("immich_ml.main.decode_pil", return_value=Image.new("RGB", size)):
|
||||||
|
response = deployed_app.post(
|
||||||
|
"http://localhost:3003/predict",
|
||||||
|
data={"entries": json.dumps({"clip": {"visual": {"modelName": "ViT-B-32__openai"}}})},
|
||||||
|
files={"image": b"fake image bytes"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert "zero" in response.json()["detail"].lower()
|
||||||
|
|
||||||
|
|
||||||
def test_root_endpoint(deployed_app: TestClient) -> None:
|
def test_root_endpoint(deployed_app: TestClient) -> None:
|
||||||
response = deployed_app.get("http://localhost:3003")
|
response = deployed_app.get("http://localhost:3003")
|
||||||
|
|
||||||
|
|||||||
Generated
+61
-19
@@ -243,14 +243,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "click"
|
||||||
version = "8.1.7"
|
version = "8.3.3"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" },
|
{ url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -785,17 +785,34 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hf-xet"
|
name = "hf-xet"
|
||||||
version = "1.1.7"
|
version = "1.5.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/0a/a0f56735940fde6dd627602fec9ab3bad23f66a272397560abd65aba416e/hf_xet-1.1.7.tar.gz", hash = "sha256:20cec8db4561338824a3b5f8c19774055b04a8df7fff0cb1ff2cb1a0c1607b80", size = 477719, upload-time = "2025-08-06T00:30:55.741Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/74/d8/5c06fc76461418326a7decf8367480c35be11a41fd938633929c60a9ec6b/hf_xet-1.5.0.tar.gz", hash = "sha256:e0fb0a34d9f406eed88233e829a67ec016bec5af19e480eac65a233ea289a948", size = 837196, upload-time = "2026-05-06T06:18:15.583Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/b1/7c/8d7803995caf14e7d19a392a486a040f923e2cfeff824e9b800b92072f76/hf_xet-1.1.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:60dae4b44d520819e54e216a2505685248ec0adbdb2dd4848b17aa85a0375cde", size = 2761743, upload-time = "2025-08-06T00:30:50.634Z" },
|
{ url = "https://files.pythonhosted.org/packages/68/9b/6912c99070915a4f28119e3c5b52a9abd1eec0ad5cb293b8c967a0c6f5a2/hf_xet-1.5.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:7d70fe2ce97b9db73b9c9b9c81fe3693640aec83416a966c446afea54acfae3c", size = 4023383, upload-time = "2026-05-06T06:17:53.947Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/51/a3/fa5897099454aa287022a34a30e68dbff0e617760f774f8bd1db17f06bd4/hf_xet-1.1.7-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b109f4c11e01c057fc82004c9e51e6cdfe2cb230637644ade40c599739067b2e", size = 2624331, upload-time = "2025-08-06T00:30:49.212Z" },
|
{ url = "https://files.pythonhosted.org/packages/0f/6d/9563cfde59b5d8128a9c7ec972a087f4c782e4f7bac5a85234edfd5d5e49/hf_xet-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:73a0dae8c71de3b0633a45c73f4a4a5ed09e94b43441d82981a781d4f12baa42", size = 3792751, upload-time = "2026-05-06T06:17:51.791Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/86/50/2446a132267e60b8a48b2e5835d6e24fd988000d0f5b9b15ebd6d64ef769/hf_xet-1.1.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efaaf1a5a9fc3a501d3e71e88a6bfebc69ee3a716d0e713a931c8b8d920038f", size = 3183844, upload-time = "2025-08-06T00:30:47.582Z" },
|
{ url = "https://files.pythonhosted.org/packages/07/a5/ed5a0cf35b49a0571af5a8f53416dad1877a718c021c9937c3a53cb45781/hf_xet-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a60290ec57e9b71767fba7c3645ddafdd0759974b540441510c629c6db6db24a", size = 4456058, upload-time = "2026-05-06T06:17:40.735Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/8f/ccc670616bb9beee867c6bb7139f7eab2b1370fe426503c25f5cbb27b148/hf_xet-1.1.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:751571540f9c1fbad9afcf222a5fb96daf2384bf821317b8bfb0c59d86078513", size = 3074209, upload-time = "2025-08-06T00:30:45.509Z" },
|
{ url = "https://files.pythonhosted.org/packages/60/fb/3ae8bf2a7a37a4197d0195d7247fd25b3952e15cb8a599e285dfaa6f52b3/hf_xet-1.5.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e5de0f6deada0dada870bb376a11bcd1f08abf3a968a6d118f33e72d1b1eb480", size = 4250783, upload-time = "2026-05-06T06:17:38.412Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/21/0a/4c30e1eb77205565b854f5e4a82cf1f056214e4dc87f2918ebf83d47ae14/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:18b61bbae92d56ae731b92087c44efcac216071182c603fc535f8e29ec4b09b8", size = 3239602, upload-time = "2025-08-06T00:30:52.41Z" },
|
{ url = "https://files.pythonhosted.org/packages/a2/9b/8bae40d4d91525085137196e84eb0ed49cf65b5e96e5c3ecdadd8bd0fac2/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c799d49f1a5544a0ef7591c0ee75e0d6b93d6f56dc7a4979f59f7518d2872216", size = 4445594, upload-time = "2026-05-06T06:18:04.219Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/1e/fc7e9baf14152662ef0b35fa52a6e889f770a7ed14ac239de3c829ecb47e/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:713f2bff61b252f8523739969f247aa354ad8e6d869b8281e174e2ea1bb8d604", size = 3348184, upload-time = "2025-08-06T00:30:54.105Z" },
|
{ url = "https://files.pythonhosted.org/packages/13/59/c74efbbd4e8728172b2cc72a2bc014d2947a4b7bdced932fbd3f5da1a4e5/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2baea1b0b989e5c152fe81425f7745ddc8901280ba3d97c98d8cdece7b706c60", size = 4663995, upload-time = "2026-05-06T06:18:06.1Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/73/e354eae84ceff117ec3560141224724794828927fcc013c5b449bf0b8745/hf_xet-1.1.7-cp37-abi3-win_amd64.whl", hash = "sha256:2e356da7d284479ae0f1dea3cf5a2f74fdf925d6dca84ac4341930d892c7cb34", size = 2820008, upload-time = "2025-08-06T00:30:57.056Z" },
|
{ url = "https://files.pythonhosted.org/packages/73/32/8e1e0410af64cda9b139d1dcebdc993a8ff9c8c7c0e2696ae356d75ccc0d/hf_xet-1.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:526345b3ed45f374f6317349df489167606736c876241ba984105afe7fd4839d", size = 3966608, upload-time = "2026-05-06T06:18:19.74Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/34/a8febc8f4edbea8b3e21b02ebc8b628679b84ba7e45cde624a7736b51500/hf_xet-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:786d28e2eb8315d5035544b9d137b4a842d600c434bb91bf7d0d953cce906ad4", size = 3796946, upload-time = "2026-05-06T06:18:17.568Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/20/8fc8996afe5815fa1a6be8e9e5c02f24500f409d599e905800d498a4e14d/hf_xet-1.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:872d5601e6deea30d15865ede55d29eac6daf5a534ab417b99b6ef6b076dd96c", size = 4023495, upload-time = "2026-05-06T06:18:01.94Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/6a/93d84463c00cecb561a7508aa6303e35ee2894294eac14245526924415fe/hf_xet-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9929561f5abf4581c8ea79587881dfef6b8abb2a0d8a51915936fc2a614f4e73", size = 3792731, upload-time = "2026-05-06T06:18:00.021Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/5a/8ec8e0c863b382d00b3c2e2af6ded6b06371be617144a625903a6d562f4b/hf_xet-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7b7bbae318e583a86fb21e5a4a175d6721d628a2874f4bd022d0e660c32a682", size = 4456738, upload-time = "2026-05-06T06:17:49.574Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/ca/f7effa1a67717da2bcc6b6c28f71c6ca648c77acaec4e2c32f40cbe16d85/hf_xet-1.5.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cf7b2dc6f31a4ea754bb50f74cde482dcf5d366d184076d8530b9872787f3761", size = 4251622, upload-time = "2026-05-06T06:17:47.096Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/f2/19247dba3e231cf77dec59ddfb878f00057635ff773d099c9b59d37812c3/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8dbcbab554c9ef158ef2c991545c3e970ddd8cc7acdcd0a78c5a41095dab4ded", size = 4445667, upload-time = "2026-05-06T06:18:11.983Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/64/6f116801a3bcfb6f59f5c251f48cadc47ea54026441c4a385079286a94fa/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5906bf7718d3636dc13402914736abe723492cb730f744834f5f5b67d3a12702", size = 4664619, upload-time = "2026-05-06T06:18:13.771Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/e8/069542d37946ed08669b127e1496fa99e78196d71de8d41eda5e9f1b7a58/hf_xet-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5f3dc2248fc01cc0a00cd392ab497f1ca373fcbc7e3f2da1f452480b384e839e", size = 3966802, upload-time = "2026-05-06T06:18:28.162Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/91/fc6fdec27b14d04e88c386ac0a0129732b53fa23f7c4a78f4b83a039c567/hf_xet-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b285cea1b5bab46b758772716ba8d6854a1a0310fed1c249d678a8b38601e5a0", size = 3797168, upload-time = "2026-05-06T06:18:26.287Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/fb/69ff198a82cae7eb1a69fb84d93b3a3e4816564d76817fe541ddc96874eb/hf_xet-1.5.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dad0dc84e941b8ba3c860659fe1fdc35c049d47cce293f003287757e971a8f56", size = 4030814, upload-time = "2026-05-06T06:17:57.933Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/ff/edcc2b40162bef3ff78e14ab637e5f3b89243d6aee72f5949d3bb6a5af83/hf_xet-1.5.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fd6e5a9b0fdac4ed03ed45ef79254a655b1aaab514a02202617fbf643f5fdf7a", size = 3798444, upload-time = "2026-05-06T06:17:55.79Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/4d/103f76b04310e5e57656696cc184690d20c466af0bca3ca88f8c8ea5d4f3/hf_xet-1.5.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3531b1823a0e6d77d80f9ed15ca0e00f0d115094f8ac033d5cae88f4564cc949", size = 4465986, upload-time = "2026-05-06T06:17:44.886Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/a2/546f47f464737b3edbab6f8ddb57f2599b93d2cbb66f06abb475ccb48651/hf_xet-1.5.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9a0ee58cd18d5ea799f7ed11290bbccbe56bdd8b1d97ca74b9cc49a3945d7a3b", size = 4259865, upload-time = "2026-05-06T06:17:42.639Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/95/7f/1be593c1f28613be2e196473481cd81bfc5910795e30a34e8f744f6cac4f/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e60df5a42e9bed8628b6416af2cba4cba57ae9f02de226a06b020d98e1aab18", size = 4459835, upload-time = "2026-05-06T06:18:08.026Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/b2/703569fc881f3284487e68cda7b42179978480da3c438042a6bbbb4a671c/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4b35549ce62601b84da4ff9b24d970032ace3d4430f52d91bcbb26c901d6c690", size = 4672414, upload-time = "2026-05-06T06:18:09.864Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/af/37/1b6def445c567286b50aa3b33828158e135b1be44938dde59f11382a500c/hf_xet-1.5.0-cp37-abi3-win_amd64.whl", hash = "sha256:2806c7c17b4d23f8d88f7c4814f838c3b6150773fe339c20af23e1cfaf2797e4", size = 3977238, upload-time = "2026-05-06T06:18:23.621Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/94/3b66b148778ee100dcfd69c2ca22b57b41b44d3063ceec934f209e9184ce/hf_xet-1.5.0-cp37-abi3-win_arm64.whl", hash = "sha256:b6c9df403040248c76d808d3e047d64db2d923bae593eb244c41e425cf6cd7be", size = 3806916, upload-time = "2026-05-06T06:18:21.7Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -857,21 +874,22 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "huggingface-hub"
|
name = "huggingface-hub"
|
||||||
version = "0.36.2"
|
version = "1.13.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "filelock" },
|
{ name = "filelock" },
|
||||||
{ name = "fsspec" },
|
{ name = "fsspec" },
|
||||||
{ name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
|
{ name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
|
||||||
|
{ name = "httpx" },
|
||||||
{ name = "packaging" },
|
{ name = "packaging" },
|
||||||
{ name = "pyyaml" },
|
{ name = "pyyaml" },
|
||||||
{ name = "requests" },
|
|
||||||
{ name = "tqdm" },
|
{ name = "tqdm" },
|
||||||
|
{ name = "typer" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782, upload-time = "2026-02-06T09:24:13.098Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/89/ff/ec7ed2eb43bd7ce8bb2233d109cc235c3e807ffe5e469dc09db261fac05e/huggingface_hub-1.13.0.tar.gz", hash = "sha256:f6df2dac5abe82ce2fe05873d10d5ff47bc677d616a2f521f4ee26db9415d9d0", size = 781788, upload-time = "2026-04-30T11:57:33.858Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" },
|
{ url = "https://files.pythonhosted.org/packages/93/db/4b1cdae9460ae1f3ca020cd767f013430ce23eb1d9c890ae3a0609b38d26/huggingface_hub-1.13.0-py3-none-any.whl", hash = "sha256:e942cb50d6a08dd5306688b1ac05bda157fd2fcc88b63dae405f7bd0d3234005", size = 660643, upload-time = "2026-04-30T11:57:31.802Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -985,7 +1003,7 @@ requires-dist = [
|
|||||||
{ name = "aiocache", specifier = ">=0.12.1,<1.0" },
|
{ name = "aiocache", specifier = ">=0.12.1,<1.0" },
|
||||||
{ name = "fastapi", specifier = ">=0.95.2,<1.0" },
|
{ name = "fastapi", specifier = ">=0.95.2,<1.0" },
|
||||||
{ name = "gunicorn", specifier = ">=21.1.0" },
|
{ name = "gunicorn", specifier = ">=21.1.0" },
|
||||||
{ name = "huggingface-hub", specifier = ">=0.20.1,<1.0" },
|
{ name = "huggingface-hub", specifier = ">=1.0,<2.0" },
|
||||||
{ name = "insightface", specifier = ">=0.7.3,<1.0" },
|
{ name = "insightface", specifier = ">=0.7.3,<1.0" },
|
||||||
{ name = "numpy", specifier = "<2.4.0" },
|
{ name = "numpy", specifier = "<2.4.0" },
|
||||||
{ name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" },
|
||||||
@@ -996,7 +1014,7 @@ requires-dist = [
|
|||||||
{ name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.24.1,<2" },
|
{ name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.24.1,<2" },
|
||||||
{ name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" },
|
{ name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" },
|
||||||
{ name = "orjson", specifier = ">=3.9.5" },
|
{ name = "orjson", specifier = ">=3.9.5" },
|
||||||
{ name = "pillow", specifier = ">=12.2,<12.3" },
|
{ name = "pillow", specifier = ">=12.2,<13" },
|
||||||
{ name = "pydantic", specifier = ">=2.0.0,<3" },
|
{ name = "pydantic", specifier = ">=2.0.0,<3" },
|
||||||
{ name = "pydantic-settings", specifier = ">=2.5.2,<3" },
|
{ name = "pydantic-settings", specifier = ">=2.5.2,<3" },
|
||||||
{ name = "python-multipart", specifier = ">=0.0.6,<1.0" },
|
{ name = "python-multipart", specifier = ">=0.0.6,<1.0" },
|
||||||
@@ -2769,6 +2787,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ea/f1/5e9b3ba5c7aa7ebfaf269657e728067d16a7c99401c7973ddf5f0cf121bd/shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7", size = 1723061, upload-time = "2025-05-19T11:04:40.082Z" },
|
{ url = "https://files.pythonhosted.org/packages/ea/f1/5e9b3ba5c7aa7ebfaf269657e728067d16a7c99401c7973ddf5f0cf121bd/shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7", size = 1723061, upload-time = "2025-05-19T11:04:40.082Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shellingham"
|
||||||
|
version = "1.5.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simple-websocket"
|
name = "simple-websocket"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -2932,6 +2959,21 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/d1/ad/7d47bbf2cae78ff79f29db0bed5016ec9c56b212a93fca624bb88b551a7c/tqdm-4.66.3-py3-none-any.whl", hash = "sha256:4f41d54107ff9a223dca80b53efe4fb654c67efaba7f47bada3ee9d50e05bd53", size = 78374, upload-time = "2024-05-02T21:44:01.541Z" },
|
{ url = "https://files.pythonhosted.org/packages/d1/ad/7d47bbf2cae78ff79f29db0bed5016ec9c56b212a93fca624bb88b551a7c/tqdm-4.66.3-py3-none-any.whl", hash = "sha256:4f41d54107ff9a223dca80b53efe4fb654c67efaba7f47bada3ee9d50e05bd53", size = 78374, upload-time = "2024-05-02T21:44:01.541Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typer"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "annotated-doc" },
|
||||||
|
{ name = "click" },
|
||||||
|
{ name = "rich" },
|
||||||
|
{ name = "shellingham" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types-pyyaml"
|
name = "types-pyyaml"
|
||||||
version = "6.0.12.20260408"
|
version = "6.0.12.20260408"
|
||||||
|
|||||||
@@ -11,21 +11,31 @@ config_roots = [
|
|||||||
"web",
|
"web",
|
||||||
"docs",
|
"docs",
|
||||||
".github",
|
".github",
|
||||||
|
"machine-learning",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
node = "24.15.0"
|
node = "24.15.0"
|
||||||
flutter = "3.41.6"
|
flutter = "3.41.7"
|
||||||
pnpm = "10.33.0"
|
pnpm = "10.33.1"
|
||||||
terragrunt = "1.0.1"
|
terragrunt = "1.0.3"
|
||||||
opentofu = "1.11.6"
|
opentofu = "1.11.6"
|
||||||
java = "21.0.2"
|
java = "21.0.2"
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"]
|
[tools."github:CQLabs/homebrew-dcm"]
|
||||||
version = "1.35.1"
|
version = "1.37.0"
|
||||||
bin = "dcm"
|
bin = "dcm"
|
||||||
postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm"
|
postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm"
|
||||||
|
|
||||||
|
[tools."github:jellyfin/jellyfin-ffmpeg"]
|
||||||
|
version = "7.1.3-6"
|
||||||
|
|
||||||
|
[tools."github:jellyfin/jellyfin-ffmpeg".platforms]
|
||||||
|
linux-x64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_linux64-gpl.tar.xz" }
|
||||||
|
linux-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_linuxarm64-gpl.tar.xz" }
|
||||||
|
macos-x64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_mac64-gpl.tar.xz" }
|
||||||
|
macos-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz" }
|
||||||
|
|
||||||
[settings]
|
[settings]
|
||||||
experimental = true
|
experimental = true
|
||||||
pin = true
|
pin = true
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dart.flutterSdkPath": ".fvm/versions/3.41.7",
|
"dart.flutterSdkPath": ".fvm/versions/3.41.9",
|
||||||
"dart.lineLength": 120,
|
"dart.lineLength": 120,
|
||||||
"[dart]": {
|
"[dart]": {
|
||||||
"editor.rulers": [
|
"editor.rulers": [
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 3046,
|
"android.injected.version.code" => 3047,
|
||||||
"android.injected.version.name" => "2.7.5",
|
"android.injected.version.name" => "3.0.0",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
version: '>=1.29.0 <=1.36.0'
|
version: '>=1.29.0 <=1.37.0'
|
||||||
|
|||||||
+3301
File diff suppressed because it is too large
Load Diff
+3358
File diff suppressed because it is too large
Load Diff
@@ -751,7 +751,7 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
@@ -760,7 +760,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.121.0;
|
MARKETING_VERSION = 1.121.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile;
|
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.profile;
|
||||||
PRODUCT_NAME = "Immich-Profile";
|
PRODUCT_NAME = "Immich-Profile";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
@@ -895,7 +895,7 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
@@ -904,7 +904,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.121.0;
|
MARKETING_VERSION = 1.121.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug;
|
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.debug;
|
||||||
PRODUCT_NAME = "Immich-Debug";
|
PRODUCT_NAME = "Immich-Debug";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
@@ -925,7 +925,7 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
@@ -958,7 +958,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -975,7 +975,7 @@
|
|||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.Widget;
|
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.debug.Widget;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
@@ -1001,7 +1001,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1041,7 +1041,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1057,7 +1057,7 @@
|
|||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.Widget;
|
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.profile.Widget;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -1081,7 +1081,7 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1098,7 +1098,7 @@
|
|||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.ShareExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.debug.ShareExtension;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -1125,7 +1125,7 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1166,7 +1166,7 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1182,7 +1182,7 @@
|
|||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.ShareExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.profile.ShareExtension;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.7.5</string>
|
<string>3.0.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>AppGroupId</key>
|
<key>AppGroupId</key>
|
||||||
<string>$(CUSTOM_GROUP_ID)</string>
|
<string>$(CUSTOM_GROUP_ID)</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionAttributes</key>
|
<key>NSExtensionAttributes</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>IntentsSupported</key>
|
<key>IntentsSupported</key>
|
||||||
<array>
|
<array>
|
||||||
<string>INSendMessageIntent</string>
|
<string>INSendMessageIntent</string>
|
||||||
</array>
|
</array>
|
||||||
<key>NSExtensionActivationRule</key>
|
<key>NSExtensionActivationRule</key>
|
||||||
<string>SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments,
|
<string>SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments,
|
||||||
$attachment, ( ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url"
|
$attachment, ( ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url"
|
||||||
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" || ANY
|
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" || ANY
|
||||||
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" || ANY
|
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" || ANY
|
||||||
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" || ANY
|
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" || ANY
|
||||||
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ) ).@count > 0
|
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ) ).@count > 0
|
||||||
).@count > 0 </string>
|
).@count > 0 </string>
|
||||||
<key>PHSupportedMediaTypes</key>
|
<key>PHSupportedMediaTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>Video</string>
|
<string>Video</string>
|
||||||
<string>Image</string>
|
<string>Image</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSExtensionMainStoryboard</key>
|
<key>NSExtensionMainStoryboard</key>
|
||||||
<string>MainInterface</string>
|
<string>MainInterface</string>
|
||||||
<key>NSExtensionPointIdentifier</key>
|
<key>NSExtensionPointIdentifier</key>
|
||||||
<string>com.apple.share-services</string>
|
<string>com.apple.share-services</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.app.immich.share</string>
|
<string>group.app.immich.share</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.app.immich.share</string>
|
<string>group.app.immich.share</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
app_identifier "app.alextran.immich" # The bundle identifier of your app
|
app_identifier "app.alextran.immich" # The bundle identifier of your app
|
||||||
apple_id "alex.tran1502@gmail.com" # Your Apple email address
|
apple_id "altran@futo.org" # Your Apple email address
|
||||||
|
|
||||||
|
|
||||||
# For more information about the Appfile, see:
|
# For more information about the Appfile, see:
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ default_platform(:ios)
|
|||||||
|
|
||||||
platform :ios do
|
platform :ios do
|
||||||
# Constants
|
# Constants
|
||||||
TEAM_ID = "2F67MQ8R79"
|
TEAM_ID = "2W7AC6T8T5"
|
||||||
CODE_SIGN_IDENTITY = "Apple Distribution: Hau Tran (#{TEAM_ID})"
|
CODE_SIGN_IDENTITY = "Apple Distribution: FUTO Holdings, Inc. (#{TEAM_ID})"
|
||||||
BASE_BUNDLE_ID = "app.alextran.immich"
|
BASE_BUNDLE_ID = "app.alextran.immich"
|
||||||
|
DEV_BUNDLE_ID = "tech.futo.immich.testflight"
|
||||||
|
|
||||||
# Helper method to get App Store Connect API key
|
# Helper method to get App Store Connect API key
|
||||||
def get_api_key
|
def get_api_key
|
||||||
app_store_connect_api_key(
|
app_store_connect_api_key(
|
||||||
@@ -44,47 +45,45 @@ def get_version_from_pubspec
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Helper method to configure code signing for all targets
|
# Helper method to configure code signing for all targets
|
||||||
def configure_code_signing(bundle_id_suffix: "", profile_name_main:, profile_name_share:, profile_name_widget:)
|
def configure_code_signing(base_bundle_id:, profile_name_main:, profile_name_share:, profile_name_widget:)
|
||||||
bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}"
|
|
||||||
|
|
||||||
# Runner (main app)
|
# Runner (main app)
|
||||||
update_code_signing_settings(
|
update_code_signing_settings(
|
||||||
use_automatic_signing: false,
|
use_automatic_signing: false,
|
||||||
path: "./Runner.xcodeproj",
|
path: "./Runner.xcodeproj",
|
||||||
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
|
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
|
||||||
code_sign_identity: CODE_SIGN_IDENTITY,
|
code_sign_identity: CODE_SIGN_IDENTITY,
|
||||||
bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}",
|
bundle_identifier: base_bundle_id,
|
||||||
profile_name: profile_name_main,
|
profile_name: profile_name_main,
|
||||||
targets: ["Runner"]
|
targets: ["Runner"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# ShareExtension
|
# ShareExtension
|
||||||
update_code_signing_settings(
|
update_code_signing_settings(
|
||||||
use_automatic_signing: false,
|
use_automatic_signing: false,
|
||||||
path: "./Runner.xcodeproj",
|
path: "./Runner.xcodeproj",
|
||||||
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
|
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
|
||||||
code_sign_identity: CODE_SIGN_IDENTITY,
|
code_sign_identity: CODE_SIGN_IDENTITY,
|
||||||
bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.ShareExtension",
|
bundle_identifier: "#{base_bundle_id}.ShareExtension",
|
||||||
profile_name: profile_name_share,
|
profile_name: profile_name_share,
|
||||||
targets: ["ShareExtension"]
|
targets: ["ShareExtension"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# WidgetExtension
|
# WidgetExtension
|
||||||
update_code_signing_settings(
|
update_code_signing_settings(
|
||||||
use_automatic_signing: false,
|
use_automatic_signing: false,
|
||||||
path: "./Runner.xcodeproj",
|
path: "./Runner.xcodeproj",
|
||||||
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
|
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
|
||||||
code_sign_identity: CODE_SIGN_IDENTITY,
|
code_sign_identity: CODE_SIGN_IDENTITY,
|
||||||
bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.Widget",
|
bundle_identifier: "#{base_bundle_id}.Widget",
|
||||||
profile_name: profile_name_widget,
|
profile_name: profile_name_widget,
|
||||||
targets: ["WidgetExtension"]
|
targets: ["WidgetExtension"]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Helper method to build and upload to TestFlight
|
# Helper method to build and upload to TestFlight
|
||||||
def build_and_upload(
|
def build_and_upload(
|
||||||
api_key:,
|
api_key:,
|
||||||
bundle_id_suffix: "",
|
base_bundle_id:,
|
||||||
configuration: "Release",
|
configuration: "Release",
|
||||||
distribute_external: true,
|
distribute_external: true,
|
||||||
version_number: nil,
|
version_number: nil,
|
||||||
@@ -92,9 +91,8 @@ end
|
|||||||
profile_name_share:,
|
profile_name_share:,
|
||||||
profile_name_widget:
|
profile_name_widget:
|
||||||
)
|
)
|
||||||
bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}"
|
app_identifier = base_bundle_id
|
||||||
app_identifier = "#{BASE_BUNDLE_ID}#{bundle_suffix}"
|
|
||||||
|
|
||||||
# Set version number if provided
|
# Set version number if provided
|
||||||
if version_number
|
if version_number
|
||||||
increment_version_number(version_number: version_number)
|
increment_version_number(version_number: version_number)
|
||||||
@@ -138,31 +136,31 @@ end
|
|||||||
desc "iOS Development Build to TestFlight (requires separate bundle ID)"
|
desc "iOS Development Build to TestFlight (requires separate bundle ID)"
|
||||||
lane :gha_testflight_dev do
|
lane :gha_testflight_dev do
|
||||||
api_key = get_api_key
|
api_key = get_api_key
|
||||||
|
|
||||||
# Download and install provisioning profiles from App Store Connect
|
# Download and install provisioning profiles from App Store Connect
|
||||||
# Certificate is imported by GHA workflow into build.keychain
|
# Certificate is imported by GHA workflow into build.keychain
|
||||||
# Capture profile names after each sigh call
|
# Capture profile names after each sigh call
|
||||||
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development", force: true)
|
sigh(api_key: api_key, app_identifier: DEV_BUNDLE_ID, force: true)
|
||||||
main_profile_name = lane_context[SharedValues::SIGH_NAME]
|
main_profile_name = lane_context[SharedValues::SIGH_NAME]
|
||||||
|
|
||||||
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.ShareExtension", force: true)
|
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.ShareExtension", force: true)
|
||||||
share_profile_name = lane_context[SharedValues::SIGH_NAME]
|
share_profile_name = lane_context[SharedValues::SIGH_NAME]
|
||||||
|
|
||||||
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.Widget", force: true)
|
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.Widget", force: true)
|
||||||
widget_profile_name = lane_context[SharedValues::SIGH_NAME]
|
widget_profile_name = lane_context[SharedValues::SIGH_NAME]
|
||||||
|
|
||||||
# Configure code signing for dev bundle IDs using the downloaded profile names
|
# Configure code signing for dev bundle IDs using the downloaded profile names
|
||||||
configure_code_signing(
|
configure_code_signing(
|
||||||
bundle_id_suffix: "development",
|
base_bundle_id: DEV_BUNDLE_ID,
|
||||||
profile_name_main: main_profile_name,
|
profile_name_main: main_profile_name,
|
||||||
profile_name_share: share_profile_name,
|
profile_name_share: share_profile_name,
|
||||||
profile_name_widget: widget_profile_name
|
profile_name_widget: widget_profile_name
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build and upload
|
# Build and upload
|
||||||
build_and_upload(
|
build_and_upload(
|
||||||
api_key: api_key,
|
api_key: api_key,
|
||||||
bundle_id_suffix: "development",
|
base_bundle_id: DEV_BUNDLE_ID,
|
||||||
configuration: "Profile",
|
configuration: "Profile",
|
||||||
distribute_external: false,
|
distribute_external: false,
|
||||||
profile_name_main: main_profile_name,
|
profile_name_main: main_profile_name,
|
||||||
@@ -189,6 +187,7 @@ end
|
|||||||
|
|
||||||
# Configure code signing for production bundle IDs
|
# Configure code signing for production bundle IDs
|
||||||
configure_code_signing(
|
configure_code_signing(
|
||||||
|
base_bundle_id: BASE_BUNDLE_ID,
|
||||||
profile_name_main: main_profile_name,
|
profile_name_main: main_profile_name,
|
||||||
profile_name_share: share_profile_name,
|
profile_name_share: share_profile_name,
|
||||||
profile_name_widget: widget_profile_name
|
profile_name_widget: widget_profile_name
|
||||||
@@ -197,6 +196,7 @@ end
|
|||||||
# Build and upload with version number
|
# Build and upload with version number
|
||||||
build_and_upload(
|
build_and_upload(
|
||||||
api_key: api_key,
|
api_key: api_key,
|
||||||
|
base_bundle_id: BASE_BUNDLE_ID,
|
||||||
version_number: get_version_from_pubspec,
|
version_number: get_version_from_pubspec,
|
||||||
distribute_external: false,
|
distribute_external: false,
|
||||||
profile_name_main: main_profile_name,
|
profile_name_main: main_profile_name,
|
||||||
@@ -243,30 +243,30 @@ end
|
|||||||
|
|
||||||
desc "iOS Build Only (no TestFlight upload)"
|
desc "iOS Build Only (no TestFlight upload)"
|
||||||
lane :gha_build_only do
|
lane :gha_build_only do
|
||||||
# Use the same build process as production, just skip the upload
|
# Use the same build process as the dev TestFlight lane, just skip the upload
|
||||||
# This ensures PR builds validate the same way as production builds
|
# This ensures PR builds validate the same way as dev TestFlight builds
|
||||||
|
|
||||||
api_key = get_api_key
|
api_key = get_api_key
|
||||||
|
|
||||||
# Download and install provisioning profiles from App Store Connect
|
# Download and install provisioning profiles from App Store Connect
|
||||||
# Certificate is imported by GHA workflow into build.keychain
|
# Certificate is imported by GHA workflow into build.keychain
|
||||||
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development", force: true)
|
sigh(api_key: api_key, app_identifier: DEV_BUNDLE_ID, force: true)
|
||||||
main_profile_name = lane_context[SharedValues::SIGH_NAME]
|
main_profile_name = lane_context[SharedValues::SIGH_NAME]
|
||||||
|
|
||||||
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.ShareExtension", force: true)
|
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.ShareExtension", force: true)
|
||||||
share_profile_name = lane_context[SharedValues::SIGH_NAME]
|
share_profile_name = lane_context[SharedValues::SIGH_NAME]
|
||||||
|
|
||||||
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.Widget", force: true)
|
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.Widget", force: true)
|
||||||
widget_profile_name = lane_context[SharedValues::SIGH_NAME]
|
widget_profile_name = lane_context[SharedValues::SIGH_NAME]
|
||||||
|
|
||||||
# Configure code signing for dev bundle IDs
|
# Configure code signing for dev bundle IDs
|
||||||
configure_code_signing(
|
configure_code_signing(
|
||||||
bundle_id_suffix: "development",
|
base_bundle_id: DEV_BUNDLE_ID,
|
||||||
profile_name_main: main_profile_name,
|
profile_name_main: main_profile_name,
|
||||||
profile_name_share: share_profile_name,
|
profile_name_share: share_profile_name,
|
||||||
profile_name_widget: widget_profile_name
|
profile_name_widget: widget_profile_name
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build the app (same as gha_testflight_dev but without upload)
|
# Build the app (same as gha_testflight_dev but without upload)
|
||||||
build_app(
|
build_app(
|
||||||
scheme: "Runner",
|
scheme: "Runner",
|
||||||
@@ -277,9 +277,9 @@ end
|
|||||||
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
|
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
|
||||||
export_options: {
|
export_options: {
|
||||||
provisioningProfiles: {
|
provisioningProfiles: {
|
||||||
"#{BASE_BUNDLE_ID}.development" => main_profile_name,
|
DEV_BUNDLE_ID => main_profile_name,
|
||||||
"#{BASE_BUNDLE_ID}.development.ShareExtension" => share_profile_name,
|
"#{DEV_BUNDLE_ID}.ShareExtension" => share_profile_name,
|
||||||
"#{BASE_BUNDLE_ID}.development.Widget" => widget_profile_name
|
"#{DEV_BUNDLE_ID}.Widget" => widget_profile_name
|
||||||
},
|
},
|
||||||
signingStyle: "manual",
|
signingStyle: "manual",
|
||||||
signingCertificate: CODE_SIGN_IDENTITY
|
signingCertificate: CODE_SIGN_IDENTITY
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ enum AlbumUserRole {
|
|||||||
// do not change this order!
|
// do not change this order!
|
||||||
editor,
|
editor,
|
||||||
viewer,
|
viewer,
|
||||||
|
owner,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Model for an album stored in the server
|
// Model for an album stored in the server
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/config/theme_config.dart';
|
||||||
|
|
||||||
|
class AppConfig {
|
||||||
|
final ThemeConfig theme;
|
||||||
|
|
||||||
|
const AppConfig({this.theme = const ThemeConfig()});
|
||||||
|
|
||||||
|
AppConfig copyWith({ThemeConfig? theme}) => .new(theme: theme ?? this.theme);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || (other is AppConfig && other.theme == theme);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => theme.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AppConfig(theme: $theme)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
|
|
||||||
|
class SystemConfig {
|
||||||
|
final LogLevel logLevel;
|
||||||
|
|
||||||
|
const SystemConfig({this.logLevel = .info});
|
||||||
|
|
||||||
|
SystemConfig copyWith({LogLevel? logLevel}) => SystemConfig(logLevel: logLevel ?? this.logLevel);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || (other is SystemConfig && other.logLevel == logLevel);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => logLevel.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SystemConfig(logLevel: $logLevel)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ThemeConfig {
|
||||||
|
final ThemeMode mode;
|
||||||
|
|
||||||
|
const ThemeConfig({this.mode = .system});
|
||||||
|
|
||||||
|
ThemeConfig copyWith({ThemeMode? mode}) => .new(mode: mode ?? this.mode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || (other is ThemeConfig && other.mode == mode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => mode.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'ThemeConfig(mode: $mode)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.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';
|
||||||
|
|
||||||
|
enum MetadataDomain<T extends Object> {
|
||||||
|
appConfig<AppConfig>('config.app'),
|
||||||
|
systemConfig<SystemConfig>('config.system');
|
||||||
|
|
||||||
|
final String prefix;
|
||||||
|
const MetadataDomain(this.prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MetadataKey<T extends Object> {
|
||||||
|
themeMode<ThemeMode>(.appConfig, 'theme.mode', .system, _EnumCodec(ThemeMode.values)),
|
||||||
|
logLevel<LogLevel>(.systemConfig, 'log.level', .info, _EnumCodec(LogLevel.values));
|
||||||
|
|
||||||
|
final MetadataDomain domain;
|
||||||
|
final String name;
|
||||||
|
final T defaultValue;
|
||||||
|
final _MetadataCodec<T>? _codecOverride;
|
||||||
|
|
||||||
|
const MetadataKey(this.domain, this.name, this.defaultValue, [this._codecOverride]);
|
||||||
|
|
||||||
|
String get key => '${domain.prefix}.$name';
|
||||||
|
|
||||||
|
_MetadataCodec<T> get _codec => _codecOverride ?? _MetadataCodec.forPrimitive(defaultValue);
|
||||||
|
|
||||||
|
String encode(T value) => _codec.encode(value);
|
||||||
|
|
||||||
|
T decode(String raw) => _codec.decode(raw) ?? defaultValue;
|
||||||
|
|
||||||
|
static Map<String, MetadataKey<Object>> asKeyMap() => {for (var value in MetadataKey.values) value.key: value};
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class _MetadataCodec<T extends Object> {
|
||||||
|
const _MetadataCodec();
|
||||||
|
|
||||||
|
String encode(T value);
|
||||||
|
T? decode(String raw);
|
||||||
|
|
||||||
|
static const Map<Type, _MetadataCodec<Object>> _primitives = {
|
||||||
|
int: _PrimitiveCodec.integer,
|
||||||
|
double: _PrimitiveCodec.real,
|
||||||
|
bool: _PrimitiveCodec.boolean,
|
||||||
|
String: _PrimitiveCodec.string,
|
||||||
|
DateTime: _DateTimeCodec(),
|
||||||
|
};
|
||||||
|
|
||||||
|
static _MetadataCodec<T> forPrimitive<T extends Object>(T sample) {
|
||||||
|
final codec = _primitives[sample.runtimeType];
|
||||||
|
if (codec == null) {
|
||||||
|
throw StateError(
|
||||||
|
'No primitive codec for ${sample.runtimeType}. Provide an explicit codec when defining the MetadataKey.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return codec as _MetadataCodec<T>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class _EnumCodec<T extends Enum> extends _MetadataCodec<T> {
|
||||||
|
final List<T> values;
|
||||||
|
|
||||||
|
const _EnumCodec(this.values);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(T value) => value.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
T? decode(String raw) => values.firstWhereOrNull((v) => v.name == raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class _DateTimeCodec extends _MetadataCodec<DateTime> {
|
||||||
|
const _DateTimeCodec();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(DateTime value) => value.toIso8601String();
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime? decode(String raw) => DateTime.tryParse(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class _PrimitiveCodec<T extends Object> extends _MetadataCodec<T> {
|
||||||
|
final T? Function(String) _parse;
|
||||||
|
|
||||||
|
const _PrimitiveCodec._(this._parse);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(T value) => value.toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
T? decode(String raw) => _parse(raw);
|
||||||
|
|
||||||
|
static const integer = _PrimitiveCodec<int>._(int.tryParse);
|
||||||
|
static const real = _PrimitiveCodec<double>._(double.tryParse);
|
||||||
|
static const boolean = _PrimitiveCodec<bool>._(bool.tryParse);
|
||||||
|
static const string = _PrimitiveCodec<String>._(_identity);
|
||||||
|
|
||||||
|
static String? _identity(String s) => s;
|
||||||
|
}
|
||||||
@@ -22,7 +22,6 @@ enum StoreKey<T> {
|
|||||||
// user settings from [AppSettingsEnum] below:
|
// user settings from [AppSettingsEnum] below:
|
||||||
loadPreview<bool>._(100),
|
loadPreview<bool>._(100),
|
||||||
loadOriginal<bool>._(101),
|
loadOriginal<bool>._(101),
|
||||||
themeMode<String>._(102),
|
|
||||||
tilesPerRow<int>._(103),
|
tilesPerRow<int>._(103),
|
||||||
dynamicLayout<bool>._(104),
|
dynamicLayout<bool>._(104),
|
||||||
groupAssetsBy<int>._(105),
|
groupAssetsBy<int>._(105),
|
||||||
@@ -35,7 +34,6 @@ enum StoreKey<T> {
|
|||||||
albumThumbnailCacheSize<int>._(112),
|
albumThumbnailCacheSize<int>._(112),
|
||||||
selectedAlbumSortOrder<int>._(113),
|
selectedAlbumSortOrder<int>._(113),
|
||||||
advancedTroubleshooting<bool>._(114),
|
advancedTroubleshooting<bool>._(114),
|
||||||
logLevel<int>._(115),
|
|
||||||
preferRemoteImage<bool>._(116),
|
preferRemoteImage<bool>._(116),
|
||||||
loopVideo<bool>._(117),
|
loopVideo<bool>._(117),
|
||||||
// map related settings
|
// map related settings
|
||||||
@@ -94,7 +92,11 @@ enum StoreKey<T> {
|
|||||||
cleanupCutoffDaysAgo<int>._(1011),
|
cleanupCutoffDaysAgo<int>._(1011),
|
||||||
cleanupDefaultsInitialized<bool>._(1012),
|
cleanupDefaultsInitialized<bool>._(1012),
|
||||||
|
|
||||||
syncMigrationStatus<String>._(1013);
|
syncMigrationStatus<String>._(1013),
|
||||||
|
|
||||||
|
// Legacy keys that have been migrated to the new metadata store
|
||||||
|
legacyThemeMode<String>._(102),
|
||||||
|
legacyLogLevel<int>._(115);
|
||||||
|
|
||||||
const StoreKey._(this.id);
|
const StoreKey._(this.id);
|
||||||
final int id;
|
final int id;
|
||||||
|
|||||||
@@ -2,20 +2,20 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
/// Service responsible for handling application logging.
|
/// Service responsible for handling application logging.
|
||||||
///
|
///
|
||||||
/// It listens to Dart's [Logger.root], buffers logs in memory (optionally),
|
/// It listens to Dart's [Logger.root], buffers logs in memory (optionally),
|
||||||
/// writes them to a persistent [ILogRepository], and manages log levels
|
/// writes them to a persistent [LogRepository], and manages log levels via
|
||||||
/// via [IStoreRepository]
|
/// [MetadataRepository].
|
||||||
class LogService {
|
class LogService {
|
||||||
final LogRepository _logRepository;
|
final LogRepository _logRepository;
|
||||||
final DriftStoreRepository _storeRepository;
|
final MetadataRepository _metadataRepository;
|
||||||
|
|
||||||
final List<LogMessage> _msgBuffer = [];
|
final List<LogMessage> _msgBuffer = [];
|
||||||
|
|
||||||
@@ -38,12 +38,12 @@ class LogService {
|
|||||||
|
|
||||||
static Future<LogService> init({
|
static Future<LogService> init({
|
||||||
required LogRepository logRepository,
|
required LogRepository logRepository,
|
||||||
required DriftStoreRepository storeRepository,
|
required MetadataRepository metadataRepository,
|
||||||
bool shouldBuffer = true,
|
bool shouldBuffer = true,
|
||||||
}) async {
|
}) async {
|
||||||
_instance ??= await create(
|
_instance ??= await create(
|
||||||
logRepository: logRepository,
|
logRepository: logRepository,
|
||||||
storeRepository: storeRepository,
|
metadataRepository: metadataRepository,
|
||||||
shouldBuffer: shouldBuffer,
|
shouldBuffer: shouldBuffer,
|
||||||
);
|
);
|
||||||
return _instance!;
|
return _instance!;
|
||||||
@@ -51,17 +51,17 @@ class LogService {
|
|||||||
|
|
||||||
static Future<LogService> create({
|
static Future<LogService> create({
|
||||||
required LogRepository logRepository,
|
required LogRepository logRepository,
|
||||||
required DriftStoreRepository storeRepository,
|
required MetadataRepository metadataRepository,
|
||||||
bool shouldBuffer = true,
|
bool shouldBuffer = true,
|
||||||
}) async {
|
}) async {
|
||||||
final instance = LogService._(logRepository, storeRepository, shouldBuffer);
|
final instance = LogService._(logRepository, metadataRepository, shouldBuffer);
|
||||||
await logRepository.truncate(limit: kLogTruncateLimit);
|
await logRepository.truncate(limit: kLogTruncateLimit);
|
||||||
final level = await instance._storeRepository.tryGet(StoreKey.logLevel) ?? LogLevel.info.index;
|
final level = instance._metadataRepository.systemConfig.logLevel;
|
||||||
Logger.root.level = Level.LEVELS.elementAtOrNull(level) ?? Level.INFO;
|
Logger.root.level = Level.LEVELS.elementAtOrNull(level.index) ?? Level.INFO;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogService._(this._logRepository, this._storeRepository, this._shouldBuffer) {
|
LogService._(this._logRepository, this._metadataRepository, this._shouldBuffer) {
|
||||||
_logSubscription = Logger.root.onRecord.listen(_handleLogRecord);
|
_logSubscription = Logger.root.onRecord.listen(_handleLogRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ class LogService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setLogLevel(LogLevel level) async {
|
Future<void> setLogLevel(LogLevel level) async {
|
||||||
await _storeRepository.upsert(StoreKey.logLevel, level.index);
|
await _metadataRepository.write(MetadataKey.logLevel, level);
|
||||||
Logger.root.level = level.toLevel();
|
Logger.root.level = level.toLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
|||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
||||||
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
||||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
|
||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||||
|
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||||
|
|
||||||
class RemoteAlbumService {
|
class RemoteAlbumService {
|
||||||
final DriftRemoteAlbumRepository _repository;
|
final DriftRemoteAlbumRepository _repository;
|
||||||
@@ -28,10 +28,6 @@ class RemoteAlbumService {
|
|||||||
return _repository.get(albumId);
|
return _repository.get(albumId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RemoteAlbum?> getByName(String albumName, String ownerId) {
|
|
||||||
return _repository.getByName(albumName, ownerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<RemoteAlbum>> sortAlbums(
|
Future<List<RemoteAlbum>> sortAlbums(
|
||||||
List<RemoteAlbum> albums,
|
List<RemoteAlbum> albums,
|
||||||
AlbumSortMode sortMode, {
|
AlbumSortMode sortMode, {
|
||||||
@@ -86,8 +82,18 @@ class RemoteAlbumService {
|
|||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RemoteAlbum> createAlbum({required String title, required List<String> assetIds, String? description}) async {
|
Future<RemoteAlbum> createAlbum({
|
||||||
final album = await _albumApiRepository.createDriftAlbum(title, description: description, assetIds: assetIds);
|
required String title,
|
||||||
|
required UserDto owner,
|
||||||
|
required List<String> assetIds,
|
||||||
|
String? description,
|
||||||
|
}) async {
|
||||||
|
final album = await _albumApiRepository.createDriftAlbum(
|
||||||
|
title,
|
||||||
|
owner,
|
||||||
|
description: description,
|
||||||
|
assetIds: assetIds,
|
||||||
|
);
|
||||||
await _repository.create(album, assetIds);
|
await _repository.create(album, assetIds);
|
||||||
|
|
||||||
return album;
|
return album;
|
||||||
@@ -101,8 +107,10 @@ class RemoteAlbumService {
|
|||||||
bool? isActivityEnabled,
|
bool? isActivityEnabled,
|
||||||
AlbumAssetOrder? order,
|
AlbumAssetOrder? order,
|
||||||
}) async {
|
}) async {
|
||||||
|
final owner = await _repository.getOwner(albumId);
|
||||||
final updatedAlbum = await _albumApiRepository.updateAlbum(
|
final updatedAlbum = await _albumApiRepository.updateAlbum(
|
||||||
albumId,
|
albumId,
|
||||||
|
owner,
|
||||||
name: name,
|
name: name,
|
||||||
description: description,
|
description: description,
|
||||||
thumbnailAssetId: thumbnailAssetId,
|
thumbnailAssetId: thumbnailAssetId,
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -12,6 +15,7 @@ final syncLinkedAlbumServiceProvider = Provider(
|
|||||||
ref.watch(localAlbumRepository),
|
ref.watch(localAlbumRepository),
|
||||||
ref.watch(remoteAlbumRepository),
|
ref.watch(remoteAlbumRepository),
|
||||||
ref.watch(driftAlbumApiRepositoryProvider),
|
ref.watch(driftAlbumApiRepositoryProvider),
|
||||||
|
ref.watch(storeServiceProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -19,8 +23,14 @@ class SyncLinkedAlbumService {
|
|||||||
final DriftLocalAlbumRepository _localAlbumRepository;
|
final DriftLocalAlbumRepository _localAlbumRepository;
|
||||||
final DriftRemoteAlbumRepository _remoteAlbumRepository;
|
final DriftRemoteAlbumRepository _remoteAlbumRepository;
|
||||||
final DriftAlbumApiRepository _albumApiRepository;
|
final DriftAlbumApiRepository _albumApiRepository;
|
||||||
|
final StoreService _storeService;
|
||||||
|
|
||||||
SyncLinkedAlbumService(this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository);
|
SyncLinkedAlbumService(
|
||||||
|
this._localAlbumRepository,
|
||||||
|
this._remoteAlbumRepository,
|
||||||
|
this._albumApiRepository,
|
||||||
|
this._storeService,
|
||||||
|
);
|
||||||
|
|
||||||
final _log = Logger("SyncLinkedAlbumService");
|
final _log = Logger("SyncLinkedAlbumService");
|
||||||
|
|
||||||
@@ -103,7 +113,11 @@ class SyncLinkedAlbumService {
|
|||||||
/// Creates a new remote album and links it to the local album
|
/// Creates a new remote album and links it to the local album
|
||||||
Future<void> _createAndLinkNewRemoteAlbum(LocalAlbum localAlbum) async {
|
Future<void> _createAndLinkNewRemoteAlbum(LocalAlbum localAlbum) async {
|
||||||
dPrint(() => "Creating new remote album for local album: ${localAlbum.name}");
|
dPrint(() => "Creating new remote album for local album: ${localAlbum.name}");
|
||||||
final newRemoteAlbum = await _albumApiRepository.createDriftAlbum(localAlbum.name, assetIds: []);
|
final newRemoteAlbum = await _albumApiRepository.createDriftAlbum(
|
||||||
|
localAlbum.name,
|
||||||
|
_storeService.get(StoreKey.currentUser),
|
||||||
|
assetIds: [],
|
||||||
|
);
|
||||||
await _remoteAlbumRepository.create(newRemoteAlbum, []);
|
await _remoteAlbumRepository.create(newRemoteAlbum, []);
|
||||||
return _localAlbumRepository.linkRemoteAlbum(localAlbum.id, newRemoteAlbum.id);
|
return _localAlbumRepository.linkRemoteAlbum(localAlbum.id, newRemoteAlbum.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
@@ -192,17 +193,22 @@ class SyncStreamService {
|
|||||||
final remoteSyncAssets = data.cast<SyncAssetV1>();
|
final remoteSyncAssets = data.cast<SyncAssetV1>();
|
||||||
await _syncStreamRepository.updateAssetsV1(remoteSyncAssets);
|
await _syncStreamRepository.updateAssetsV1(remoteSyncAssets);
|
||||||
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||||
final hasPermission = await _localFilesManager.hasManageMediaPermission();
|
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList());
|
||||||
if (hasPermission) {
|
}
|
||||||
await _handleRemoteTrashed(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.checksum));
|
return;
|
||||||
await _applyRemoteRestoreToLocal();
|
case SyncEntityType.assetV2:
|
||||||
} else {
|
final remoteSyncAssets = data.cast<SyncAssetV2>();
|
||||||
_logger.warning("sync Trashed Assets cannot proceed because MANAGE_MEDIA permission is missing");
|
await _syncStreamRepository.updateAssetsV2(remoteSyncAssets);
|
||||||
}
|
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||||
|
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SyncEntityType.assetDeleteV1:
|
case SyncEntityType.assetDeleteV1:
|
||||||
return _syncStreamRepository.deleteAssetsV1(data.cast());
|
final remoteSyncAssets = data.cast<SyncAssetDeleteV1>();
|
||||||
|
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||||
|
await _syncAssetDeletion(remoteSyncAssets.map((e) => e.assetId).toList());
|
||||||
|
}
|
||||||
|
return _syncStreamRepository.deleteAssetsV1(remoteSyncAssets);
|
||||||
case SyncEntityType.assetExifV1:
|
case SyncEntityType.assetExifV1:
|
||||||
return _syncStreamRepository.updateAssetsExifV1(data.cast());
|
return _syncStreamRepository.updateAssetsExifV1(data.cast());
|
||||||
case SyncEntityType.assetEditV1:
|
case SyncEntityType.assetEditV1:
|
||||||
@@ -215,8 +221,12 @@ class SyncStreamService {
|
|||||||
return _syncStreamRepository.deleteAssetsMetadataV1(data.cast());
|
return _syncStreamRepository.deleteAssetsMetadataV1(data.cast());
|
||||||
case SyncEntityType.partnerAssetV1:
|
case SyncEntityType.partnerAssetV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner');
|
||||||
|
case SyncEntityType.partnerAssetV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'partner');
|
||||||
case SyncEntityType.partnerAssetBackfillV1:
|
case SyncEntityType.partnerAssetBackfillV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner backfill');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner backfill');
|
||||||
|
case SyncEntityType.partnerAssetBackfillV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'partner backfill');
|
||||||
case SyncEntityType.partnerAssetDeleteV1:
|
case SyncEntityType.partnerAssetDeleteV1:
|
||||||
return _syncStreamRepository.deleteAssetsV1(data.cast(), debugLabel: "partner");
|
return _syncStreamRepository.deleteAssetsV1(data.cast(), debugLabel: "partner");
|
||||||
case SyncEntityType.partnerAssetExifV1:
|
case SyncEntityType.partnerAssetExifV1:
|
||||||
@@ -225,6 +235,8 @@ class SyncStreamService {
|
|||||||
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'partner backfill');
|
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'partner backfill');
|
||||||
case SyncEntityType.albumV1:
|
case SyncEntityType.albumV1:
|
||||||
return _syncStreamRepository.updateAlbumsV1(data.cast());
|
return _syncStreamRepository.updateAlbumsV1(data.cast());
|
||||||
|
case SyncEntityType.albumV2:
|
||||||
|
return _syncStreamRepository.updateAlbumsV2(data.cast());
|
||||||
case SyncEntityType.albumDeleteV1:
|
case SyncEntityType.albumDeleteV1:
|
||||||
return _syncStreamRepository.deleteAlbumsV1(data.cast());
|
return _syncStreamRepository.deleteAlbumsV1(data.cast());
|
||||||
case SyncEntityType.albumUserV1:
|
case SyncEntityType.albumUserV1:
|
||||||
@@ -235,10 +247,16 @@ class SyncStreamService {
|
|||||||
return _syncStreamRepository.deleteAlbumUsersV1(data.cast());
|
return _syncStreamRepository.deleteAlbumUsersV1(data.cast());
|
||||||
case SyncEntityType.albumAssetCreateV1:
|
case SyncEntityType.albumAssetCreateV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset create');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset create');
|
||||||
|
case SyncEntityType.albumAssetCreateV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset create');
|
||||||
case SyncEntityType.albumAssetUpdateV1:
|
case SyncEntityType.albumAssetUpdateV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset update');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset update');
|
||||||
|
case SyncEntityType.albumAssetUpdateV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset update');
|
||||||
case SyncEntityType.albumAssetBackfillV1:
|
case SyncEntityType.albumAssetBackfillV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset backfill');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset backfill');
|
||||||
|
case SyncEntityType.albumAssetBackfillV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset backfill');
|
||||||
case SyncEntityType.albumAssetExifCreateV1:
|
case SyncEntityType.albumAssetExifCreateV1:
|
||||||
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album asset exif create');
|
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album asset exif create');
|
||||||
case SyncEntityType.albumAssetExifUpdateV1:
|
case SyncEntityType.albumAssetExifUpdateV1:
|
||||||
@@ -340,6 +358,47 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> handleWsAssetUploadReadyV2Batch(List<dynamic> batchData) async {
|
||||||
|
if (batchData.isEmpty) return;
|
||||||
|
|
||||||
|
_logger.info('Processing batch of ${batchData.length} AssetUploadReadyV2 events');
|
||||||
|
|
||||||
|
final List<SyncAssetV2> assets = [];
|
||||||
|
final List<SyncAssetExifV1> exifs = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (final data in batchData) {
|
||||||
|
if (data is! Map<String, dynamic>) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final payload = data;
|
||||||
|
final assetData = payload['asset'];
|
||||||
|
final exifData = payload['exif'];
|
||||||
|
|
||||||
|
if (assetData == null || exifData == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final asset = SyncAssetV2.fromJson(assetData);
|
||||||
|
final exif = SyncAssetExifV1.fromJson(exifData);
|
||||||
|
|
||||||
|
if (asset != null && exif != null) {
|
||||||
|
assets.add(asset);
|
||||||
|
exifs.add(exif);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assets.isNotEmpty && exifs.isNotEmpty) {
|
||||||
|
await _syncStreamRepository.updateAssetsV2(assets, debugLabel: 'websocket-batch');
|
||||||
|
await _syncStreamRepository.updateAssetsExifV1(exifs, debugLabel: 'websocket-batch');
|
||||||
|
_logger.info('Successfully processed ${assets.length} assets in batch');
|
||||||
|
}
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_logger.severe("Error processing AssetUploadReadyV2 websocket batch events", error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> handleWsAssetEditReadyV1(dynamic data) async {
|
Future<void> handleWsAssetEditReadyV1(dynamic data) async {
|
||||||
_logger.info('Processing AssetEditReadyV1 event');
|
_logger.info('Processing AssetEditReadyV1 event');
|
||||||
|
|
||||||
@@ -380,28 +439,67 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleRemoteTrashed(Iterable<String> checksums) async {
|
Future<void> handleWsAssetEditReadyV2(dynamic data) async {
|
||||||
if (checksums.isEmpty) {
|
_logger.info('Processing AssetEditReadyV2 event');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (data is! Map<String, dynamic>) {
|
||||||
|
throw ArgumentError("Invalid data format for AssetEditReadyV2 event");
|
||||||
|
}
|
||||||
|
|
||||||
|
final payload = data;
|
||||||
|
|
||||||
|
if (payload['asset'] == null) {
|
||||||
|
throw ArgumentError("Missing 'asset' field in AssetEditReadyV2 event data");
|
||||||
|
}
|
||||||
|
|
||||||
|
final asset = SyncAssetV2.fromJson(payload['asset']);
|
||||||
|
if (asset == null) {
|
||||||
|
throw ArgumentError("Failed to parse 'asset' field in AssetEditReadyV2 event data");
|
||||||
|
}
|
||||||
|
|
||||||
|
final assetEdits = (payload['edit'] as List<dynamic>)
|
||||||
|
.map((e) => SyncAssetEditV1.fromJson(e))
|
||||||
|
.whereType<SyncAssetEditV1>()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
await _syncStreamRepository.updateAssetsV2([asset], debugLabel: 'websocket-edit');
|
||||||
|
await _syncStreamRepository.replaceAssetEditsV1(asset.id, assetEdits, debugLabel: 'websocket-edit');
|
||||||
|
|
||||||
|
_logger.info(
|
||||||
|
'Successfully processed AssetEditReadyV2 event for asset ${asset.id} with ${assetEdits.length} edits',
|
||||||
|
);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_logger.severe("Error processing AssetEditReadyV2 websocket event", error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleRemoteDeleted(Iterable<String> remoteIds) async {
|
||||||
|
if (remoteIds.isEmpty) {
|
||||||
return Future.value();
|
return Future.value();
|
||||||
} else {
|
} else {
|
||||||
final localAssetsToTrash = await _localAssetRepository.getAssetsFromBackupAlbums(checksums);
|
final localAssetsToTrash = await _localAssetRepository.getAssetsFromBackupAlbums(remoteIds);
|
||||||
if (localAssetsToTrash.isNotEmpty) {
|
if (localAssetsToTrash.isNotEmpty) {
|
||||||
final mediaUrls = await Future.wait(
|
await _trashLocalAssets(localAssetsToTrash);
|
||||||
localAssetsToTrash.values
|
|
||||||
.expand((e) => e)
|
|
||||||
.map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())),
|
|
||||||
);
|
|
||||||
_logger.info("Moving to trash ${mediaUrls.join(", ")} assets");
|
|
||||||
final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList());
|
|
||||||
if (result) {
|
|
||||||
await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
_logger.info("No assets found in backup-enabled albums for assets: $checksums");
|
_logger.info("No assets found in backup-enabled albums for remote assets: $remoteIds");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _trashLocalAssets(Map<String, List<LocalAsset>> localAssetsToTrash) async {
|
||||||
|
final mediaUrls = await Future.wait(
|
||||||
|
localAssetsToTrash.values
|
||||||
|
.expand((e) => e)
|
||||||
|
.map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())),
|
||||||
|
);
|
||||||
|
_logger.info("Moving to trash ${mediaUrls.join(", ")} assets");
|
||||||
|
final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList());
|
||||||
|
if (result) {
|
||||||
|
await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _applyRemoteRestoreToLocal() async {
|
Future<void> _applyRemoteRestoreToLocal() async {
|
||||||
final assetsToRestore = await _trashedLocalAssetRepository.getToRestore();
|
final assetsToRestore = await _trashedLocalAssetRepository.getToRestore();
|
||||||
if (assetsToRestore.isNotEmpty) {
|
if (assetsToRestore.isNotEmpty) {
|
||||||
@@ -411,4 +509,23 @@ class SyncStreamService {
|
|||||||
_logger.info("No remote assets found for restoration");
|
_logger.info("No remote assets found for restoration");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _syncAssetTrashStatus(List<String> remoteIds) async {
|
||||||
|
if (!(await _localFilesManager.hasManageMediaPermission())) {
|
||||||
|
_logger.warning("Syncing asset trash status cannot proceed because MANAGE_MEDIA permission is missing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _handleRemoteDeleted(remoteIds);
|
||||||
|
await _applyRemoteRestoreToLocal();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _syncAssetDeletion(List<String> remoteIds) async {
|
||||||
|
if (!(await _localFilesManager.hasManageMediaPermission())) {
|
||||||
|
_logger.warning("Syncing asset deletion cannot proceed because MANAGE_MEDIA permission is missing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _handleRemoteDeleted(remoteIds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ class BackgroundSyncManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> syncWebsocketBatch(List<dynamic> batchData) {
|
Future<void> syncWebsocketBatchV1(List<dynamic> batchData) {
|
||||||
if (_syncWebsocketTask != null) {
|
if (_syncWebsocketTask != null) {
|
||||||
return _syncWebsocketTask!.future;
|
return _syncWebsocketTask!.future;
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,17 @@ class BackgroundSyncManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> syncWebsocketEdit(dynamic data) {
|
Future<void> syncWebsocketBatchV2(List<dynamic> batchData) {
|
||||||
|
if (_syncWebsocketTask != null) {
|
||||||
|
return _syncWebsocketTask!.future;
|
||||||
|
}
|
||||||
|
_syncWebsocketTask = _handleWsAssetUploadReadyV2Batch(batchData);
|
||||||
|
return _syncWebsocketTask!.whenComplete(() {
|
||||||
|
_syncWebsocketTask = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> syncWebsocketEditV1(dynamic data) {
|
||||||
if (_syncWebsocketTask != null) {
|
if (_syncWebsocketTask != null) {
|
||||||
return _syncWebsocketTask!.future;
|
return _syncWebsocketTask!.future;
|
||||||
}
|
}
|
||||||
@@ -206,6 +216,16 @@ class BackgroundSyncManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> syncWebsocketEditV2(dynamic data) {
|
||||||
|
if (_syncWebsocketTask != null) {
|
||||||
|
return _syncWebsocketTask!.future;
|
||||||
|
}
|
||||||
|
_syncWebsocketTask = _handleWsAssetEditReadyV2(data);
|
||||||
|
return _syncWebsocketTask!.whenComplete(() {
|
||||||
|
_syncWebsocketTask = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> syncLinkedAlbum() {
|
Future<void> syncLinkedAlbum() {
|
||||||
if (_linkedAlbumSyncTask != null) {
|
if (_linkedAlbumSyncTask != null) {
|
||||||
return _linkedAlbumSyncTask!.future;
|
return _linkedAlbumSyncTask!.future;
|
||||||
@@ -242,7 +262,17 @@ Cancelable<void> _handleWsAssetUploadReadyV1Batch(List<dynamic> batchData) => ru
|
|||||||
debugLabel: 'websocket-batch',
|
debugLabel: 'websocket-batch',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Cancelable<void> _handleWsAssetUploadReadyV2Batch(List<dynamic> batchData) => runInIsolateGentle(
|
||||||
|
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetUploadReadyV2Batch(batchData),
|
||||||
|
debugLabel: 'websocket-batch',
|
||||||
|
);
|
||||||
|
|
||||||
Cancelable<void> _handleWsAssetEditReadyV1(dynamic data) => runInIsolateGentle(
|
Cancelable<void> _handleWsAssetEditReadyV1(dynamic data) => runInIsolateGentle(
|
||||||
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV1(data),
|
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV1(data),
|
||||||
debugLabel: 'websocket-edit',
|
debugLabel: 'websocket-edit',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Cancelable<void> _handleWsAssetEditReadyV2(dynamic data) => runInIsolateGentle(
|
||||||
|
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV2(data),
|
||||||
|
debugLabel: 'websocket-edit',
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||||
import 'package:immich_mobile/extensions/string_extensions.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
||||||
import 'package:openapi/api.dart' as api;
|
import 'package:openapi/api.dart' as api;
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ extension DTOToAsset on api.AssetResponseDto {
|
|||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
visibility: visibility.toAssetVisibility(),
|
visibility: visibility.toAssetVisibility(),
|
||||||
durationMs: duration?.toDuration()?.inMilliseconds ?? 0,
|
durationMs: duration,
|
||||||
height: height?.toInt(),
|
height: height?.toInt(),
|
||||||
width: width?.toInt(),
|
width: width?.toInt(),
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
@@ -36,7 +35,7 @@ extension DTOToAsset on api.AssetResponseDto {
|
|||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
visibility: visibility.toAssetVisibility(),
|
visibility: visibility.toAssetVisibility(),
|
||||||
durationMs: duration?.toDuration()?.inMilliseconds ?? 0,
|
durationMs: duration,
|
||||||
height: height?.toInt(),
|
height: height?.toInt(),
|
||||||
width: width?.toInt(),
|
width: width?.toInt(),
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
|
class MetadataEntity extends Table with DriftDefaultsMixin {
|
||||||
|
const MetadataEntity();
|
||||||
|
|
||||||
|
TextColumn get key => text()();
|
||||||
|
|
||||||
|
TextColumn get value => text()();
|
||||||
|
|
||||||
|
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {key};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tableName => "metadata";
|
||||||
|
}
|
||||||
@@ -0,0 +1,429 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:drift/drift.dart' as i0;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart'
|
||||||
|
as i1;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/metadata.entity.dart'
|
||||||
|
as i2;
|
||||||
|
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
|
||||||
|
|
||||||
|
typedef $$MetadataEntityTableCreateCompanionBuilder =
|
||||||
|
i1.MetadataEntityCompanion Function({
|
||||||
|
required String key,
|
||||||
|
required String value,
|
||||||
|
i0.Value<DateTime> updatedAt,
|
||||||
|
});
|
||||||
|
typedef $$MetadataEntityTableUpdateCompanionBuilder =
|
||||||
|
i1.MetadataEntityCompanion Function({
|
||||||
|
i0.Value<String> key,
|
||||||
|
i0.Value<String> value,
|
||||||
|
i0.Value<DateTime> updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
class $$MetadataEntityTableFilterComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$MetadataEntityTable> {
|
||||||
|
$$MetadataEntityTableFilterComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnFilters<String> get key => $composableBuilder(
|
||||||
|
column: $table.key,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<String> get value => $composableBuilder(
|
||||||
|
column: $table.value,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<DateTime> get updatedAt => $composableBuilder(
|
||||||
|
column: $table.updatedAt,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$MetadataEntityTableOrderingComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$MetadataEntityTable> {
|
||||||
|
$$MetadataEntityTableOrderingComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnOrderings<String> get key => $composableBuilder(
|
||||||
|
column: $table.key,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<String> get value => $composableBuilder(
|
||||||
|
column: $table.value,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<DateTime> get updatedAt => $composableBuilder(
|
||||||
|
column: $table.updatedAt,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$MetadataEntityTableAnnotationComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$MetadataEntityTable> {
|
||||||
|
$$MetadataEntityTableAnnotationComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.GeneratedColumn<String> get key =>
|
||||||
|
$composableBuilder(column: $table.key, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<String> get value =>
|
||||||
|
$composableBuilder(column: $table.value, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<DateTime> get updatedAt =>
|
||||||
|
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$MetadataEntityTableTableManager
|
||||||
|
extends
|
||||||
|
i0.RootTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$MetadataEntityTable,
|
||||||
|
i1.MetadataEntityData,
|
||||||
|
i1.$$MetadataEntityTableFilterComposer,
|
||||||
|
i1.$$MetadataEntityTableOrderingComposer,
|
||||||
|
i1.$$MetadataEntityTableAnnotationComposer,
|
||||||
|
$$MetadataEntityTableCreateCompanionBuilder,
|
||||||
|
$$MetadataEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.MetadataEntityData,
|
||||||
|
i0.BaseReferences<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$MetadataEntityTable,
|
||||||
|
i1.MetadataEntityData
|
||||||
|
>,
|
||||||
|
),
|
||||||
|
i1.MetadataEntityData,
|
||||||
|
i0.PrefetchHooks Function()
|
||||||
|
> {
|
||||||
|
$$MetadataEntityTableTableManager(
|
||||||
|
i0.GeneratedDatabase db,
|
||||||
|
i1.$MetadataEntityTable table,
|
||||||
|
) : super(
|
||||||
|
i0.TableManagerState(
|
||||||
|
db: db,
|
||||||
|
table: table,
|
||||||
|
createFilteringComposer: () =>
|
||||||
|
i1.$$MetadataEntityTableFilterComposer($db: db, $table: table),
|
||||||
|
createOrderingComposer: () =>
|
||||||
|
i1.$$MetadataEntityTableOrderingComposer($db: db, $table: table),
|
||||||
|
createComputedFieldComposer: () => i1
|
||||||
|
.$$MetadataEntityTableAnnotationComposer($db: db, $table: table),
|
||||||
|
updateCompanionCallback:
|
||||||
|
({
|
||||||
|
i0.Value<String> key = const i0.Value.absent(),
|
||||||
|
i0.Value<String> value = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
|
}) => i1.MetadataEntityCompanion(
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
),
|
||||||
|
createCompanionCallback:
|
||||||
|
({
|
||||||
|
required String key,
|
||||||
|
required String value,
|
||||||
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
|
}) => i1.MetadataEntityCompanion.insert(
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
),
|
||||||
|
withReferenceMapper: (p0) => p0
|
||||||
|
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||||
|
.toList(),
|
||||||
|
prefetchHooksCallback: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef $$MetadataEntityTableProcessedTableManager =
|
||||||
|
i0.ProcessedTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$MetadataEntityTable,
|
||||||
|
i1.MetadataEntityData,
|
||||||
|
i1.$$MetadataEntityTableFilterComposer,
|
||||||
|
i1.$$MetadataEntityTableOrderingComposer,
|
||||||
|
i1.$$MetadataEntityTableAnnotationComposer,
|
||||||
|
$$MetadataEntityTableCreateCompanionBuilder,
|
||||||
|
$$MetadataEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.MetadataEntityData,
|
||||||
|
i0.BaseReferences<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$MetadataEntityTable,
|
||||||
|
i1.MetadataEntityData
|
||||||
|
>,
|
||||||
|
),
|
||||||
|
i1.MetadataEntityData,
|
||||||
|
i0.PrefetchHooks Function()
|
||||||
|
>;
|
||||||
|
|
||||||
|
class $MetadataEntityTable extends i2.MetadataEntity
|
||||||
|
with i0.TableInfo<$MetadataEntityTable, i1.MetadataEntityData> {
|
||||||
|
@override
|
||||||
|
final i0.GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
$MetadataEntityTable(this.attachedDatabase, [this._alias]);
|
||||||
|
static const i0.VerificationMeta _keyMeta = const i0.VerificationMeta('key');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> key = i0.GeneratedColumn<String>(
|
||||||
|
'key',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _valueMeta = const i0.VerificationMeta(
|
||||||
|
'value',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> value = i0.GeneratedColumn<String>(
|
||||||
|
'value',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta(
|
||||||
|
'updatedAt',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<DateTime> updatedAt =
|
||||||
|
i0.GeneratedColumn<DateTime>(
|
||||||
|
'updated_at',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: i3.currentDateAndTime,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
List<i0.GeneratedColumn> get $columns => [key, value, updatedAt];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'metadata';
|
||||||
|
@override
|
||||||
|
i0.VerificationContext validateIntegrity(
|
||||||
|
i0.Insertable<i1.MetadataEntityData> instance, {
|
||||||
|
bool isInserting = false,
|
||||||
|
}) {
|
||||||
|
final context = i0.VerificationContext();
|
||||||
|
final data = instance.toColumns(true);
|
||||||
|
if (data.containsKey('key')) {
|
||||||
|
context.handle(
|
||||||
|
_keyMeta,
|
||||||
|
key.isAcceptableOrUnknown(data['key']!, _keyMeta),
|
||||||
|
);
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_keyMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('value')) {
|
||||||
|
context.handle(
|
||||||
|
_valueMeta,
|
||||||
|
value.isAcceptableOrUnknown(data['value']!, _valueMeta),
|
||||||
|
);
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_valueMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('updated_at')) {
|
||||||
|
context.handle(
|
||||||
|
_updatedAtMeta,
|
||||||
|
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<i0.GeneratedColumn> get $primaryKey => {key};
|
||||||
|
@override
|
||||||
|
i1.MetadataEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return i1.MetadataEntityData(
|
||||||
|
key: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}key'],
|
||||||
|
)!,
|
||||||
|
value: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}value'],
|
||||||
|
)!,
|
||||||
|
updatedAt: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.dateTime,
|
||||||
|
data['${effectivePrefix}updated_at'],
|
||||||
|
)!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$MetadataEntityTable createAlias(String alias) {
|
||||||
|
return $MetadataEntityTable(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get withoutRowId => true;
|
||||||
|
@override
|
||||||
|
bool get isStrict => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MetadataEntityData extends i0.DataClass
|
||||||
|
implements i0.Insertable<i1.MetadataEntityData> {
|
||||||
|
final String key;
|
||||||
|
final String value;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
const MetadataEntityData({
|
||||||
|
required this.key,
|
||||||
|
required this.value,
|
||||||
|
required this.updatedAt,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
map['key'] = i0.Variable<String>(key);
|
||||||
|
map['value'] = i0.Variable<String>(value);
|
||||||
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory MetadataEntityData.fromJson(
|
||||||
|
Map<String, dynamic> json, {
|
||||||
|
i0.ValueSerializer? serializer,
|
||||||
|
}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return MetadataEntityData(
|
||||||
|
key: serializer.fromJson<String>(json['key']),
|
||||||
|
value: serializer.fromJson<String>(json['value']),
|
||||||
|
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'key': serializer.toJson<String>(key),
|
||||||
|
'value': serializer.toJson<String>(value),
|
||||||
|
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.MetadataEntityData copyWith({
|
||||||
|
String? key,
|
||||||
|
String? value,
|
||||||
|
DateTime? updatedAt,
|
||||||
|
}) => i1.MetadataEntityData(
|
||||||
|
key: key ?? this.key,
|
||||||
|
value: value ?? this.value,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
);
|
||||||
|
MetadataEntityData copyWithCompanion(i1.MetadataEntityCompanion data) {
|
||||||
|
return MetadataEntityData(
|
||||||
|
key: data.key.present ? data.key.value : this.key,
|
||||||
|
value: data.value.present ? data.value.value : this.value,
|
||||||
|
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('MetadataEntityData(')
|
||||||
|
..write('key: $key, ')
|
||||||
|
..write('value: $value, ')
|
||||||
|
..write('updatedAt: $updatedAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(key, value, updatedAt);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is i1.MetadataEntityData &&
|
||||||
|
other.key == this.key &&
|
||||||
|
other.value == this.value &&
|
||||||
|
other.updatedAt == this.updatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MetadataEntityCompanion
|
||||||
|
extends i0.UpdateCompanion<i1.MetadataEntityData> {
|
||||||
|
final i0.Value<String> key;
|
||||||
|
final i0.Value<String> value;
|
||||||
|
final i0.Value<DateTime> updatedAt;
|
||||||
|
const MetadataEntityCompanion({
|
||||||
|
this.key = const i0.Value.absent(),
|
||||||
|
this.value = const i0.Value.absent(),
|
||||||
|
this.updatedAt = const i0.Value.absent(),
|
||||||
|
});
|
||||||
|
MetadataEntityCompanion.insert({
|
||||||
|
required String key,
|
||||||
|
required String value,
|
||||||
|
this.updatedAt = const i0.Value.absent(),
|
||||||
|
}) : key = i0.Value(key),
|
||||||
|
value = i0.Value(value);
|
||||||
|
static i0.Insertable<i1.MetadataEntityData> custom({
|
||||||
|
i0.Expression<String>? key,
|
||||||
|
i0.Expression<String>? value,
|
||||||
|
i0.Expression<DateTime>? updatedAt,
|
||||||
|
}) {
|
||||||
|
return i0.RawValuesInsertable({
|
||||||
|
if (key != null) 'key': key,
|
||||||
|
if (value != null) 'value': value,
|
||||||
|
if (updatedAt != null) 'updated_at': updatedAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.MetadataEntityCompanion copyWith({
|
||||||
|
i0.Value<String>? key,
|
||||||
|
i0.Value<String>? value,
|
||||||
|
i0.Value<DateTime>? updatedAt,
|
||||||
|
}) {
|
||||||
|
return i1.MetadataEntityCompanion(
|
||||||
|
key: key ?? this.key,
|
||||||
|
value: value ?? this.value,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
if (key.present) {
|
||||||
|
map['key'] = i0.Variable<String>(key.value);
|
||||||
|
}
|
||||||
|
if (value.present) {
|
||||||
|
map['value'] = i0.Variable<String>(value.value);
|
||||||
|
}
|
||||||
|
if (updatedAt.present) {
|
||||||
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('MetadataEntityCompanion(')
|
||||||
|
..write('key: $key, ')
|
||||||
|
..write('value: $value, ')
|
||||||
|
..write('updatedAt: $updatedAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)')
|
|
||||||
class RemoteAlbumEntity extends Table with DriftDefaultsMixin {
|
class RemoteAlbumEntity extends Table with DriftDefaultsMixin {
|
||||||
const RemoteAlbumEntity();
|
const RemoteAlbumEntity();
|
||||||
|
|
||||||
@@ -18,8 +16,6 @@ class RemoteAlbumEntity extends Table with DriftDefaultsMixin {
|
|||||||
|
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
TextColumn get ownerId => text().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
|
||||||
|
|
||||||
TextColumn get thumbnailAssetId =>
|
TextColumn get thumbnailAssetId =>
|
||||||
text().references(RemoteAssetEntity, #id, onDelete: KeyAction.setNull).nullable()();
|
text().references(RemoteAssetEntity, #id, onDelete: KeyAction.setNull).nullable()();
|
||||||
|
|
||||||
|
|||||||
+25
-215
@@ -7,11 +7,9 @@ import 'package:immich_mobile/domain/models/album/album.model.dart' as i2;
|
|||||||
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart'
|
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart'
|
||||||
as i3;
|
as i3;
|
||||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
|
||||||
as i5;
|
as i5;
|
||||||
import 'package:drift/internal/modular.dart' as i6;
|
import 'package:drift/internal/modular.dart' as i6;
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
|
|
||||||
as i7;
|
|
||||||
|
|
||||||
typedef $$RemoteAlbumEntityTableCreateCompanionBuilder =
|
typedef $$RemoteAlbumEntityTableCreateCompanionBuilder =
|
||||||
i1.RemoteAlbumEntityCompanion Function({
|
i1.RemoteAlbumEntityCompanion Function({
|
||||||
@@ -20,7 +18,6 @@ typedef $$RemoteAlbumEntityTableCreateCompanionBuilder =
|
|||||||
i0.Value<String> description,
|
i0.Value<String> description,
|
||||||
i0.Value<DateTime> createdAt,
|
i0.Value<DateTime> createdAt,
|
||||||
i0.Value<DateTime> updatedAt,
|
i0.Value<DateTime> updatedAt,
|
||||||
required String ownerId,
|
|
||||||
i0.Value<String?> thumbnailAssetId,
|
i0.Value<String?> thumbnailAssetId,
|
||||||
i0.Value<bool> isActivityEnabled,
|
i0.Value<bool> isActivityEnabled,
|
||||||
required i2.AlbumAssetOrder order,
|
required i2.AlbumAssetOrder order,
|
||||||
@@ -32,7 +29,6 @@ typedef $$RemoteAlbumEntityTableUpdateCompanionBuilder =
|
|||||||
i0.Value<String> description,
|
i0.Value<String> description,
|
||||||
i0.Value<DateTime> createdAt,
|
i0.Value<DateTime> createdAt,
|
||||||
i0.Value<DateTime> updatedAt,
|
i0.Value<DateTime> updatedAt,
|
||||||
i0.Value<String> ownerId,
|
|
||||||
i0.Value<String?> thumbnailAssetId,
|
i0.Value<String?> thumbnailAssetId,
|
||||||
i0.Value<bool> isActivityEnabled,
|
i0.Value<bool> isActivityEnabled,
|
||||||
i0.Value<i2.AlbumAssetOrder> order,
|
i0.Value<i2.AlbumAssetOrder> order,
|
||||||
@@ -51,42 +47,10 @@ final class $$RemoteAlbumEntityTableReferences
|
|||||||
super.$_typedResult,
|
super.$_typedResult,
|
||||||
);
|
);
|
||||||
|
|
||||||
static i5.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) =>
|
static i5.$RemoteAssetEntityTable _thumbnailAssetIdTable(
|
||||||
i6.ReadDatabaseContainer(db)
|
|
||||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
|
||||||
.createAlias(
|
|
||||||
i0.$_aliasNameGenerator(
|
|
||||||
i6.ReadDatabaseContainer(db)
|
|
||||||
.resultSet<i1.$RemoteAlbumEntityTable>('remote_album_entity')
|
|
||||||
.ownerId,
|
|
||||||
i6.ReadDatabaseContainer(
|
|
||||||
db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity').id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
i5.$$UserEntityTableProcessedTableManager get ownerId {
|
|
||||||
final $_column = $_itemColumn<String>('owner_id')!;
|
|
||||||
|
|
||||||
final manager = i5
|
|
||||||
.$$UserEntityTableTableManager(
|
|
||||||
$_db,
|
|
||||||
i6.ReadDatabaseContainer(
|
|
||||||
$_db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
)
|
|
||||||
.filter((f) => f.id.sqlEquals($_column));
|
|
||||||
final item = $_typedResult.readTableOrNull(_ownerIdTable($_db));
|
|
||||||
if (item == null) return manager;
|
|
||||||
return i0.ProcessedTableManager(
|
|
||||||
manager.$state.copyWith(prefetchedData: [item]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static i7.$RemoteAssetEntityTable _thumbnailAssetIdTable(
|
|
||||||
i0.GeneratedDatabase db,
|
i0.GeneratedDatabase db,
|
||||||
) => i6.ReadDatabaseContainer(db)
|
) => i6.ReadDatabaseContainer(db)
|
||||||
.resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity')
|
.resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity')
|
||||||
.createAlias(
|
.createAlias(
|
||||||
i0.$_aliasNameGenerator(
|
i0.$_aliasNameGenerator(
|
||||||
i6.ReadDatabaseContainer(db)
|
i6.ReadDatabaseContainer(db)
|
||||||
@@ -94,19 +58,19 @@ final class $$RemoteAlbumEntityTableReferences
|
|||||||
.thumbnailAssetId,
|
.thumbnailAssetId,
|
||||||
i6.ReadDatabaseContainer(
|
i6.ReadDatabaseContainer(
|
||||||
db,
|
db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity').id,
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity').id,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
i7.$$RemoteAssetEntityTableProcessedTableManager? get thumbnailAssetId {
|
i5.$$RemoteAssetEntityTableProcessedTableManager? get thumbnailAssetId {
|
||||||
final $_column = $_itemColumn<String>('thumbnail_asset_id');
|
final $_column = $_itemColumn<String>('thumbnail_asset_id');
|
||||||
if ($_column == null) return null;
|
if ($_column == null) return null;
|
||||||
final manager = i7
|
final manager = i5
|
||||||
.$$RemoteAssetEntityTableTableManager(
|
.$$RemoteAssetEntityTableTableManager(
|
||||||
$_db,
|
$_db,
|
||||||
i6.ReadDatabaseContainer(
|
i6.ReadDatabaseContainer(
|
||||||
$_db,
|
$_db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
)
|
)
|
||||||
.filter((f) => f.id.sqlEquals($_column));
|
.filter((f) => f.id.sqlEquals($_column));
|
||||||
final item = $_typedResult.readTableOrNull(_thumbnailAssetIdTable($_db));
|
final item = $_typedResult.readTableOrNull(_thumbnailAssetIdTable($_db));
|
||||||
@@ -162,51 +126,24 @@ class $$RemoteAlbumEntityTableFilterComposer
|
|||||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
i5.$$UserEntityTableFilterComposer get ownerId {
|
i5.$$RemoteAssetEntityTableFilterComposer get thumbnailAssetId {
|
||||||
final i5.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
final i5.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
|
||||||
composer: this,
|
|
||||||
getCurrentColumn: (t) => t.ownerId,
|
|
||||||
referencedTable: i6.ReadDatabaseContainer(
|
|
||||||
$db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
getReferencedColumn: (t) => t.id,
|
|
||||||
builder:
|
|
||||||
(
|
|
||||||
joinBuilder, {
|
|
||||||
$addJoinBuilderToRootComposer,
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
}) => i5.$$UserEntityTableFilterComposer(
|
|
||||||
$db: $db,
|
|
||||||
$table: i6.ReadDatabaseContainer(
|
|
||||||
$db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
|
||||||
joinBuilder: joinBuilder,
|
|
||||||
$removeJoinBuilderFromRootComposer:
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return composer;
|
|
||||||
}
|
|
||||||
|
|
||||||
i7.$$RemoteAssetEntityTableFilterComposer get thumbnailAssetId {
|
|
||||||
final i7.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
|
|
||||||
composer: this,
|
composer: this,
|
||||||
getCurrentColumn: (t) => t.thumbnailAssetId,
|
getCurrentColumn: (t) => t.thumbnailAssetId,
|
||||||
referencedTable: i6.ReadDatabaseContainer(
|
referencedTable: i6.ReadDatabaseContainer(
|
||||||
$db,
|
$db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
getReferencedColumn: (t) => t.id,
|
getReferencedColumn: (t) => t.id,
|
||||||
builder:
|
builder:
|
||||||
(
|
(
|
||||||
joinBuilder, {
|
joinBuilder, {
|
||||||
$addJoinBuilderToRootComposer,
|
$addJoinBuilderToRootComposer,
|
||||||
$removeJoinBuilderFromRootComposer,
|
$removeJoinBuilderFromRootComposer,
|
||||||
}) => i7.$$RemoteAssetEntityTableFilterComposer(
|
}) => i5.$$RemoteAssetEntityTableFilterComposer(
|
||||||
$db: $db,
|
$db: $db,
|
||||||
$table: i6.ReadDatabaseContainer(
|
$table: i6.ReadDatabaseContainer(
|
||||||
$db,
|
$db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
joinBuilder: joinBuilder,
|
joinBuilder: joinBuilder,
|
||||||
$removeJoinBuilderFromRootComposer:
|
$removeJoinBuilderFromRootComposer:
|
||||||
@@ -261,52 +198,25 @@ class $$RemoteAlbumEntityTableOrderingComposer
|
|||||||
builder: (column) => i0.ColumnOrderings(column),
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
i5.$$UserEntityTableOrderingComposer get ownerId {
|
i5.$$RemoteAssetEntityTableOrderingComposer get thumbnailAssetId {
|
||||||
final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
final i5.$$RemoteAssetEntityTableOrderingComposer composer =
|
||||||
composer: this,
|
|
||||||
getCurrentColumn: (t) => t.ownerId,
|
|
||||||
referencedTable: i6.ReadDatabaseContainer(
|
|
||||||
$db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
getReferencedColumn: (t) => t.id,
|
|
||||||
builder:
|
|
||||||
(
|
|
||||||
joinBuilder, {
|
|
||||||
$addJoinBuilderToRootComposer,
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
}) => i5.$$UserEntityTableOrderingComposer(
|
|
||||||
$db: $db,
|
|
||||||
$table: i6.ReadDatabaseContainer(
|
|
||||||
$db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
|
||||||
joinBuilder: joinBuilder,
|
|
||||||
$removeJoinBuilderFromRootComposer:
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return composer;
|
|
||||||
}
|
|
||||||
|
|
||||||
i7.$$RemoteAssetEntityTableOrderingComposer get thumbnailAssetId {
|
|
||||||
final i7.$$RemoteAssetEntityTableOrderingComposer composer =
|
|
||||||
$composerBuilder(
|
$composerBuilder(
|
||||||
composer: this,
|
composer: this,
|
||||||
getCurrentColumn: (t) => t.thumbnailAssetId,
|
getCurrentColumn: (t) => t.thumbnailAssetId,
|
||||||
referencedTable: i6.ReadDatabaseContainer(
|
referencedTable: i6.ReadDatabaseContainer(
|
||||||
$db,
|
$db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
getReferencedColumn: (t) => t.id,
|
getReferencedColumn: (t) => t.id,
|
||||||
builder:
|
builder:
|
||||||
(
|
(
|
||||||
joinBuilder, {
|
joinBuilder, {
|
||||||
$addJoinBuilderToRootComposer,
|
$addJoinBuilderToRootComposer,
|
||||||
$removeJoinBuilderFromRootComposer,
|
$removeJoinBuilderFromRootComposer,
|
||||||
}) => i7.$$RemoteAssetEntityTableOrderingComposer(
|
}) => i5.$$RemoteAssetEntityTableOrderingComposer(
|
||||||
$db: $db,
|
$db: $db,
|
||||||
$table: i6.ReadDatabaseContainer(
|
$table: i6.ReadDatabaseContainer(
|
||||||
$db,
|
$db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
joinBuilder: joinBuilder,
|
joinBuilder: joinBuilder,
|
||||||
$removeJoinBuilderFromRootComposer:
|
$removeJoinBuilderFromRootComposer:
|
||||||
@@ -351,52 +261,25 @@ class $$RemoteAlbumEntityTableAnnotationComposer
|
|||||||
i0.GeneratedColumnWithTypeConverter<i2.AlbumAssetOrder, int> get order =>
|
i0.GeneratedColumnWithTypeConverter<i2.AlbumAssetOrder, int> get order =>
|
||||||
$composableBuilder(column: $table.order, builder: (column) => column);
|
$composableBuilder(column: $table.order, builder: (column) => column);
|
||||||
|
|
||||||
i5.$$UserEntityTableAnnotationComposer get ownerId {
|
i5.$$RemoteAssetEntityTableAnnotationComposer get thumbnailAssetId {
|
||||||
final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
final i5.$$RemoteAssetEntityTableAnnotationComposer composer =
|
||||||
composer: this,
|
|
||||||
getCurrentColumn: (t) => t.ownerId,
|
|
||||||
referencedTable: i6.ReadDatabaseContainer(
|
|
||||||
$db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
getReferencedColumn: (t) => t.id,
|
|
||||||
builder:
|
|
||||||
(
|
|
||||||
joinBuilder, {
|
|
||||||
$addJoinBuilderToRootComposer,
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
}) => i5.$$UserEntityTableAnnotationComposer(
|
|
||||||
$db: $db,
|
|
||||||
$table: i6.ReadDatabaseContainer(
|
|
||||||
$db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
|
||||||
joinBuilder: joinBuilder,
|
|
||||||
$removeJoinBuilderFromRootComposer:
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return composer;
|
|
||||||
}
|
|
||||||
|
|
||||||
i7.$$RemoteAssetEntityTableAnnotationComposer get thumbnailAssetId {
|
|
||||||
final i7.$$RemoteAssetEntityTableAnnotationComposer composer =
|
|
||||||
$composerBuilder(
|
$composerBuilder(
|
||||||
composer: this,
|
composer: this,
|
||||||
getCurrentColumn: (t) => t.thumbnailAssetId,
|
getCurrentColumn: (t) => t.thumbnailAssetId,
|
||||||
referencedTable: i6.ReadDatabaseContainer(
|
referencedTable: i6.ReadDatabaseContainer(
|
||||||
$db,
|
$db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
getReferencedColumn: (t) => t.id,
|
getReferencedColumn: (t) => t.id,
|
||||||
builder:
|
builder:
|
||||||
(
|
(
|
||||||
joinBuilder, {
|
joinBuilder, {
|
||||||
$addJoinBuilderToRootComposer,
|
$addJoinBuilderToRootComposer,
|
||||||
$removeJoinBuilderFromRootComposer,
|
$removeJoinBuilderFromRootComposer,
|
||||||
}) => i7.$$RemoteAssetEntityTableAnnotationComposer(
|
}) => i5.$$RemoteAssetEntityTableAnnotationComposer(
|
||||||
$db: $db,
|
$db: $db,
|
||||||
$table: i6.ReadDatabaseContainer(
|
$table: i6.ReadDatabaseContainer(
|
||||||
$db,
|
$db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
joinBuilder: joinBuilder,
|
joinBuilder: joinBuilder,
|
||||||
$removeJoinBuilderFromRootComposer:
|
$removeJoinBuilderFromRootComposer:
|
||||||
@@ -420,7 +303,7 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
$$RemoteAlbumEntityTableUpdateCompanionBuilder,
|
$$RemoteAlbumEntityTableUpdateCompanionBuilder,
|
||||||
(i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences),
|
(i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences),
|
||||||
i1.RemoteAlbumEntityData,
|
i1.RemoteAlbumEntityData,
|
||||||
i0.PrefetchHooks Function({bool ownerId, bool thumbnailAssetId})
|
i0.PrefetchHooks Function({bool thumbnailAssetId})
|
||||||
> {
|
> {
|
||||||
$$RemoteAlbumEntityTableTableManager(
|
$$RemoteAlbumEntityTableTableManager(
|
||||||
i0.GeneratedDatabase db,
|
i0.GeneratedDatabase db,
|
||||||
@@ -445,7 +328,6 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
i0.Value<String> description = const i0.Value.absent(),
|
i0.Value<String> description = const i0.Value.absent(),
|
||||||
i0.Value<DateTime> createdAt = const i0.Value.absent(),
|
i0.Value<DateTime> createdAt = const i0.Value.absent(),
|
||||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
i0.Value<String> ownerId = const i0.Value.absent(),
|
|
||||||
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
|
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
|
||||||
i0.Value<bool> isActivityEnabled = const i0.Value.absent(),
|
i0.Value<bool> isActivityEnabled = const i0.Value.absent(),
|
||||||
i0.Value<i2.AlbumAssetOrder> order = const i0.Value.absent(),
|
i0.Value<i2.AlbumAssetOrder> order = const i0.Value.absent(),
|
||||||
@@ -455,7 +337,6 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
description: description,
|
description: description,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
ownerId: ownerId,
|
|
||||||
thumbnailAssetId: thumbnailAssetId,
|
thumbnailAssetId: thumbnailAssetId,
|
||||||
isActivityEnabled: isActivityEnabled,
|
isActivityEnabled: isActivityEnabled,
|
||||||
order: order,
|
order: order,
|
||||||
@@ -467,7 +348,6 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
i0.Value<String> description = const i0.Value.absent(),
|
i0.Value<String> description = const i0.Value.absent(),
|
||||||
i0.Value<DateTime> createdAt = const i0.Value.absent(),
|
i0.Value<DateTime> createdAt = const i0.Value.absent(),
|
||||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
required String ownerId,
|
|
||||||
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
|
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
|
||||||
i0.Value<bool> isActivityEnabled = const i0.Value.absent(),
|
i0.Value<bool> isActivityEnabled = const i0.Value.absent(),
|
||||||
required i2.AlbumAssetOrder order,
|
required i2.AlbumAssetOrder order,
|
||||||
@@ -477,7 +357,6 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
description: description,
|
description: description,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
ownerId: ownerId,
|
|
||||||
thumbnailAssetId: thumbnailAssetId,
|
thumbnailAssetId: thumbnailAssetId,
|
||||||
isActivityEnabled: isActivityEnabled,
|
isActivityEnabled: isActivityEnabled,
|
||||||
order: order,
|
order: order,
|
||||||
@@ -490,7 +369,7 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
prefetchHooksCallback: ({ownerId = false, thumbnailAssetId = false}) {
|
prefetchHooksCallback: ({thumbnailAssetId = false}) {
|
||||||
return i0.PrefetchHooks(
|
return i0.PrefetchHooks(
|
||||||
db: db,
|
db: db,
|
||||||
explicitlyWatchedTables: [],
|
explicitlyWatchedTables: [],
|
||||||
@@ -510,21 +389,6 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
dynamic
|
dynamic
|
||||||
>
|
>
|
||||||
>(state) {
|
>(state) {
|
||||||
if (ownerId) {
|
|
||||||
state =
|
|
||||||
state.withJoin(
|
|
||||||
currentTable: table,
|
|
||||||
currentColumn: table.ownerId,
|
|
||||||
referencedTable: i1
|
|
||||||
.$$RemoteAlbumEntityTableReferences
|
|
||||||
._ownerIdTable(db),
|
|
||||||
referencedColumn: i1
|
|
||||||
.$$RemoteAlbumEntityTableReferences
|
|
||||||
._ownerIdTable(db)
|
|
||||||
.id,
|
|
||||||
)
|
|
||||||
as T;
|
|
||||||
}
|
|
||||||
if (thumbnailAssetId) {
|
if (thumbnailAssetId) {
|
||||||
state =
|
state =
|
||||||
state.withJoin(
|
state.withJoin(
|
||||||
@@ -564,12 +428,8 @@ typedef $$RemoteAlbumEntityTableProcessedTableManager =
|
|||||||
$$RemoteAlbumEntityTableUpdateCompanionBuilder,
|
$$RemoteAlbumEntityTableUpdateCompanionBuilder,
|
||||||
(i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences),
|
(i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences),
|
||||||
i1.RemoteAlbumEntityData,
|
i1.RemoteAlbumEntityData,
|
||||||
i0.PrefetchHooks Function({bool ownerId, bool thumbnailAssetId})
|
i0.PrefetchHooks Function({bool thumbnailAssetId})
|
||||||
>;
|
>;
|
||||||
i0.Index get idxRemoteAlbumOwnerId => i0.Index(
|
|
||||||
'idx_remote_album_owner_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)',
|
|
||||||
);
|
|
||||||
|
|
||||||
class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
|
class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
|
||||||
with i0.TableInfo<$RemoteAlbumEntityTable, i1.RemoteAlbumEntityData> {
|
with i0.TableInfo<$RemoteAlbumEntityTable, i1.RemoteAlbumEntityData> {
|
||||||
@@ -636,20 +496,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
|
|||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultValue: i4.currentDateAndTime,
|
defaultValue: i4.currentDateAndTime,
|
||||||
);
|
);
|
||||||
static const i0.VerificationMeta _ownerIdMeta = const i0.VerificationMeta(
|
|
||||||
'ownerId',
|
|
||||||
);
|
|
||||||
@override
|
|
||||||
late final i0.GeneratedColumn<String> ownerId = i0.GeneratedColumn<String>(
|
|
||||||
'owner_id',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: i0.DriftSqlType.string,
|
|
||||||
requiredDuringInsert: true,
|
|
||||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
|
||||||
'REFERENCES user_entity (id) ON DELETE CASCADE',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
static const i0.VerificationMeta _thumbnailAssetIdMeta =
|
static const i0.VerificationMeta _thumbnailAssetIdMeta =
|
||||||
const i0.VerificationMeta('thumbnailAssetId');
|
const i0.VerificationMeta('thumbnailAssetId');
|
||||||
@override
|
@override
|
||||||
@@ -698,7 +544,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
|
|||||||
description,
|
description,
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
ownerId,
|
|
||||||
thumbnailAssetId,
|
thumbnailAssetId,
|
||||||
isActivityEnabled,
|
isActivityEnabled,
|
||||||
order,
|
order,
|
||||||
@@ -749,14 +594,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
|
|||||||
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta),
|
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (data.containsKey('owner_id')) {
|
|
||||||
context.handle(
|
|
||||||
_ownerIdMeta,
|
|
||||||
ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta),
|
|
||||||
);
|
|
||||||
} else if (isInserting) {
|
|
||||||
context.missing(_ownerIdMeta);
|
|
||||||
}
|
|
||||||
if (data.containsKey('thumbnail_asset_id')) {
|
if (data.containsKey('thumbnail_asset_id')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_thumbnailAssetIdMeta,
|
_thumbnailAssetIdMeta,
|
||||||
@@ -807,10 +644,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
|
|||||||
i0.DriftSqlType.dateTime,
|
i0.DriftSqlType.dateTime,
|
||||||
data['${effectivePrefix}updated_at'],
|
data['${effectivePrefix}updated_at'],
|
||||||
)!,
|
)!,
|
||||||
ownerId: attachedDatabase.typeMapping.read(
|
|
||||||
i0.DriftSqlType.string,
|
|
||||||
data['${effectivePrefix}owner_id'],
|
|
||||||
)!,
|
|
||||||
thumbnailAssetId: attachedDatabase.typeMapping.read(
|
thumbnailAssetId: attachedDatabase.typeMapping.read(
|
||||||
i0.DriftSqlType.string,
|
i0.DriftSqlType.string,
|
||||||
data['${effectivePrefix}thumbnail_asset_id'],
|
data['${effectivePrefix}thumbnail_asset_id'],
|
||||||
@@ -850,7 +683,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
final String description;
|
final String description;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
final DateTime updatedAt;
|
final DateTime updatedAt;
|
||||||
final String ownerId;
|
|
||||||
final String? thumbnailAssetId;
|
final String? thumbnailAssetId;
|
||||||
final bool isActivityEnabled;
|
final bool isActivityEnabled;
|
||||||
final i2.AlbumAssetOrder order;
|
final i2.AlbumAssetOrder order;
|
||||||
@@ -860,7 +692,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
required this.description,
|
required this.description,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
required this.ownerId,
|
|
||||||
this.thumbnailAssetId,
|
this.thumbnailAssetId,
|
||||||
required this.isActivityEnabled,
|
required this.isActivityEnabled,
|
||||||
required this.order,
|
required this.order,
|
||||||
@@ -873,7 +704,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
map['description'] = i0.Variable<String>(description);
|
map['description'] = i0.Variable<String>(description);
|
||||||
map['created_at'] = i0.Variable<DateTime>(createdAt);
|
map['created_at'] = i0.Variable<DateTime>(createdAt);
|
||||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
||||||
map['owner_id'] = i0.Variable<String>(ownerId);
|
|
||||||
if (!nullToAbsent || thumbnailAssetId != null) {
|
if (!nullToAbsent || thumbnailAssetId != null) {
|
||||||
map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId);
|
map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId);
|
||||||
}
|
}
|
||||||
@@ -897,7 +727,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
description: serializer.fromJson<String>(json['description']),
|
description: serializer.fromJson<String>(json['description']),
|
||||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||||
ownerId: serializer.fromJson<String>(json['ownerId']),
|
|
||||||
thumbnailAssetId: serializer.fromJson<String?>(json['thumbnailAssetId']),
|
thumbnailAssetId: serializer.fromJson<String?>(json['thumbnailAssetId']),
|
||||||
isActivityEnabled: serializer.fromJson<bool>(json['isActivityEnabled']),
|
isActivityEnabled: serializer.fromJson<bool>(json['isActivityEnabled']),
|
||||||
order: i1.$RemoteAlbumEntityTable.$converterorder.fromJson(
|
order: i1.$RemoteAlbumEntityTable.$converterorder.fromJson(
|
||||||
@@ -914,7 +743,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
'description': serializer.toJson<String>(description),
|
'description': serializer.toJson<String>(description),
|
||||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
||||||
'ownerId': serializer.toJson<String>(ownerId),
|
|
||||||
'thumbnailAssetId': serializer.toJson<String?>(thumbnailAssetId),
|
'thumbnailAssetId': serializer.toJson<String?>(thumbnailAssetId),
|
||||||
'isActivityEnabled': serializer.toJson<bool>(isActivityEnabled),
|
'isActivityEnabled': serializer.toJson<bool>(isActivityEnabled),
|
||||||
'order': serializer.toJson<int>(
|
'order': serializer.toJson<int>(
|
||||||
@@ -929,7 +757,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
String? description,
|
String? description,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
String? ownerId,
|
|
||||||
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
|
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
|
||||||
bool? isActivityEnabled,
|
bool? isActivityEnabled,
|
||||||
i2.AlbumAssetOrder? order,
|
i2.AlbumAssetOrder? order,
|
||||||
@@ -939,7 +766,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
description: description ?? this.description,
|
description: description ?? this.description,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
ownerId: ownerId ?? this.ownerId,
|
|
||||||
thumbnailAssetId: thumbnailAssetId.present
|
thumbnailAssetId: thumbnailAssetId.present
|
||||||
? thumbnailAssetId.value
|
? thumbnailAssetId.value
|
||||||
: this.thumbnailAssetId,
|
: this.thumbnailAssetId,
|
||||||
@@ -955,7 +781,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
: this.description,
|
: this.description,
|
||||||
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
||||||
ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId,
|
|
||||||
thumbnailAssetId: data.thumbnailAssetId.present
|
thumbnailAssetId: data.thumbnailAssetId.present
|
||||||
? data.thumbnailAssetId.value
|
? data.thumbnailAssetId.value
|
||||||
: this.thumbnailAssetId,
|
: this.thumbnailAssetId,
|
||||||
@@ -974,7 +799,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
..write('description: $description, ')
|
..write('description: $description, ')
|
||||||
..write('createdAt: $createdAt, ')
|
..write('createdAt: $createdAt, ')
|
||||||
..write('updatedAt: $updatedAt, ')
|
..write('updatedAt: $updatedAt, ')
|
||||||
..write('ownerId: $ownerId, ')
|
|
||||||
..write('thumbnailAssetId: $thumbnailAssetId, ')
|
..write('thumbnailAssetId: $thumbnailAssetId, ')
|
||||||
..write('isActivityEnabled: $isActivityEnabled, ')
|
..write('isActivityEnabled: $isActivityEnabled, ')
|
||||||
..write('order: $order')
|
..write('order: $order')
|
||||||
@@ -989,7 +813,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
description,
|
description,
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
ownerId,
|
|
||||||
thumbnailAssetId,
|
thumbnailAssetId,
|
||||||
isActivityEnabled,
|
isActivityEnabled,
|
||||||
order,
|
order,
|
||||||
@@ -1003,7 +826,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
other.description == this.description &&
|
other.description == this.description &&
|
||||||
other.createdAt == this.createdAt &&
|
other.createdAt == this.createdAt &&
|
||||||
other.updatedAt == this.updatedAt &&
|
other.updatedAt == this.updatedAt &&
|
||||||
other.ownerId == this.ownerId &&
|
|
||||||
other.thumbnailAssetId == this.thumbnailAssetId &&
|
other.thumbnailAssetId == this.thumbnailAssetId &&
|
||||||
other.isActivityEnabled == this.isActivityEnabled &&
|
other.isActivityEnabled == this.isActivityEnabled &&
|
||||||
other.order == this.order);
|
other.order == this.order);
|
||||||
@@ -1016,7 +838,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
final i0.Value<String> description;
|
final i0.Value<String> description;
|
||||||
final i0.Value<DateTime> createdAt;
|
final i0.Value<DateTime> createdAt;
|
||||||
final i0.Value<DateTime> updatedAt;
|
final i0.Value<DateTime> updatedAt;
|
||||||
final i0.Value<String> ownerId;
|
|
||||||
final i0.Value<String?> thumbnailAssetId;
|
final i0.Value<String?> thumbnailAssetId;
|
||||||
final i0.Value<bool> isActivityEnabled;
|
final i0.Value<bool> isActivityEnabled;
|
||||||
final i0.Value<i2.AlbumAssetOrder> order;
|
final i0.Value<i2.AlbumAssetOrder> order;
|
||||||
@@ -1026,7 +847,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
this.description = const i0.Value.absent(),
|
this.description = const i0.Value.absent(),
|
||||||
this.createdAt = const i0.Value.absent(),
|
this.createdAt = const i0.Value.absent(),
|
||||||
this.updatedAt = const i0.Value.absent(),
|
this.updatedAt = const i0.Value.absent(),
|
||||||
this.ownerId = const i0.Value.absent(),
|
|
||||||
this.thumbnailAssetId = const i0.Value.absent(),
|
this.thumbnailAssetId = const i0.Value.absent(),
|
||||||
this.isActivityEnabled = const i0.Value.absent(),
|
this.isActivityEnabled = const i0.Value.absent(),
|
||||||
this.order = const i0.Value.absent(),
|
this.order = const i0.Value.absent(),
|
||||||
@@ -1037,13 +857,11 @@ class RemoteAlbumEntityCompanion
|
|||||||
this.description = const i0.Value.absent(),
|
this.description = const i0.Value.absent(),
|
||||||
this.createdAt = const i0.Value.absent(),
|
this.createdAt = const i0.Value.absent(),
|
||||||
this.updatedAt = const i0.Value.absent(),
|
this.updatedAt = const i0.Value.absent(),
|
||||||
required String ownerId,
|
|
||||||
this.thumbnailAssetId = const i0.Value.absent(),
|
this.thumbnailAssetId = const i0.Value.absent(),
|
||||||
this.isActivityEnabled = const i0.Value.absent(),
|
this.isActivityEnabled = const i0.Value.absent(),
|
||||||
required i2.AlbumAssetOrder order,
|
required i2.AlbumAssetOrder order,
|
||||||
}) : id = i0.Value(id),
|
}) : id = i0.Value(id),
|
||||||
name = i0.Value(name),
|
name = i0.Value(name),
|
||||||
ownerId = i0.Value(ownerId),
|
|
||||||
order = i0.Value(order);
|
order = i0.Value(order);
|
||||||
static i0.Insertable<i1.RemoteAlbumEntityData> custom({
|
static i0.Insertable<i1.RemoteAlbumEntityData> custom({
|
||||||
i0.Expression<String>? id,
|
i0.Expression<String>? id,
|
||||||
@@ -1051,7 +869,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
i0.Expression<String>? description,
|
i0.Expression<String>? description,
|
||||||
i0.Expression<DateTime>? createdAt,
|
i0.Expression<DateTime>? createdAt,
|
||||||
i0.Expression<DateTime>? updatedAt,
|
i0.Expression<DateTime>? updatedAt,
|
||||||
i0.Expression<String>? ownerId,
|
|
||||||
i0.Expression<String>? thumbnailAssetId,
|
i0.Expression<String>? thumbnailAssetId,
|
||||||
i0.Expression<bool>? isActivityEnabled,
|
i0.Expression<bool>? isActivityEnabled,
|
||||||
i0.Expression<int>? order,
|
i0.Expression<int>? order,
|
||||||
@@ -1062,7 +879,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
if (description != null) 'description': description,
|
if (description != null) 'description': description,
|
||||||
if (createdAt != null) 'created_at': createdAt,
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
if (updatedAt != null) 'updated_at': updatedAt,
|
if (updatedAt != null) 'updated_at': updatedAt,
|
||||||
if (ownerId != null) 'owner_id': ownerId,
|
|
||||||
if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId,
|
if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId,
|
||||||
if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled,
|
if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled,
|
||||||
if (order != null) 'order': order,
|
if (order != null) 'order': order,
|
||||||
@@ -1075,7 +891,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
i0.Value<String>? description,
|
i0.Value<String>? description,
|
||||||
i0.Value<DateTime>? createdAt,
|
i0.Value<DateTime>? createdAt,
|
||||||
i0.Value<DateTime>? updatedAt,
|
i0.Value<DateTime>? updatedAt,
|
||||||
i0.Value<String>? ownerId,
|
|
||||||
i0.Value<String?>? thumbnailAssetId,
|
i0.Value<String?>? thumbnailAssetId,
|
||||||
i0.Value<bool>? isActivityEnabled,
|
i0.Value<bool>? isActivityEnabled,
|
||||||
i0.Value<i2.AlbumAssetOrder>? order,
|
i0.Value<i2.AlbumAssetOrder>? order,
|
||||||
@@ -1086,7 +901,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
description: description ?? this.description,
|
description: description ?? this.description,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
ownerId: ownerId ?? this.ownerId,
|
|
||||||
thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId,
|
thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId,
|
||||||
isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled,
|
isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled,
|
||||||
order: order ?? this.order,
|
order: order ?? this.order,
|
||||||
@@ -1111,9 +925,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
if (updatedAt.present) {
|
if (updatedAt.present) {
|
||||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
||||||
}
|
}
|
||||||
if (ownerId.present) {
|
|
||||||
map['owner_id'] = i0.Variable<String>(ownerId.value);
|
|
||||||
}
|
|
||||||
if (thumbnailAssetId.present) {
|
if (thumbnailAssetId.present) {
|
||||||
map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId.value);
|
map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId.value);
|
||||||
}
|
}
|
||||||
@@ -1136,7 +947,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
..write('description: $description, ')
|
..write('description: $description, ')
|
||||||
..write('createdAt: $createdAt, ')
|
..write('createdAt: $createdAt, ')
|
||||||
..write('updatedAt: $updatedAt, ')
|
..write('updatedAt: $updatedAt, ')
|
||||||
..write('ownerId: $ownerId, ')
|
|
||||||
..write('thumbnailAssetId: $thumbnailAssetId, ')
|
..write('thumbnailAssetId: $thumbnailAssetId, ')
|
||||||
..write('isActivityEnabled: $isActivityEnabled, ')
|
..write('isActivityEnabled: $isActivityEnabled, ')
|
||||||
..write('order: $order')
|
..write('order: $order')
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/metadata.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/person.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/person.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
|
||||||
@@ -53,6 +54,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.steps.da
|
|||||||
StoreEntity,
|
StoreEntity,
|
||||||
TrashedLocalAssetEntity,
|
TrashedLocalAssetEntity,
|
||||||
AssetEditEntity,
|
AssetEditEntity,
|
||||||
|
MetadataEntity,
|
||||||
],
|
],
|
||||||
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
||||||
)
|
)
|
||||||
@@ -84,7 +86,7 @@ class Drift extends $Drift {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 23;
|
int get schemaVersion => 25;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -246,6 +248,13 @@ class Drift extends $Drift {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
from23To24: (m, v24) async {
|
||||||
|
await customStatement('DROP INDEX IF EXISTS idx_remote_album_owner_id');
|
||||||
|
await m.alterTable(TableMigration(v24.remoteAlbumEntity));
|
||||||
|
},
|
||||||
|
from24To25: (m, v25) async {
|
||||||
|
await m.createTable(v25.metadata);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+12
-14
@@ -43,9 +43,11 @@ import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity
|
|||||||
as i20;
|
as i20;
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart'
|
||||||
as i21;
|
as i21;
|
||||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart'
|
||||||
as i22;
|
as i22;
|
||||||
import 'package:drift/internal/modular.dart' as i23;
|
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||||
|
as i23;
|
||||||
|
import 'package:drift/internal/modular.dart' as i24;
|
||||||
|
|
||||||
abstract class $Drift extends i0.GeneratedDatabase {
|
abstract class $Drift extends i0.GeneratedDatabase {
|
||||||
$Drift(i0.QueryExecutor e) : super(e);
|
$Drift(i0.QueryExecutor e) : super(e);
|
||||||
@@ -89,9 +91,12 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
.$TrashedLocalAssetEntityTable(this);
|
.$TrashedLocalAssetEntityTable(this);
|
||||||
late final i21.$AssetEditEntityTable assetEditEntity = i21
|
late final i21.$AssetEditEntityTable assetEditEntity = i21
|
||||||
.$AssetEditEntityTable(this);
|
.$AssetEditEntityTable(this);
|
||||||
i22.MergedAssetDrift get mergedAssetDrift => i23.ReadDatabaseContainer(
|
late final i22.$MetadataEntityTable metadataEntity = i22.$MetadataEntityTable(
|
||||||
this,
|
this,
|
||||||
).accessor<i22.MergedAssetDrift>(i22.MergedAssetDrift.new);
|
);
|
||||||
|
i23.MergedAssetDrift get mergedAssetDrift => i24.ReadDatabaseContainer(
|
||||||
|
this,
|
||||||
|
).accessor<i23.MergedAssetDrift>(i23.MergedAssetDrift.new);
|
||||||
@override
|
@override
|
||||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||||
@@ -105,7 +110,6 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
localAlbumEntity,
|
localAlbumEntity,
|
||||||
localAlbumAssetEntity,
|
localAlbumAssetEntity,
|
||||||
i7.idxLocalAlbumAssetAlbumAsset,
|
i7.idxLocalAlbumAssetAlbumAsset,
|
||||||
i5.idxRemoteAlbumOwnerId,
|
|
||||||
i4.idxLocalAssetChecksum,
|
i4.idxLocalAssetChecksum,
|
||||||
i4.idxLocalAssetCloudId,
|
i4.idxLocalAssetCloudId,
|
||||||
i3.idxStackPrimaryAssetId,
|
i3.idxStackPrimaryAssetId,
|
||||||
@@ -130,6 +134,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
storeEntity,
|
storeEntity,
|
||||||
trashedLocalAssetEntity,
|
trashedLocalAssetEntity,
|
||||||
assetEditEntity,
|
assetEditEntity,
|
||||||
|
metadataEntity,
|
||||||
i10.idxPartnerSharedWithId,
|
i10.idxPartnerSharedWithId,
|
||||||
i11.idxLatLng,
|
i11.idxLatLng,
|
||||||
i12.idxRemoteAlbumAssetAlbumAsset,
|
i12.idxRemoteAlbumAssetAlbumAsset,
|
||||||
@@ -160,15 +165,6 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
),
|
),
|
||||||
result: [i0.TableUpdate('stack_entity', kind: i0.UpdateKind.delete)],
|
result: [i0.TableUpdate('stack_entity', kind: i0.UpdateKind.delete)],
|
||||||
),
|
),
|
||||||
i0.WritePropagation(
|
|
||||||
on: i0.TableUpdateQuery.onTableName(
|
|
||||||
'user_entity',
|
|
||||||
limitUpdateKind: i0.UpdateKind.delete,
|
|
||||||
),
|
|
||||||
result: [
|
|
||||||
i0.TableUpdate('remote_album_entity', kind: i0.UpdateKind.delete),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
i0.WritePropagation(
|
i0.WritePropagation(
|
||||||
on: i0.TableUpdateQuery.onTableName(
|
on: i0.TableUpdateQuery.onTableName(
|
||||||
'remote_asset_entity',
|
'remote_asset_entity',
|
||||||
@@ -399,4 +395,6 @@ class $DriftManager {
|
|||||||
);
|
);
|
||||||
i21.$$AssetEditEntityTableTableManager get assetEditEntity =>
|
i21.$$AssetEditEntityTableTableManager get assetEditEntity =>
|
||||||
i21.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity);
|
i21.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity);
|
||||||
|
i22.$$MetadataEntityTableTableManager get metadataEntity =>
|
||||||
|
i22.$$MetadataEntityTableTableManager(_db, _db.metadataEntity);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -109,31 +109,40 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
return query.map((localAlbum) => localAlbum.toDto()).get();
|
return query.map((localAlbum) => localAlbum.toDto()).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, List<LocalAsset>>> getAssetsFromBackupAlbums(Iterable<String> checksums) async {
|
Future<Map<String, List<LocalAsset>>> getAssetsFromBackupAlbums(Iterable<String> remoteIds) async {
|
||||||
if (checksums.isEmpty) {
|
if (remoteIds.isEmpty) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = <String, List<LocalAsset>>{};
|
final result = <String, List<LocalAsset>>{};
|
||||||
|
|
||||||
for (final slice in checksums.toSet().slices(kDriftMaxChunk)) {
|
for (final slice in remoteIds.toSet().slices(kDriftMaxChunk)) {
|
||||||
final rows =
|
final rows =
|
||||||
await (_db.select(_db.localAlbumAssetEntity).join([
|
await (_db.select(_db.localAlbumAssetEntity).join([
|
||||||
innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id)),
|
innerJoin(
|
||||||
|
_db.localAlbumEntity,
|
||||||
|
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)),
|
innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)),
|
||||||
|
innerJoin(
|
||||||
|
_db.remoteAssetEntity,
|
||||||
|
_db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
])..where(
|
])..where(
|
||||||
_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) &
|
_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) &
|
||||||
_db.localAssetEntity.checksum.isIn(slice),
|
_db.remoteAssetEntity.id.isIn(slice),
|
||||||
))
|
))
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
for (final row in rows) {
|
for (final row in rows) {
|
||||||
final albumId = row.readTable(_db.localAlbumAssetEntity).albumId;
|
final albumId = row.readTable(_db.localAlbumAssetEntity).albumId;
|
||||||
final assetData = row.readTable(_db.localAssetEntity);
|
final asset = row.readTable(_db.localAssetEntity).toDto();
|
||||||
final asset = assetData.toDto();
|
|
||||||
(result[albumId] ??= <LocalAsset>[]).add(asset);
|
(result[albumId] ??= <LocalAsset>[]).add(asset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import 'package:drift/drift.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/metadata_key.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
|
||||||
|
class MetadataRepository extends DriftDatabaseRepository {
|
||||||
|
final Drift _db;
|
||||||
|
final Map<MetadataKey, Object> _cache = {};
|
||||||
|
|
||||||
|
MetadataRepository._(this._db) : super(_db);
|
||||||
|
|
||||||
|
static MetadataRepository? _instance;
|
||||||
|
|
||||||
|
static MetadataRepository get instance {
|
||||||
|
final instance = _instance;
|
||||||
|
if (instance == null) {
|
||||||
|
throw StateError('MetadataRepository not initialized. Call ensureInitialized() first');
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppConfig _appConfig = const .new();
|
||||||
|
AppConfig get appConfig => _appConfig;
|
||||||
|
|
||||||
|
SystemConfig _systemConfig = const .new();
|
||||||
|
SystemConfig get systemConfig => _systemConfig;
|
||||||
|
|
||||||
|
static Future<MetadataRepository> ensureInitialized(Drift db) async {
|
||||||
|
if (_instance == null) {
|
||||||
|
final instance = MetadataRepository._(db);
|
||||||
|
await instance._hydrate();
|
||||||
|
_instance = instance;
|
||||||
|
}
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> refresh() async {
|
||||||
|
instance._cache.clear();
|
||||||
|
instance._appConfig = const .new();
|
||||||
|
instance._systemConfig = const .new();
|
||||||
|
await instance._hydrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _hydrate() async => _hydrateCache(await _db.select(_db.metadataEntity).get());
|
||||||
|
|
||||||
|
T _read<T extends Object>(MetadataKey<T> key) => (_cache[key] as T?) ?? key.defaultValue;
|
||||||
|
|
||||||
|
Future<void> write<T extends Object>(MetadataKey<T> key, T value) async {
|
||||||
|
if (_read(key) == value) return;
|
||||||
|
|
||||||
|
await _db
|
||||||
|
.into(_db.metadataEntity)
|
||||||
|
.insertOnConflictUpdate(
|
||||||
|
MetadataEntityCompanion.insert(key: key.key, value: key.encode(value), updatedAt: Value(DateTime.now())),
|
||||||
|
);
|
||||||
|
_updateCache(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> delete<T extends Object>(MetadataKey<T> key) async {
|
||||||
|
await (_db.delete(_db.metadataEntity)..where((t) => t.key.equals(key.key))).go();
|
||||||
|
_updateCache(key, key.defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<AppConfig> watchAppConfig() => _watchDomain(MetadataDomain.appConfig).distinct();
|
||||||
|
|
||||||
|
Stream<SystemConfig> watchSystemConfig() => _watchDomain(MetadataDomain.systemConfig).distinct();
|
||||||
|
|
||||||
|
Stream<T> _watchDomain<T extends Object>(MetadataDomain<T> domain) {
|
||||||
|
final query = _db.select(_db.metadataEntity)..where((t) => t.key.like('${domain.prefix}.%'));
|
||||||
|
return query.watch().map((rows) {
|
||||||
|
_hydrateCache(rows);
|
||||||
|
return domain.config(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hydrateCache(List<MetadataEntityData> rows) {
|
||||||
|
final keyMap = MetadataKey.asKeyMap();
|
||||||
|
for (final row in rows) {
|
||||||
|
final key = keyMap[row.key];
|
||||||
|
if (key == null) continue;
|
||||||
|
_updateCache(key, key.decode(row.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateCache<T extends Object>(MetadataKey<T> key, T value) {
|
||||||
|
if (_cache[key] == value) return;
|
||||||
|
_cache[key] = value;
|
||||||
|
key.domain.rebuild(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension<T extends Object> on MetadataDomain<T> {
|
||||||
|
T config(MetadataRepository repo) => switch (this) {
|
||||||
|
.appConfig => repo._appConfig as T,
|
||||||
|
.systemConfig => repo._systemConfig as T,
|
||||||
|
};
|
||||||
|
|
||||||
|
void rebuild(MetadataRepository repo) {
|
||||||
|
switch (this) {
|
||||||
|
case .appConfig:
|
||||||
|
repo._appConfig = .new(theme: .new(mode: repo._read(.themeMode)));
|
||||||
|
case .systemConfig:
|
||||||
|
repo._systemConfig = .new(logLevel: repo._read(.logLevel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,17 +32,23 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
leftOuterJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), useColumns: false),
|
|
||||||
leftOuterJoin(
|
leftOuterJoin(
|
||||||
_db.remoteAlbumUserEntity,
|
_db.remoteAlbumUserEntity,
|
||||||
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
|
innerJoin(
|
||||||
|
_db.userEntity,
|
||||||
|
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) &
|
||||||
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
|
||||||
|
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
query
|
query
|
||||||
..where(_db.remoteAssetEntity.deletedAt.isNull())
|
..where(_db.remoteAssetEntity.deletedAt.isNull())
|
||||||
..addColumns([assetCount])
|
..addColumns([assetCount])
|
||||||
..addColumns([_db.userEntity.name])
|
..addColumns([_db.userEntity.name, _db.userEntity.id])
|
||||||
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
||||||
..groupBy([_db.remoteAlbumEntity.id]);
|
..groupBy([_db.remoteAlbumEntity.id]);
|
||||||
|
|
||||||
@@ -63,6 +69,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
.readTable(_db.remoteAlbumEntity)
|
.readTable(_db.remoteAlbumEntity)
|
||||||
.toDto(
|
.toDto(
|
||||||
assetCount: row.read(assetCount) ?? 0,
|
assetCount: row.read(assetCount) ?? 0,
|
||||||
|
ownerId: row.read(_db.userEntity.id)!,
|
||||||
ownerName: row.read(_db.userEntity.name)!,
|
ownerName: row.read(_db.userEntity.name)!,
|
||||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
||||||
),
|
),
|
||||||
@@ -85,20 +92,22 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
leftOuterJoin(
|
|
||||||
_db.userEntity,
|
|
||||||
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
|
|
||||||
useColumns: false,
|
|
||||||
),
|
|
||||||
leftOuterJoin(
|
leftOuterJoin(
|
||||||
_db.remoteAlbumUserEntity,
|
_db.remoteAlbumUserEntity,
|
||||||
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
|
innerJoin(
|
||||||
|
_db.userEntity,
|
||||||
|
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) &
|
||||||
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
|
||||||
|
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(_db.remoteAlbumEntity.id.equals(albumId) & _db.remoteAssetEntity.deletedAt.isNull())
|
..where(_db.remoteAlbumEntity.id.equals(albumId) & _db.remoteAssetEntity.deletedAt.isNull())
|
||||||
..addColumns([assetCount])
|
..addColumns([assetCount])
|
||||||
..addColumns([_db.userEntity.name])
|
..addColumns([_db.userEntity.name, _db.userEntity.id])
|
||||||
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
||||||
..groupBy([_db.remoteAlbumEntity.id]);
|
..groupBy([_db.remoteAlbumEntity.id]);
|
||||||
|
|
||||||
@@ -108,6 +117,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
.readTable(_db.remoteAlbumEntity)
|
.readTable(_db.remoteAlbumEntity)
|
||||||
.toDto(
|
.toDto(
|
||||||
assetCount: row.read(assetCount) ?? 0,
|
assetCount: row.read(assetCount) ?? 0,
|
||||||
|
ownerId: row.read(_db.userEntity.id)!,
|
||||||
ownerName: row.read(_db.userEntity.name)!,
|
ownerName: row.read(_db.userEntity.name)!,
|
||||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
||||||
),
|
),
|
||||||
@@ -116,12 +126,29 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<RemoteAlbum?> getByName(String albumName, String ownerId) {
|
Future<RemoteAlbum?> getByName(String albumName, String ownerId) {
|
||||||
final query = _db.remoteAlbumEntity.select()
|
final query = _db.remoteAlbumEntity.select().join([
|
||||||
..where((row) => row.name.equals(albumName) & row.ownerId.equals(ownerId))
|
innerJoin(
|
||||||
..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
|
_db.remoteAlbumUserEntity,
|
||||||
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
|
||||||
|
_db.remoteAlbumUserEntity.userId.equals(ownerId) &
|
||||||
|
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
query
|
||||||
|
..addColumns([_db.remoteAlbumUserEntity.userId])
|
||||||
|
..where(_db.remoteAlbumEntity.name.equals(albumName))
|
||||||
|
..orderBy([OrderingTerm.desc(_db.remoteAlbumEntity.createdAt)])
|
||||||
..limit(1);
|
..limit(1);
|
||||||
|
|
||||||
return query.map((row) => row.toDto(ownerName: '', isShared: false)).getSingleOrNull();
|
return query
|
||||||
|
.map(
|
||||||
|
(row) => row
|
||||||
|
.readTable(_db.remoteAlbumEntity)
|
||||||
|
.toDto(ownerId: row.read(_db.remoteAlbumUserEntity.userId)!, ownerName: '', isShared: false),
|
||||||
|
)
|
||||||
|
.getSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> create(RemoteAlbum album, List<String> assetIds) async {
|
Future<void> create(RemoteAlbum album, List<String> assetIds) async {
|
||||||
@@ -129,7 +156,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
final entity = RemoteAlbumEntityCompanion(
|
final entity = RemoteAlbumEntityCompanion(
|
||||||
id: Value(album.id),
|
id: Value(album.id),
|
||||||
name: Value(album.name),
|
name: Value(album.name),
|
||||||
ownerId: Value(album.ownerId),
|
|
||||||
createdAt: Value(album.createdAt),
|
createdAt: Value(album.createdAt),
|
||||||
updatedAt: Value(album.updatedAt),
|
updatedAt: Value(album.updatedAt),
|
||||||
description: Value(album.description),
|
description: Value(album.description),
|
||||||
@@ -140,6 +166,14 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
|
|
||||||
await _db.remoteAlbumEntity.insertOne(entity);
|
await _db.remoteAlbumEntity.insertOne(entity);
|
||||||
|
|
||||||
|
await _db.remoteAlbumUserEntity.insertOne(
|
||||||
|
RemoteAlbumUserEntityCompanion(
|
||||||
|
albumId: Value(album.id),
|
||||||
|
userId: Value(album.ownerId),
|
||||||
|
role: const Value(AlbumUserRole.owner),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (assetIds.isNotEmpty) {
|
if (assetIds.isNotEmpty) {
|
||||||
final albumAssets = assetIds.map(
|
final albumAssets = assetIds.map(
|
||||||
(assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(album.id), assetId: Value(assetId)),
|
(assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(album.id), assetId: Value(assetId)),
|
||||||
@@ -157,7 +191,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
RemoteAlbumEntityCompanion(
|
RemoteAlbumEntityCompanion(
|
||||||
id: Value(album.id),
|
id: Value(album.id),
|
||||||
name: Value(album.name),
|
name: Value(album.name),
|
||||||
ownerId: Value(album.ownerId),
|
|
||||||
createdAt: Value(album.createdAt),
|
createdAt: Value(album.createdAt),
|
||||||
updatedAt: Value(album.updatedAt),
|
updatedAt: Value(album.updatedAt),
|
||||||
description: Value(album.description),
|
description: Value(album.description),
|
||||||
@@ -197,7 +230,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
Future<List<UserDto>> getSharedUsers(String albumId) async {
|
Future<List<UserDto>> getSharedUsers(String albumId) async {
|
||||||
final albumUserRows = await (_db.select(
|
final albumUserRows = await (_db.select(
|
||||||
_db.remoteAlbumUserEntity,
|
_db.remoteAlbumUserEntity,
|
||||||
)..where((row) => row.albumId.equals(albumId))).get();
|
)..where((row) => row.albumId.equals(albumId) & row.role.isNotValue(AlbumUserRole.owner.index))).get();
|
||||||
|
|
||||||
if (albumUserRows.isEmpty) {
|
if (albumUserRows.isEmpty) {
|
||||||
return [];
|
return [];
|
||||||
@@ -295,19 +328,21 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
leftOuterJoin(
|
|
||||||
_db.userEntity,
|
|
||||||
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
|
|
||||||
useColumns: false,
|
|
||||||
),
|
|
||||||
leftOuterJoin(
|
leftOuterJoin(
|
||||||
_db.remoteAlbumUserEntity,
|
_db.remoteAlbumUserEntity,
|
||||||
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
|
innerJoin(
|
||||||
|
_db.userEntity,
|
||||||
|
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) &
|
||||||
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
|
||||||
|
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(_db.remoteAlbumEntity.id.equals(albumId))
|
..where(_db.remoteAlbumEntity.id.equals(albumId))
|
||||||
..addColumns([_db.userEntity.name])
|
..addColumns([_db.userEntity.name, _db.userEntity.id])
|
||||||
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
||||||
..groupBy([_db.remoteAlbumEntity.id]);
|
..groupBy([_db.remoteAlbumEntity.id]);
|
||||||
|
|
||||||
@@ -315,6 +350,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
final album = row
|
final album = row
|
||||||
.readTable(_db.remoteAlbumEntity)
|
.readTable(_db.remoteAlbumEntity)
|
||||||
.toDto(
|
.toDto(
|
||||||
|
ownerId: row.read(_db.userEntity.id)!,
|
||||||
ownerName: row.read(_db.userEntity.name)!,
|
ownerName: row.read(_db.userEntity.name)!,
|
||||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
||||||
);
|
);
|
||||||
@@ -355,6 +391,37 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
return _db.managers.remoteAlbumEntity.count();
|
return _db.managers.remoteAlbumEntity.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<UserDto> getOwner(String albumId) {
|
||||||
|
final query =
|
||||||
|
_db.userEntity.select().join([
|
||||||
|
innerJoin(
|
||||||
|
_db.remoteAlbumUserEntity,
|
||||||
|
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
|
])..where(
|
||||||
|
_db.remoteAlbumUserEntity.albumId.equals(albumId) &
|
||||||
|
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
|
||||||
|
);
|
||||||
|
|
||||||
|
return query
|
||||||
|
.map(
|
||||||
|
(row) => UserDto(
|
||||||
|
id: row.read(_db.userEntity.id)!,
|
||||||
|
email: row.read(_db.userEntity.email)!,
|
||||||
|
name: row.read(_db.userEntity.name)!,
|
||||||
|
memoryEnabled: true,
|
||||||
|
inTimeline: false,
|
||||||
|
isPartnerSharedBy: false,
|
||||||
|
isPartnerSharedWith: false,
|
||||||
|
profileChangedAt: row.read(_db.userEntity.profileChangedAt)!,
|
||||||
|
hasProfileImage: row.read(_db.userEntity.hasProfileImage)!,
|
||||||
|
avatarColor: AvatarColor.values[row.read(_db.userEntity.avatarColor)!],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.getSingle();
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<String>> getLinkedAssetIds(String userId, String localAlbumId, String remoteAlbumId) async {
|
Future<List<String>> getLinkedAssetIds(String userId, String localAlbumId, String remoteAlbumId) async {
|
||||||
// Find remote asset ids that:
|
// Find remote asset ids that:
|
||||||
// 1. Belong to the provided local album (via local_album_asset_entity)
|
// 1. Belong to the provided local album (via local_album_asset_entity)
|
||||||
@@ -416,21 +483,23 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
leftOuterJoin(
|
|
||||||
_db.userEntity,
|
|
||||||
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
|
|
||||||
useColumns: false,
|
|
||||||
),
|
|
||||||
leftOuterJoin(
|
leftOuterJoin(
|
||||||
_db.remoteAlbumUserEntity,
|
_db.remoteAlbumUserEntity,
|
||||||
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
|
innerJoin(
|
||||||
|
_db.userEntity,
|
||||||
|
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) &
|
||||||
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
|
||||||
|
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(_db.remoteAlbumEntity.id.isIn(albumIds) & _db.remoteAssetEntity.deletedAt.isNull())
|
..where(_db.remoteAlbumEntity.id.isIn(albumIds) & _db.remoteAssetEntity.deletedAt.isNull())
|
||||||
..addColumns([assetCount])
|
..addColumns([assetCount])
|
||||||
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
||||||
..addColumns([_db.userEntity.name])
|
..addColumns([_db.userEntity.name, _db.userEntity.id])
|
||||||
..groupBy([_db.remoteAlbumEntity.id]);
|
..groupBy([_db.remoteAlbumEntity.id]);
|
||||||
|
|
||||||
return query
|
return query
|
||||||
@@ -438,6 +507,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
(row) => row
|
(row) => row
|
||||||
.readTable(_db.remoteAlbumEntity)
|
.readTable(_db.remoteAlbumEntity)
|
||||||
.toDto(
|
.toDto(
|
||||||
|
ownerId: row.read(_db.userEntity.id)!,
|
||||||
ownerName: row.read(_db.userEntity.name) ?? '',
|
ownerName: row.read(_db.userEntity.name) ?? '',
|
||||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
||||||
assetCount: row.read(assetCount) ?? 0,
|
assetCount: row.read(assetCount) ?? 0,
|
||||||
@@ -448,7 +518,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension on RemoteAlbumEntityData {
|
extension on RemoteAlbumEntityData {
|
||||||
RemoteAlbum toDto({int assetCount = 0, required String ownerName, required bool isShared}) {
|
RemoteAlbum toDto({int assetCount = 0, required String ownerName, required String ownerId, required bool isShared}) {
|
||||||
return RemoteAlbum(
|
return RemoteAlbum(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
|
|||||||
@@ -46,16 +46,25 @@ class SyncApiRepository {
|
|||||||
types: [
|
types: [
|
||||||
SyncRequestType.authUsersV1,
|
SyncRequestType.authUsersV1,
|
||||||
SyncRequestType.usersV1,
|
SyncRequestType.usersV1,
|
||||||
SyncRequestType.assetsV1,
|
serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)
|
||||||
|
? SyncRequestType.assetsV2
|
||||||
|
: SyncRequestType.assetsV1,
|
||||||
SyncRequestType.assetExifsV1,
|
SyncRequestType.assetExifsV1,
|
||||||
if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetEditsV1,
|
if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetEditsV1,
|
||||||
SyncRequestType.assetMetadataV1,
|
SyncRequestType.assetMetadataV1,
|
||||||
SyncRequestType.partnersV1,
|
SyncRequestType.partnersV1,
|
||||||
SyncRequestType.partnerAssetsV1,
|
serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)
|
||||||
|
? SyncRequestType.partnerAssetsV2
|
||||||
|
: SyncRequestType.partnerAssetsV1,
|
||||||
SyncRequestType.partnerAssetExifsV1,
|
SyncRequestType.partnerAssetExifsV1,
|
||||||
SyncRequestType.albumsV1,
|
if (serverVersion < const SemVer(major: 3, minor: 0, patch: 0))
|
||||||
|
SyncRequestType.albumsV1
|
||||||
|
else
|
||||||
|
SyncRequestType.albumsV2,
|
||||||
SyncRequestType.albumUsersV1,
|
SyncRequestType.albumUsersV1,
|
||||||
SyncRequestType.albumAssetsV1,
|
serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)
|
||||||
|
? SyncRequestType.albumAssetsV2
|
||||||
|
: SyncRequestType.albumAssetsV1,
|
||||||
SyncRequestType.albumAssetExifsV1,
|
SyncRequestType.albumAssetExifsV1,
|
||||||
SyncRequestType.albumToAssetsV1,
|
SyncRequestType.albumToAssetsV1,
|
||||||
SyncRequestType.memoriesV1,
|
SyncRequestType.memoriesV1,
|
||||||
@@ -64,8 +73,9 @@ class SyncApiRepository {
|
|||||||
SyncRequestType.partnerStacksV1,
|
SyncRequestType.partnerStacksV1,
|
||||||
SyncRequestType.userMetadataV1,
|
SyncRequestType.userMetadataV1,
|
||||||
SyncRequestType.peopleV1,
|
SyncRequestType.peopleV1,
|
||||||
if (serverVersion < const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV1,
|
serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)
|
||||||
if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV2,
|
? SyncRequestType.assetFacesV2
|
||||||
|
: SyncRequestType.assetFacesV1,
|
||||||
],
|
],
|
||||||
reset: shouldReset,
|
reset: shouldReset,
|
||||||
).toJson(),
|
).toJson(),
|
||||||
@@ -150,6 +160,7 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
|||||||
SyncEntityType.partnerV1: SyncPartnerV1.fromJson,
|
SyncEntityType.partnerV1: SyncPartnerV1.fromJson,
|
||||||
SyncEntityType.partnerDeleteV1: SyncPartnerDeleteV1.fromJson,
|
SyncEntityType.partnerDeleteV1: SyncPartnerDeleteV1.fromJson,
|
||||||
SyncEntityType.assetV1: SyncAssetV1.fromJson,
|
SyncEntityType.assetV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.assetV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.assetDeleteV1: SyncAssetDeleteV1.fromJson,
|
SyncEntityType.assetDeleteV1: SyncAssetDeleteV1.fromJson,
|
||||||
SyncEntityType.assetExifV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.assetExifV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.assetEditV1: SyncAssetEditV1.fromJson,
|
SyncEntityType.assetEditV1: SyncAssetEditV1.fromJson,
|
||||||
@@ -157,18 +168,24 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
|||||||
SyncEntityType.assetMetadataV1: SyncAssetMetadataV1.fromJson,
|
SyncEntityType.assetMetadataV1: SyncAssetMetadataV1.fromJson,
|
||||||
SyncEntityType.assetMetadataDeleteV1: SyncAssetMetadataDeleteV1.fromJson,
|
SyncEntityType.assetMetadataDeleteV1: SyncAssetMetadataDeleteV1.fromJson,
|
||||||
SyncEntityType.partnerAssetV1: SyncAssetV1.fromJson,
|
SyncEntityType.partnerAssetV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.partnerAssetV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.partnerAssetBackfillV1: SyncAssetV1.fromJson,
|
SyncEntityType.partnerAssetBackfillV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.partnerAssetBackfillV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.partnerAssetDeleteV1: SyncAssetDeleteV1.fromJson,
|
SyncEntityType.partnerAssetDeleteV1: SyncAssetDeleteV1.fromJson,
|
||||||
SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.albumV1: SyncAlbumV1.fromJson,
|
SyncEntityType.albumV1: SyncAlbumV1.fromJson,
|
||||||
|
SyncEntityType.albumV2: SyncAlbumV2.fromJson,
|
||||||
SyncEntityType.albumDeleteV1: SyncAlbumDeleteV1.fromJson,
|
SyncEntityType.albumDeleteV1: SyncAlbumDeleteV1.fromJson,
|
||||||
SyncEntityType.albumUserV1: SyncAlbumUserV1.fromJson,
|
SyncEntityType.albumUserV1: SyncAlbumUserV1.fromJson,
|
||||||
SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson,
|
SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson,
|
||||||
SyncEntityType.albumUserDeleteV1: SyncAlbumUserDeleteV1.fromJson,
|
SyncEntityType.albumUserDeleteV1: SyncAlbumUserDeleteV1.fromJson,
|
||||||
SyncEntityType.albumAssetCreateV1: SyncAssetV1.fromJson,
|
SyncEntityType.albumAssetCreateV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.albumAssetCreateV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.albumAssetUpdateV1: SyncAssetV1.fromJson,
|
SyncEntityType.albumAssetUpdateV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.albumAssetUpdateV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.albumAssetBackfillV1: SyncAssetV1.fromJson,
|
SyncEntityType.albumAssetBackfillV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.albumAssetBackfillV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.albumAssetExifCreateV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.albumAssetExifCreateV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.albumAssetExifUpdateV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.albumAssetExifUpdateV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.albumAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.albumAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart' as api show AssetVisibility, AlbumUserRole, UserMetadataKey, AssetEditAction;
|
import 'package:openapi/api.dart' as api show AssetVisibility, AlbumUserRole, UserMetadataKey, AssetEditAction;
|
||||||
import 'package:openapi/api.dart' hide AlbumUserRole, UserMetadataKey, AssetEditAction, AssetVisibility;
|
import 'package:openapi/api.dart' hide UserMetadataKey, AssetEditAction, AssetVisibility, AlbumUserRole;
|
||||||
|
|
||||||
class SyncStreamRepository extends DriftDatabaseRepository {
|
class SyncStreamRepository extends DriftDatabaseRepository {
|
||||||
final Logger _logger = Logger('DriftSyncStreamRepository');
|
final Logger _logger = Logger('DriftSyncStreamRepository');
|
||||||
@@ -220,6 +220,44 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateAssetsV2(Iterable<SyncAssetV2> data, {String debugLabel = 'user'}) async {
|
||||||
|
try {
|
||||||
|
await _db.batch((batch) {
|
||||||
|
for (final asset in data) {
|
||||||
|
final companion = RemoteAssetEntityCompanion(
|
||||||
|
name: Value(asset.originalFileName),
|
||||||
|
type: Value(asset.type.toAssetType()),
|
||||||
|
createdAt: Value.absentIfNull(asset.fileCreatedAt),
|
||||||
|
updatedAt: Value.absentIfNull(asset.fileModifiedAt),
|
||||||
|
durationMs: Value(asset.duration),
|
||||||
|
checksum: Value(asset.checksum),
|
||||||
|
isFavorite: Value(asset.isFavorite),
|
||||||
|
ownerId: Value(asset.ownerId),
|
||||||
|
localDateTime: Value(asset.localDateTime),
|
||||||
|
thumbHash: Value(asset.thumbhash),
|
||||||
|
deletedAt: Value(asset.deletedAt),
|
||||||
|
visibility: Value(asset.visibility.toAssetVisibility()),
|
||||||
|
livePhotoVideoId: Value(asset.livePhotoVideoId),
|
||||||
|
stackId: Value(asset.stackId),
|
||||||
|
libraryId: Value(asset.libraryId),
|
||||||
|
width: Value(asset.width),
|
||||||
|
height: Value(asset.height),
|
||||||
|
isEdited: Value(asset.isEdited),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.insert(
|
||||||
|
_db.remoteAssetEntity,
|
||||||
|
companion.copyWith(id: Value(asset.id)),
|
||||||
|
onConflict: DoUpdate((_) => companion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Error: updateAssetsV2 - $debugLabel', error, stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data, {String debugLabel = 'user'}) async {
|
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data, {String debugLabel = 'user'}) async {
|
||||||
try {
|
try {
|
||||||
await _db.batch((batch) {
|
await _db.batch((batch) {
|
||||||
@@ -397,6 +435,47 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateAlbumsV1(Iterable<SyncAlbumV1> data) async {
|
Future<void> updateAlbumsV1(Iterable<SyncAlbumV1> data) async {
|
||||||
|
try {
|
||||||
|
await _db.transaction(() async {
|
||||||
|
await _db.batch((batch) {
|
||||||
|
for (final album in data) {
|
||||||
|
final companion = RemoteAlbumEntityCompanion(
|
||||||
|
name: Value(album.name),
|
||||||
|
description: Value(album.description),
|
||||||
|
isActivityEnabled: Value(album.isActivityEnabled),
|
||||||
|
order: Value(album.order.toAlbumAssetOrder()),
|
||||||
|
thumbnailAssetId: Value(album.thumbnailAssetId),
|
||||||
|
createdAt: Value(album.createdAt),
|
||||||
|
updatedAt: Value(album.updatedAt),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.insert(
|
||||||
|
_db.remoteAlbumEntity,
|
||||||
|
companion.copyWith(id: Value(album.id)),
|
||||||
|
onConflict: DoUpdate((_) => companion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await _db.batch((batch) {
|
||||||
|
for (final album in data) {
|
||||||
|
final companion = RemoteAlbumUserEntityCompanion(
|
||||||
|
albumId: Value(album.id),
|
||||||
|
userId: Value(album.ownerId),
|
||||||
|
role: const Value(AlbumUserRole.owner),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.insert(_db.remoteAlbumUserEntity, companion, onConflict: DoUpdate((_) => companion));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Error: updateAlbumsV1', error, stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateAlbumsV2(Iterable<SyncAlbumV2> data) async {
|
||||||
try {
|
try {
|
||||||
await _db.batch((batch) {
|
await _db.batch((batch) {
|
||||||
for (final album in data) {
|
for (final album in data) {
|
||||||
@@ -406,7 +485,6 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
isActivityEnabled: Value(album.isActivityEnabled),
|
isActivityEnabled: Value(album.isActivityEnabled),
|
||||||
order: Value(album.order.toAlbumAssetOrder()),
|
order: Value(album.order.toAlbumAssetOrder()),
|
||||||
thumbnailAssetId: Value(album.thumbnailAssetId),
|
thumbnailAssetId: Value(album.thumbnailAssetId),
|
||||||
ownerId: Value(album.ownerId),
|
|
||||||
createdAt: Value(album.createdAt),
|
createdAt: Value(album.createdAt),
|
||||||
updatedAt: Value(album.updatedAt),
|
updatedAt: Value(album.updatedAt),
|
||||||
);
|
);
|
||||||
@@ -419,7 +497,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
_logger.severe('Error: updateAlbumsV1', error, stack);
|
_logger.severe('Error: updateAlbumsV2', error, stack);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -821,6 +899,7 @@ extension on api.AlbumUserRole {
|
|||||||
AlbumUserRole toAlbumUserRole() => switch (this) {
|
AlbumUserRole toAlbumUserRole() => switch (this) {
|
||||||
api.AlbumUserRole.editor => AlbumUserRole.editor,
|
api.AlbumUserRole.editor => AlbumUserRole.editor,
|
||||||
api.AlbumUserRole.viewer => AlbumUserRole.viewer,
|
api.AlbumUserRole.viewer => AlbumUserRole.viewer,
|
||||||
|
api.AlbumUserRole.owner => AlbumUserRole.owner,
|
||||||
_ => throw Exception('Unknown AlbumUserRole value: $this'),
|
_ => throw Exception('Unknown AlbumUserRole value: $this'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ void main() async {
|
|||||||
await initApp();
|
await initApp();
|
||||||
// Warm-up isolate pool for worker manager
|
// Warm-up isolate pool for worker manager
|
||||||
await workerManagerPatch.init(dynamicSpawning: true, isolatesCount: max(Platform.numberOfProcessors - 1, 5));
|
await workerManagerPatch.init(dynamicSpawning: true, isolatesCount: max(Platform.numberOfProcessors - 1, 5));
|
||||||
await migrateDatabaseIfNeeded();
|
await migrateDatabaseIfNeeded(drift);
|
||||||
|
|
||||||
runApp(ProviderScope(overrides: [driftProvider.overrideWith(driftOverride(drift))], child: const MainWidget()));
|
runApp(ProviderScope(overrides: [driftProvider.overrideWith(driftOverride(drift))], child: const MainWidget()));
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
|
|||||||
@@ -26,6 +26,14 @@ class AssetDetails extends ConsumerWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: context.colorScheme.surface,
|
color: context.colorScheme.surface,
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.2),
|
||||||
|
offset: const Offset(0, -3),
|
||||||
|
blurRadius: 10,
|
||||||
|
spreadRadius: 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
|
|||||||
+10
-1
@@ -22,6 +22,7 @@ class TechnicalDetails extends ConsumerWidget {
|
|||||||
final exifInfo = this.exifInfo;
|
final exifInfo = this.exifInfo;
|
||||||
final cameraTitle = _getCameraInfoTitle(exifInfo);
|
final cameraTitle = _getCameraInfoTitle(exifInfo);
|
||||||
final lensTitle = exifInfo?.lens != null && exifInfo!.lens!.isNotEmpty ? exifInfo.lens : null;
|
final lensTitle = exifInfo?.lens != null && exifInfo!.lens!.isNotEmpty ? exifInfo.lens : null;
|
||||||
|
final lensSubtitle = _getLensInfoSubtitle(exifInfo);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -46,9 +47,16 @@ class TechnicalDetails extends ConsumerWidget {
|
|||||||
title: lensTitle,
|
title: lensTitle,
|
||||||
titleStyle: context.textTheme.labelLarge,
|
titleStyle: context.textTheme.labelLarge,
|
||||||
leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color),
|
leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color),
|
||||||
subtitle: _getLensInfoSubtitle(exifInfo),
|
subtitle: lensSubtitle,
|
||||||
subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||||
),
|
),
|
||||||
|
] else if (lensSubtitle != null) ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SheetTile(
|
||||||
|
title: lensSubtitle,
|
||||||
|
titleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||||
|
leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -123,6 +131,7 @@ class TechnicalDetails extends ConsumerWidget {
|
|||||||
if (exifInfo == null) return null;
|
if (exifInfo == null) return null;
|
||||||
final fNumber = exifInfo.fNumber.isNotEmpty ? 'ƒ/${exifInfo.fNumber}' : null;
|
final fNumber = exifInfo.fNumber.isNotEmpty ? 'ƒ/${exifInfo.fNumber}' : null;
|
||||||
final focalLength = exifInfo.focalLength.isNotEmpty ? '${exifInfo.focalLength} mm' : null;
|
final focalLength = exifInfo.focalLength.isNotEmpty ? '${exifInfo.focalLength} mm' : null;
|
||||||
|
if (fNumber == null && focalLength == null) return null;
|
||||||
return [fNumber, focalLength].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator);
|
return [fNumber, focalLength].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/events.model.dart';
|
import 'package:immich_mobile/domain/models/events.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/scroll_extensions.dart';
|
import 'package:immich_mobile/extensions/scroll_extensions.dart';
|
||||||
@@ -363,7 +364,8 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BaseAsset displayAsset = asset;
|
BaseAsset displayAsset = asset;
|
||||||
final stackChildren = ref.watch(stackChildrenNotifier(asset)).valueOrNull;
|
final showAssetStack = ref.watch(timelineServiceProvider.select((s) => s.origin != TimelineOrigin.trash));
|
||||||
|
final stackChildren = showAssetStack ? ref.watch(stackChildrenNotifier(asset)).valueOrNull : null;
|
||||||
if (stackChildren != null && stackChildren.isNotEmpty) {
|
if (stackChildren != null && stackChildren.isNotEmpty) {
|
||||||
displayAsset = stackChildren.elementAt(stackIndex);
|
displayAsset = stackChildren.elementAt(stackIndex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
|
|
||||||
class AssetStackRow extends ConsumerWidget {
|
class AssetStackRow extends ConsumerWidget {
|
||||||
final List<RemoteAsset> stack;
|
final List<RemoteAsset> stack;
|
||||||
@@ -15,6 +17,11 @@ class AssetStackRow extends ConsumerWidget {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final hideAssetStack = ref.read(timelineServiceProvider).origin == TimelineOrigin.trash;
|
||||||
|
if (hideAssetStack) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
final showingControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
final showingControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||||
double opacity = ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0);
|
double opacity = ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0);
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,21 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
|
||||||
class MapBottomSheet extends StatelessWidget {
|
class MapBottomSheet extends StatelessWidget {
|
||||||
const MapBottomSheet({super.key});
|
final Key? sheetKey;
|
||||||
|
|
||||||
|
const MapBottomSheet({super.key, this.sheetKey});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BaseBottomSheet(
|
return BaseBottomSheet(
|
||||||
|
key: sheetKey,
|
||||||
initialChildSize: 0.25,
|
initialChildSize: 0.25,
|
||||||
maxChildSize: 0.75,
|
maxChildSize: 0.75,
|
||||||
shouldCloseOnMinExtent: false,
|
shouldCloseOnMinExtent: false,
|
||||||
@@ -49,7 +53,7 @@ class _ScopedMapTimeline extends StatelessWidget {
|
|||||||
return timelineService;
|
return timelineService;
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
child: const Timeline(appBar: null, bottomSheet: null, withScrubber: false),
|
child: const Timeline(appBar: null, bottomSheet: GeneralBottomSheet(minChildSize: 0.23), withScrubber: false),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class ThumbnailTile extends ConsumerStatefulWidget {
|
|||||||
this.showStorageIndicator = false,
|
this.showStorageIndicator = false,
|
||||||
this.lockSelection = false,
|
this.lockSelection = false,
|
||||||
this.heroOffset,
|
this.heroOffset,
|
||||||
|
this.showStackIndicator = false,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ class ThumbnailTile extends ConsumerStatefulWidget {
|
|||||||
final bool showStorageIndicator;
|
final bool showStorageIndicator;
|
||||||
final bool lockSelection;
|
final bool lockSelection;
|
||||||
final int? heroOffset;
|
final int? heroOffset;
|
||||||
|
final bool showStackIndicator;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<ThumbnailTile> createState() => _ThumbnailTileState();
|
ConsumerState<ThumbnailTile> createState() => _ThumbnailTileState();
|
||||||
@@ -139,7 +141,14 @@ class _ThumbnailTileState extends ConsumerState<ThumbnailTile> {
|
|||||||
duration: Durations.short4,
|
duration: Durations.short4,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.topRight,
|
alignment: Alignment.topRight,
|
||||||
child: _AssetTypeIcons(asset: asset),
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
_AssetTypeIcons(asset: asset),
|
||||||
|
if (widget.showStackIndicator) _StackIndicator(asset: asset),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (storageIndicator && asset != null)
|
if (storageIndicator && asset != null)
|
||||||
@@ -286,8 +295,8 @@ class _AssetTypeIcons extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final hasStack = asset is RemoteAsset && (asset as RemoteAsset).stackId != null;
|
final remoteAsset = asset is RemoteAsset ? asset as RemoteAsset : null;
|
||||||
final isLivePhoto = asset is RemoteAsset && asset.livePhotoVideoId != null;
|
final isLivePhoto = remoteAsset?.livePhotoVideoId != null;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -295,11 +304,6 @@ class _AssetTypeIcons extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
if (asset.isVideo)
|
if (asset.isVideo)
|
||||||
Padding(padding: const EdgeInsets.only(right: 10.0, top: 6.0), child: _VideoIndicator(asset.duration)),
|
Padding(padding: const EdgeInsets.only(right: 10.0, top: 6.0), child: _VideoIndicator(asset.duration)),
|
||||||
if (hasStack)
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.only(right: 10.0, top: 6.0),
|
|
||||||
child: _TileOverlayIcon(Icons.burst_mode_rounded),
|
|
||||||
),
|
|
||||||
if (isLivePhoto)
|
if (isLivePhoto)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.only(right: 10.0, top: 6.0),
|
padding: EdgeInsets.only(right: 10.0, top: 6.0),
|
||||||
@@ -312,6 +316,24 @@ class _AssetTypeIcons extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _StackIndicator extends StatelessWidget {
|
||||||
|
final BaseAsset asset;
|
||||||
|
|
||||||
|
const _StackIndicator({required this.asset});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (asset is! RemoteAsset || (asset as RemoteAsset).stackId == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return const Padding(
|
||||||
|
padding: EdgeInsets.only(right: 10.0, top: 6.0),
|
||||||
|
child: _TileOverlayIcon(Icons.burst_mode_rounded),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _UploadProgressOverlay extends StatelessWidget {
|
class _UploadProgressOverlay extends StatelessWidget {
|
||||||
final double progress;
|
final double progress;
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'package:immich_mobile/domain/utils/event_stream.dart';
|
|||||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/map/map_utils.dart';
|
import 'package:immich_mobile/presentation/widgets/map/map_utils.dart';
|
||||||
@@ -53,6 +54,7 @@ class _DriftMapState extends ConsumerState<DriftMap> {
|
|||||||
final _reloadMutex = AsyncMutex();
|
final _reloadMutex = AsyncMutex();
|
||||||
final _debouncer = Debouncer(interval: const Duration(milliseconds: 500), maxWaitTime: const Duration(seconds: 2));
|
final _debouncer = Debouncer(interval: const Duration(milliseconds: 500), maxWaitTime: const Duration(seconds: 2));
|
||||||
final ValueNotifier<double> bottomSheetOffset = ValueNotifier(0.25);
|
final ValueNotifier<double> bottomSheetOffset = ValueNotifier(0.25);
|
||||||
|
final GlobalKey _bottomSheetKey = GlobalKey();
|
||||||
StreamSubscription? _eventSubscription;
|
StreamSubscription? _eventSubscription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -184,7 +186,7 @@ class _DriftMapState extends ConsumerState<DriftMap> {
|
|||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
_Map(initialLocation: widget.initialLocation, onMapCreated: onMapCreated, onMapReady: onMapReady),
|
_Map(initialLocation: widget.initialLocation, onMapCreated: onMapCreated, onMapReady: onMapReady),
|
||||||
_DynamicBottomSheet(bottomSheetOffset: bottomSheetOffset),
|
_DynamicBottomSheet(bottomSheetOffset: bottomSheetOffset, sheetKey: _bottomSheetKey),
|
||||||
_DynamicMyLocationButton(onZoomToLocation: onZoomToLocation, bottomSheetOffset: bottomSheetOffset),
|
_DynamicMyLocationButton(onZoomToLocation: onZoomToLocation, bottomSheetOffset: bottomSheetOffset),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -224,8 +226,9 @@ class _Map extends StatelessWidget {
|
|||||||
|
|
||||||
class _DynamicBottomSheet extends StatefulWidget {
|
class _DynamicBottomSheet extends StatefulWidget {
|
||||||
final ValueNotifier<double> bottomSheetOffset;
|
final ValueNotifier<double> bottomSheetOffset;
|
||||||
|
final GlobalKey sheetKey;
|
||||||
|
|
||||||
const _DynamicBottomSheet({required this.bottomSheetOffset});
|
const _DynamicBottomSheet({required this.bottomSheetOffset, required this.sheetKey});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_DynamicBottomSheet> createState() => _DynamicBottomSheetState();
|
State<_DynamicBottomSheet> createState() => _DynamicBottomSheetState();
|
||||||
@@ -236,10 +239,13 @@ class _DynamicBottomSheetState extends State<_DynamicBottomSheet> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return NotificationListener<DraggableScrollableNotification>(
|
return NotificationListener<DraggableScrollableNotification>(
|
||||||
onNotification: (notification) {
|
onNotification: (notification) {
|
||||||
widget.bottomSheetOffset.value = notification.extent;
|
final sheet = notification.context.findAncestorWidgetOfExactType<BaseBottomSheet>();
|
||||||
return true;
|
if (sheet?.key == widget.sheetKey) {
|
||||||
|
widget.bottomSheetOffset.value = notification.extent;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
child: const MapBottomSheet(),
|
child: MapBottomSheet(sheetKey: widget.sheetKey),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,7 @@ class DriftMemoryBottomInfo extends StatelessWidget {
|
|||||||
child: MaterialButton(
|
child: MaterialButton(
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await context.maybePop();
|
await context.router.navigate(const TabShellRoute(children: [MainTimelineRoute()]));
|
||||||
await context.navigateTo(const TabShellRoute(children: [MainTimelineRoute()]));
|
|
||||||
EventStream.shared.emit(ScrollToDateEvent(fileCreatedDate));
|
EventStream.shared.emit(ScrollToDateEvent(fileCreatedDate));
|
||||||
},
|
},
|
||||||
shape: const CircleBorder(),
|
shape: const CircleBorder(),
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ class _AssetTileWidget extends ConsumerWidget {
|
|||||||
final lockSelection = _getLockSelectionStatus(ref);
|
final lockSelection = _getLockSelectionStatus(ref);
|
||||||
final showStorageIndicator = ref.watch(timelineArgsProvider.select((args) => args.showStorageIndicator));
|
final showStorageIndicator = ref.watch(timelineArgsProvider.select((args) => args.showStorageIndicator));
|
||||||
final isReadonlyModeEnabled = ref.watch(readonlyModeProvider);
|
final isReadonlyModeEnabled = ref.watch(readonlyModeProvider);
|
||||||
|
final showStackIndicator = ref.read(timelineServiceProvider).origin != TimelineOrigin.trash;
|
||||||
|
|
||||||
return RepaintBoundary(
|
return RepaintBoundary(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
@@ -253,6 +254,7 @@ class _AssetTileWidget extends ConsumerWidget {
|
|||||||
asset,
|
asset,
|
||||||
lockSelection: lockSelection,
|
lockSelection: lockSelection,
|
||||||
showStorageIndicator: showStorageIndicator,
|
showStorageIndicator: showStorageIndicator,
|
||||||
|
showStackIndicator: showStackIndicator,
|
||||||
heroOffset: heroOffset,
|
heroOffset: heroOffset,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -469,6 +469,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
|||||||
ref.read(timelineStateProvider.notifier).setScrolling(true);
|
ref.read(timelineStateProvider.notifier).setScrolling(true);
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
timeline,
|
timeline,
|
||||||
if (isBottomWidgetVisible)
|
if (isBottomWidgetVisible)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user