mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
Merge remote-tracking branch 'origin/main' into feat/yucca-integration
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
|||||||
24.14.1
|
24.15.0
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -103,7 +103,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Restore Gradle Cache
|
- name: Restore Gradle Cache
|
||||||
id: cache-gradle-restore
|
id: cache-gradle-restore
|
||||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -160,7 +160,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Save Gradle Cache
|
- name: Save Gradle Cache
|
||||||
id: cache-gradle-save
|
id: cache-gradle-save
|
||||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
@@ -210,7 +210,7 @@ jobs:
|
|||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@e65c17d16e57e481586a6a5a0282698790062f92 # v1.300.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@e6faebce24cf20ac38653d0d2c7f4aa80aaafc79 # v0.0.38
|
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
|
||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
@@ -83,6 +83,6 @@ jobs:
|
|||||||
# ./location_of_script_within_repo/buildscript.sh
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||||
with:
|
with:
|
||||||
category: '/language:${{matrix.language}}'
|
category: '/language:${{matrix.language}}'
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -211,7 +213,7 @@ jobs:
|
|||||||
run: 'mise run //deployment:tf apply'
|
run: 'mise run //deployment:tf apply'
|
||||||
|
|
||||||
- name: Comment
|
- name: Comment
|
||||||
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
|
uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0
|
||||||
if: ${{ steps.parameters.outputs.event == 'pr' }}
|
if: ${{ steps.parameters.outputs.event == 'pr' }}
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -42,7 +44,7 @@ jobs:
|
|||||||
run: 'mise run //deployment:tf destroy -- -refresh=false'
|
run: 'mise run //deployment:tf destroy -- -refresh=false'
|
||||||
|
|
||||||
- name: Comment
|
- name: Comment
|
||||||
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
|
uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
number: ${{ github.event.number }}
|
number: ${{ github.event.number }}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -142,7 +142,7 @@ jobs:
|
|||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Create draft release
|
- name: Create draft release
|
||||||
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
|
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
tag_name: ${{ needs.bump_version.outputs.version }}
|
tag_name: ${{ needs.bump_version.outputs.version }}
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -110,9 +114,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
|
||||||
@@ -123,7 +127,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'
|
||||||
@@ -159,9 +163,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
|
||||||
@@ -172,7 +176,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'
|
||||||
@@ -203,9 +207,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
|
||||||
@@ -216,7 +220,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'
|
||||||
@@ -249,9 +253,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
|
||||||
@@ -262,7 +266,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'
|
||||||
@@ -289,9 +293,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
|
||||||
@@ -302,7 +306,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'
|
||||||
@@ -339,9 +343,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
|
||||||
@@ -352,7 +356,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'
|
||||||
@@ -387,9 +391,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
|
||||||
@@ -401,13 +405,17 @@ 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: Configure npm registry
|
- name: Configure npm registry
|
||||||
run: pnpm set registry https://npm.raccoon.sh/
|
run: pnpm set registry https://npm.raccoon.sh/
|
||||||
|
- 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
|
||||||
@@ -428,9 +436,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
|
||||||
@@ -442,7 +450,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'
|
||||||
@@ -502,9 +510,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
|
||||||
@@ -516,7 +524,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'
|
||||||
@@ -586,7 +594,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:
|
||||||
@@ -598,9 +606,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
|
||||||
@@ -625,39 +633,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
|
||||||
@@ -670,9 +679,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
|
||||||
@@ -683,7 +692,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'
|
||||||
@@ -702,9 +711,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
|
||||||
@@ -723,9 +732,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
|
||||||
@@ -736,7 +745,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'
|
||||||
@@ -787,9 +796,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
|
||||||
@@ -800,7 +809,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
-13
@@ -13,10 +13,6 @@
|
|||||||
"editor.wordBasedSuggestions": "off"
|
"editor.wordBasedSuggestions": "off"
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.organizeImports": "explicit",
|
|
||||||
"source.removeUnusedImports": "explicit"
|
|
||||||
},
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
@@ -29,18 +25,14 @@
|
|||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
"[svelte]": {
|
"[svelte]": {
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.organizeImports": "explicit",
|
|
||||||
"source.removeUnusedImports": "explicit"
|
|
||||||
},
|
|
||||||
"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.codeActionsOnSave": {
|
|
||||||
"source.organizeImports": "explicit",
|
|
||||||
"source.removeUnusedImports": "explicit"
|
|
||||||
},
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
24.14.1
|
24.15.0
|
||||||
|
|||||||
+1
-1
@@ -68,6 +68,6 @@
|
|||||||
"micromatch": "^4.0.8"
|
"micromatch": "^4.0.8"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "24.14.1"
|
"node": "24.15.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tools]
|
[tools]
|
||||||
terragrunt = "1.0.0"
|
terragrunt = "1.0.3"
|
||||||
opentofu = "1.11.5"
|
opentofu = "1.11.6"
|
||||||
|
|
||||||
[tasks."tg:fmt"]
|
[tasks."tg:fmt"]
|
||||||
run = "terragrunt hclfmt"
|
run = "terragrunt hclfmt"
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ services:
|
|||||||
container_name: immich_prometheus
|
container_name: immich_prometheus
|
||||||
ports:
|
ports:
|
||||||
- 9090:9090
|
- 9090:9090
|
||||||
image: prom/prometheus@sha256:dda13e28bf95a5e5ca5b8ed56852006094c1c8e8871d9c9dbeed30aa6e55271f
|
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
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
24.14.1
|
24.15.0
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ For the full list, refer to the [Immich source code](https://github.com/immich-a
|
|||||||
| `JPEG 2000` | `.jp2` | :white_check_mark: | |
|
| `JPEG 2000` | `.jp2` | :white_check_mark: | |
|
||||||
| `JPEG` | `.jpeg` `.jpg` `.jpe` `.insp` | :white_check_mark: | |
|
| `JPEG` | `.jpeg` `.jpg` `.jpe` `.insp` | :white_check_mark: | |
|
||||||
| `JPEG XL` | `.jxl` | :white_check_mark: | |
|
| `JPEG XL` | `.jxl` | :white_check_mark: | |
|
||||||
|
| `MPO` | `.mpo` | :white_check_mark: | Multi-Picture |
|
||||||
| `PNG` | `.png` | :white_check_mark: | |
|
| `PNG` | `.png` | :white_check_mark: | |
|
||||||
| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop |
|
| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop |
|
||||||
| `RAW` | `.raw` | :white_check_mark: | |
|
| `RAW` | `.raw` | :white_check_mark: | |
|
||||||
|
|||||||
@@ -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.
|
|
||||||
|
|||||||
+7
-7
@@ -17,10 +17,10 @@
|
|||||||
"write-heading-ids": "docusaurus write-heading-ids"
|
"write-heading-ids": "docusaurus write-heading-ids"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "~3.9.0",
|
"@docusaurus/core": "~3.10.0",
|
||||||
"@docusaurus/preset-classic": "~3.9.0",
|
"@docusaurus/preset-classic": "~3.10.0",
|
||||||
"@docusaurus/theme-common": "~3.9.0",
|
"@docusaurus/theme-common": "~3.10.0",
|
||||||
"@docusaurus/theme-mermaid": "~3.9.0",
|
"@docusaurus/theme-mermaid": "~3.10.0",
|
||||||
"@mdi/js": "^7.3.67",
|
"@mdi/js": "^7.3.67",
|
||||||
"@mdi/react": "^1.6.1",
|
"@mdi/react": "^1.6.1",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
@@ -36,9 +36,9 @@
|
|||||||
"url": "^0.11.0"
|
"url": "^0.11.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "~3.9.0",
|
"@docusaurus/module-type-aliases": "~3.10.0",
|
||||||
"@docusaurus/tsconfig": "^3.10.0",
|
"@docusaurus/tsconfig": "^3.10.0",
|
||||||
"@docusaurus/types": "^3.7.0",
|
"@docusaurus/types": "^3.10.0",
|
||||||
"prettier": "^3.7.4",
|
"prettier": "^3.7.4",
|
||||||
"typescript": "^6.0.0"
|
"typescript": "^6.0.0"
|
||||||
},
|
},
|
||||||
@@ -58,6 +58,6 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "24.14.1"
|
"node": "24.15.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,5 +11,6 @@
|
|||||||
"@types/oidc-provider": "^9.0.0",
|
"@types/oidc-provider": "^9.0.0",
|
||||||
"oidc-provider": "^9.0.0",
|
"oidc-provider": "^9.0.0",
|
||||||
"tsx": "^4.20.6"
|
"tsx": "^4.20.6"
|
||||||
}
|
},
|
||||||
|
"packageManager": "pnpm@10.33.1"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
24.14.1
|
24.15.0
|
||||||
|
|||||||
+1
-1
@@ -59,6 +59,6 @@
|
|||||||
"vitest": "^4.0.0"
|
"vitest": "^4.0.0"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "24.14.1"
|
"node": "24.15.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ describe('/albums', () => {
|
|||||||
|
|
||||||
it('should not return shared albums with a deleted owner', async () => {
|
it('should not return shared albums with a deleted owner', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get('/albums?shared=true')
|
.get('/albums?isShared=true')
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
@@ -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,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -180,82 +188,164 @@ describe('/albums', () => {
|
|||||||
it('should return the album collection including owned and shared', async () => {
|
it('should return the album collection including owned and shared', async () => {
|
||||||
const { status, body } = await request(app).get('/albums').set('Authorization', `Bearer ${user1.accessToken}`);
|
const { status, body } = await request(app).get('/albums').set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toHaveLength(4);
|
expect(body).toHaveLength(5);
|
||||||
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,
|
||||||
}),
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
albumName: user2SharedUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) },
|
||||||
|
]),
|
||||||
|
shared: true,
|
||||||
|
}),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the album collection filtered by shared', async () => {
|
it('should return the album collection filtered by isShared', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get('/albums?shared=true')
|
.get('/albums?isShared=true')
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toHaveLength(4);
|
expect(body).toHaveLength(4);
|
||||||
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,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the album collection filtered by NOT shared', async () => {
|
it('should return the album collection filtered by NOT isShared', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get('/albums?shared=false')
|
.get('/albums?isShared=false')
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toHaveLength(1);
|
expect(body).toHaveLength(1);
|
||||||
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,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return only owned albums when filtered by isOwned=true', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/albums?isOwned=true')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toHaveLength(4);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({ albumName: user1SharedEditorUser }),
|
||||||
|
expect.objectContaining({ albumName: user1SharedViewerUser }),
|
||||||
|
expect.objectContaining({ albumName: user1SharedLink }),
|
||||||
|
expect.objectContaining({ albumName: user1NotShared }),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return only shared-with-me albums when filtered by isOwned=false', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/albums?isOwned=false')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toHaveLength(1);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
albumName: user2SharedUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) },
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return owned shared-out albums when filtered by isOwned=true&ishared=true', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/albums?isOwned=true&isShared=true')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toHaveLength(3);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({ albumName: user1SharedEditorUser }),
|
||||||
|
expect.objectContaining({ albumName: user1SharedViewerUser }),
|
||||||
|
expect.objectContaining({ albumName: user1SharedLink }),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty list when filtered by isOwned=false&isShared=false', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/albums?isOwned=false&isShared=false')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
it('should return the album collection filtered by assetId', async () => {
|
it('should return the album collection filtered by assetId', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get(`/albums?assetId=${user1Asset2.id}`)
|
.get(`/albums?assetId=${user1Asset2.id}`)
|
||||||
@@ -264,17 +354,17 @@ describe('/albums', () => {
|
|||||||
expect(body).toHaveLength(2);
|
expect(body).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the album collection filtered by assetId and ignores shared=true', async () => {
|
it('should return the album collection filtered by assetId and ignores isShared=true', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get(`/albums?shared=true&assetId=${user1Asset1.id}`)
|
.get(`/albums?isShared=true&assetId=${user1Asset1.id}`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toHaveLength(5);
|
expect(body).toHaveLength(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the album collection filtered by assetId and ignores shared=false', async () => {
|
it('should return the album collection filtered by assetId and ignores isShared=false', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get(`/albums?shared=false&assetId=${user1Asset1.id}`)
|
.get(`/albums?isShared=false&assetId=${user1Asset1.id}`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toHaveLength(5);
|
expect(body).toHaveLength(5);
|
||||||
@@ -286,13 +376,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 +456,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 +492,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 +714,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 +730,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 +756,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 +771,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 +785,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;
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI
|
|||||||
'.jp2',
|
'.jp2',
|
||||||
'.jpe',
|
'.jpe',
|
||||||
'.jxl',
|
'.jxl',
|
||||||
|
'.mpo',
|
||||||
'.svg',
|
'.svg',
|
||||||
'.tif',
|
'.tif',
|
||||||
'.tiff',
|
'.tiff',
|
||||||
@@ -239,7 +240,8 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
await context.route('**/api/albums*', async (route, request) => {
|
await context.route('**/api/albums*', async (route, request) => {
|
||||||
if (request.url().endsWith('albums?shared=true') || request.url().endsWith('albums')) {
|
const url = request.url();
|
||||||
|
if (url.endsWith('albums?isShared=true') || url.endsWith('albums?isOwned=true') || url.endsWith('albums')) {
|
||||||
return route.fulfill({
|
return route.fulfill({
|
||||||
status: 200,
|
status: 200,
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
|
|||||||
@@ -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');
|
||||||
@@ -349,7 +349,7 @@ test.describe('Timeline', () => {
|
|||||||
expect(visibleMockAssetsYearMonths).toContain(month);
|
expect(visibleMockAssetsYearMonths).toContain(month);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
test('Deep link to last photo, scroll up', async ({ page }) => {
|
test.skip('Deep link to last photo, scroll up', async ({ page }) => {
|
||||||
const lastAsset = assets.at(-1)!;
|
const lastAsset = assets.at(-1)!;
|
||||||
await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
|
await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
|
||||||
|
|
||||||
@@ -361,7 +361,7 @@ test.describe('Timeline', () => {
|
|||||||
|
|
||||||
await thumbnailUtils.expectInViewport(page, '14e5901f-fd7f-40c0-b186-4d7e7fc67968');
|
await thumbnailUtils.expectInViewport(page, '14e5901f-fd7f-40c0-b186-4d7e7fc67968');
|
||||||
});
|
});
|
||||||
test('Deep link to first bucket, scroll down', async ({ page }) => {
|
test.skip('Deep link to first bucket, scroll down', async ({ page }) => {
|
||||||
const lastAsset = assets.at(0)!;
|
const lastAsset = assets.at(0)!;
|
||||||
await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
|
await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
|
||||||
await timelineUtils.locator(page).hover();
|
await timelineUtils.locator(page).hover();
|
||||||
@@ -440,7 +440,7 @@ test.describe('Timeline', () => {
|
|||||||
await thumbnailUtils.expectInViewport(page, asset.id);
|
await thumbnailUtils.expectInViewport(page, asset.id);
|
||||||
await thumbnailUtils.expectSelectedDisabled(page, asset.id);
|
await thumbnailUtils.expectSelectedDisabled(page, asset.id);
|
||||||
});
|
});
|
||||||
test('Add photos to album', async ({ page }) => {
|
test.skip('Add photos to album', async ({ page }) => {
|
||||||
const album = timelineRestData.album;
|
const album = timelineRestData.album;
|
||||||
await pageUtils.openAlbumPage(page, album.id);
|
await pageUtils.openAlbumPage(page, album.id);
|
||||||
await page.locator('nav button[aria-label="Add photos"]').click();
|
await page.locator('nav button[aria-label="Add photos"]').click();
|
||||||
@@ -752,7 +752,7 @@ test.describe('Timeline', () => {
|
|||||||
await page.getByText('Photos', { exact: true }).click();
|
await page.getByText('Photos', { exact: true }).click();
|
||||||
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
|
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
|
||||||
});
|
});
|
||||||
test('open /favorites, archive photo, unarchive photo', async ({ page }) => {
|
test.skip('open /favorites, archive photo, unarchive photo', async ({ page }) => {
|
||||||
await pageUtils.openFavorites(page);
|
await pageUtils.openFavorites(page);
|
||||||
const assetToArchive = getAsset(timelineRestData, 'ad31e29f-2069-4574-b9a9-ad86523c92cb')!;
|
const assetToArchive = getAsset(timelineRestData, 'ad31e29f-2069-4574-b9a9-ad86523c92cb')!;
|
||||||
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
|
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
|
||||||
|
|||||||
@@ -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,28 @@
|
|||||||
|
[tools]
|
||||||
|
python = "3.11"
|
||||||
|
uv = "0.8.15"
|
||||||
|
|
||||||
|
[tasks.install]
|
||||||
|
run = "uv sync --locked"
|
||||||
|
|
||||||
|
[tasks.lint]
|
||||||
|
run = "uv run ruff check immich_ml"
|
||||||
|
|
||||||
|
[tasks.test]
|
||||||
|
run = "uv run pytest --cov=immich_ml --cov-report term-missing"
|
||||||
|
|
||||||
|
[tasks.format]
|
||||||
|
run = "uv run ruff format immich_ml"
|
||||||
|
|
||||||
|
[tasks.check]
|
||||||
|
run = "uv run mypy --strict immich_ml/"
|
||||||
|
|
||||||
|
[tasks.checklist]
|
||||||
|
run = [
|
||||||
|
{ task = ":install" },
|
||||||
|
{ task = ":format" },
|
||||||
|
{ task = ":lint" },
|
||||||
|
{ task = ":check" },
|
||||||
|
{ task = ":test" },
|
||||||
|
]
|
||||||
|
|
||||||
@@ -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,<3.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
+138
-98
@@ -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,9 +1003,9 @@ 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,<3.0" },
|
||||||
{ name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" },
|
||||||
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
|
||||||
{ name = "onnxruntime", marker = "extra == 'rknn'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime", marker = "extra == 'rknn'", 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" },
|
||||||
@@ -1540,83 +1558,81 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numpy"
|
name = "numpy"
|
||||||
version = "2.3.5"
|
version = "2.4.4"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" },
|
{ url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" },
|
{ url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" },
|
{ url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" },
|
{ url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" },
|
{ url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" },
|
{ url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" },
|
{ url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" },
|
{ url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" },
|
{ url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" },
|
{ url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" },
|
{ url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" },
|
{ url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" },
|
{ url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" },
|
{ url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" },
|
{ url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" },
|
{ url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" },
|
{ url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" },
|
{ url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" },
|
{ url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" },
|
{ url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" },
|
{ url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" },
|
{ url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" },
|
{ url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" },
|
{ url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" },
|
{ url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" },
|
{ url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" },
|
{ url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" },
|
{ url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" },
|
{ url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" },
|
{ url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" },
|
{ url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" },
|
{ url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" },
|
{ url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" },
|
{ url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" },
|
{ url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" },
|
{ url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" },
|
{ url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" },
|
{ url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" },
|
{ url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" },
|
{ url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" },
|
{ url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" },
|
{ url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" },
|
{ url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" },
|
{ url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" },
|
{ url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" },
|
{ url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" },
|
{ url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" },
|
{ url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" },
|
{ url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" },
|
{ url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" },
|
{ url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" },
|
{ url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" },
|
{ url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" },
|
{ url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" },
|
{ url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" },
|
{ url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" },
|
{ url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" },
|
{ url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" },
|
{ url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" },
|
{ url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" },
|
{ url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" },
|
{ url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" },
|
{ url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" },
|
{ url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" },
|
{ url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" },
|
{ url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" },
|
{ url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" },
|
{ url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" },
|
{ url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" },
|
{ url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" },
|
{ url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2296,11 +2312,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-multipart"
|
name = "python-multipart"
|
||||||
version = "0.0.26"
|
version = "0.0.27"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/88/71/b145a380824a960ebd60e1014256dbb7d2253f2316ff2d73dfd8928ec2c3/python_multipart-0.0.26.tar.gz", hash = "sha256:08fadc45918cd615e26846437f50c5d6d23304da32c341f289a617127b081f17", size = 43501, upload-time = "2026-04-10T14:09:59.473Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/69/9b/f23807317a113dc36e74e75eb265a02dd1a4d9082abc3c1064acd22997c4/python_multipart-0.0.27.tar.gz", hash = "sha256:9870a6a8c5a20a5bf4f07c017bd1489006ff8836cff097b6933355ee2b49b602", size = 44043, upload-time = "2026-04-27T10:51:26.649Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/22/f1925cdda983ab66fc8ec6ec8014b959262747e58bdca26a4e3d1da29d56/python_multipart-0.0.26-py3-none-any.whl", hash = "sha256:c0b169f8c4484c13b0dcf2ef0ec3a4adb255c4b7d18d8e420477d2b1dd03f185", size = 28847, upload-time = "2026-04-10T14:09:58.131Z" },
|
{ url = "https://files.pythonhosted.org/packages/99/78/4126abcbdbd3c559d43e0db7f7b9173fc6befe45d39a2856cc0b8ec2a5a6/python_multipart-0.0.27-py3-none-any.whl", hash = "sha256:6fccfad17a27334bd0193681b369f476eda3409f17381a2d65aa7df3f7275645", size = 29254, upload-time = "2026-04-27T10:51:24.997Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2769,6 +2785,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 +2957,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.14.1"
|
node = "24.15.0"
|
||||||
flutter = "3.41.6"
|
flutter = "3.41.9"
|
||||||
pnpm = "10.33.0"
|
pnpm = "10.33.1"
|
||||||
terragrunt = "1.0.0"
|
terragrunt = "1.0.3"
|
||||||
opentofu = "1.11.5"
|
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.6",
|
"dart.flutterSdkPath": ".fvm/versions/3.41.9",
|
||||||
"dart.lineLength": 120,
|
"dart.lineLength": 120,
|
||||||
"[dart]": {
|
"[dart]": {
|
||||||
"editor.rulers": [
|
"editor.rulers": [
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ data class PlatformAsset (
|
|||||||
val updatedAt: Long? = null,
|
val updatedAt: Long? = null,
|
||||||
val width: Long? = null,
|
val width: Long? = null,
|
||||||
val height: Long? = null,
|
val height: Long? = null,
|
||||||
val durationInSeconds: Long,
|
val durationMs: Long,
|
||||||
val orientation: Long,
|
val orientation: Long,
|
||||||
val isFavorite: Boolean,
|
val isFavorite: Boolean,
|
||||||
val adjustmentTime: Long? = null,
|
val adjustmentTime: Long? = null,
|
||||||
@@ -234,14 +234,14 @@ data class PlatformAsset (
|
|||||||
val updatedAt = pigeonVar_list[4] as Long?
|
val updatedAt = pigeonVar_list[4] as Long?
|
||||||
val width = pigeonVar_list[5] as Long?
|
val width = pigeonVar_list[5] as Long?
|
||||||
val height = pigeonVar_list[6] as Long?
|
val height = pigeonVar_list[6] as Long?
|
||||||
val durationInSeconds = pigeonVar_list[7] as Long
|
val durationMs = pigeonVar_list[7] as Long
|
||||||
val orientation = pigeonVar_list[8] as Long
|
val orientation = pigeonVar_list[8] as Long
|
||||||
val isFavorite = pigeonVar_list[9] as Boolean
|
val isFavorite = pigeonVar_list[9] as Boolean
|
||||||
val adjustmentTime = pigeonVar_list[10] as Long?
|
val adjustmentTime = pigeonVar_list[10] as Long?
|
||||||
val latitude = pigeonVar_list[11] as Double?
|
val latitude = pigeonVar_list[11] as Double?
|
||||||
val longitude = pigeonVar_list[12] as Double?
|
val longitude = pigeonVar_list[12] as Double?
|
||||||
val playbackStyle = pigeonVar_list[13] as PlatformAssetPlaybackStyle
|
val playbackStyle = pigeonVar_list[13] as PlatformAssetPlaybackStyle
|
||||||
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, adjustmentTime, latitude, longitude, playbackStyle)
|
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationMs, orientation, isFavorite, adjustmentTime, latitude, longitude, playbackStyle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun toList(): List<Any?> {
|
fun toList(): List<Any?> {
|
||||||
@@ -253,7 +253,7 @@ data class PlatformAsset (
|
|||||||
updatedAt,
|
updatedAt,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
durationInSeconds,
|
durationMs,
|
||||||
orientation,
|
orientation,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
adjustmentTime,
|
adjustmentTime,
|
||||||
@@ -270,7 +270,7 @@ data class PlatformAsset (
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
val other = other as PlatformAsset
|
val other = other as PlatformAsset
|
||||||
return MessagesPigeonUtils.deepEquals(this.id, other.id) && MessagesPigeonUtils.deepEquals(this.name, other.name) && MessagesPigeonUtils.deepEquals(this.type, other.type) && MessagesPigeonUtils.deepEquals(this.createdAt, other.createdAt) && MessagesPigeonUtils.deepEquals(this.updatedAt, other.updatedAt) && MessagesPigeonUtils.deepEquals(this.width, other.width) && MessagesPigeonUtils.deepEquals(this.height, other.height) && MessagesPigeonUtils.deepEquals(this.durationInSeconds, other.durationInSeconds) && MessagesPigeonUtils.deepEquals(this.orientation, other.orientation) && MessagesPigeonUtils.deepEquals(this.isFavorite, other.isFavorite) && MessagesPigeonUtils.deepEquals(this.adjustmentTime, other.adjustmentTime) && MessagesPigeonUtils.deepEquals(this.latitude, other.latitude) && MessagesPigeonUtils.deepEquals(this.longitude, other.longitude) && MessagesPigeonUtils.deepEquals(this.playbackStyle, other.playbackStyle)
|
return MessagesPigeonUtils.deepEquals(this.id, other.id) && MessagesPigeonUtils.deepEquals(this.name, other.name) && MessagesPigeonUtils.deepEquals(this.type, other.type) && MessagesPigeonUtils.deepEquals(this.createdAt, other.createdAt) && MessagesPigeonUtils.deepEquals(this.updatedAt, other.updatedAt) && MessagesPigeonUtils.deepEquals(this.width, other.width) && MessagesPigeonUtils.deepEquals(this.height, other.height) && MessagesPigeonUtils.deepEquals(this.durationMs, other.durationMs) && MessagesPigeonUtils.deepEquals(this.orientation, other.orientation) && MessagesPigeonUtils.deepEquals(this.isFavorite, other.isFavorite) && MessagesPigeonUtils.deepEquals(this.adjustmentTime, other.adjustmentTime) && MessagesPigeonUtils.deepEquals(this.latitude, other.latitude) && MessagesPigeonUtils.deepEquals(this.longitude, other.longitude) && MessagesPigeonUtils.deepEquals(this.playbackStyle, other.playbackStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
@@ -282,7 +282,7 @@ data class PlatformAsset (
|
|||||||
result = 31 * result + MessagesPigeonUtils.deepHash(this.updatedAt)
|
result = 31 * result + MessagesPigeonUtils.deepHash(this.updatedAt)
|
||||||
result = 31 * result + MessagesPigeonUtils.deepHash(this.width)
|
result = 31 * result + MessagesPigeonUtils.deepHash(this.width)
|
||||||
result = 31 * result + MessagesPigeonUtils.deepHash(this.height)
|
result = 31 * result + MessagesPigeonUtils.deepHash(this.height)
|
||||||
result = 31 * result + MessagesPigeonUtils.deepHash(this.durationInSeconds)
|
result = 31 * result + MessagesPigeonUtils.deepHash(this.durationMs)
|
||||||
result = 31 * result + MessagesPigeonUtils.deepHash(this.orientation)
|
result = 31 * result + MessagesPigeonUtils.deepHash(this.orientation)
|
||||||
result = 31 * result + MessagesPigeonUtils.deepHash(this.isFavorite)
|
result = 31 * result + MessagesPigeonUtils.deepHash(this.isFavorite)
|
||||||
result = 31 * result + MessagesPigeonUtils.deepHash(this.adjustmentTime)
|
result = 31 * result + MessagesPigeonUtils.deepHash(this.adjustmentTime)
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
val height = c.getInt(heightColumn).toLong()
|
val height = c.getInt(heightColumn).toLong()
|
||||||
// Duration is milliseconds
|
// Duration is milliseconds
|
||||||
val duration = if (rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0L
|
val duration = if (rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0L
|
||||||
else c.getLong(durationColumn) / 1000
|
else c.getLong(durationColumn)
|
||||||
val orientation = c.getInt(orientationColumn)
|
val orientation = c.getInt(orientationColumn)
|
||||||
val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0
|
val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
+3351
File diff suppressed because it is too large
Load Diff
+3301
File diff suppressed because it is too large
Load Diff
+3358
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 77;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -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>
|
||||||
|
|||||||
Generated
+6
-6
@@ -192,7 +192,7 @@ struct PlatformAsset: Hashable {
|
|||||||
var updatedAt: Int64? = nil
|
var updatedAt: Int64? = nil
|
||||||
var width: Int64? = nil
|
var width: Int64? = nil
|
||||||
var height: Int64? = nil
|
var height: Int64? = nil
|
||||||
var durationInSeconds: Int64
|
var durationMs: Int64
|
||||||
var orientation: Int64
|
var orientation: Int64
|
||||||
var isFavorite: Bool
|
var isFavorite: Bool
|
||||||
var adjustmentTime: Int64? = nil
|
var adjustmentTime: Int64? = nil
|
||||||
@@ -210,7 +210,7 @@ struct PlatformAsset: Hashable {
|
|||||||
let updatedAt: Int64? = nilOrValue(pigeonVar_list[4])
|
let updatedAt: Int64? = nilOrValue(pigeonVar_list[4])
|
||||||
let width: Int64? = nilOrValue(pigeonVar_list[5])
|
let width: Int64? = nilOrValue(pigeonVar_list[5])
|
||||||
let height: Int64? = nilOrValue(pigeonVar_list[6])
|
let height: Int64? = nilOrValue(pigeonVar_list[6])
|
||||||
let durationInSeconds = pigeonVar_list[7] as! Int64
|
let durationMs = pigeonVar_list[7] as! Int64
|
||||||
let orientation = pigeonVar_list[8] as! Int64
|
let orientation = pigeonVar_list[8] as! Int64
|
||||||
let isFavorite = pigeonVar_list[9] as! Bool
|
let isFavorite = pigeonVar_list[9] as! Bool
|
||||||
let adjustmentTime: Int64? = nilOrValue(pigeonVar_list[10])
|
let adjustmentTime: Int64? = nilOrValue(pigeonVar_list[10])
|
||||||
@@ -226,7 +226,7 @@ struct PlatformAsset: Hashable {
|
|||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
durationInSeconds: durationInSeconds,
|
durationMs: durationMs,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
adjustmentTime: adjustmentTime,
|
adjustmentTime: adjustmentTime,
|
||||||
@@ -244,7 +244,7 @@ struct PlatformAsset: Hashable {
|
|||||||
updatedAt,
|
updatedAt,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
durationInSeconds,
|
durationMs,
|
||||||
orientation,
|
orientation,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
adjustmentTime,
|
adjustmentTime,
|
||||||
@@ -257,7 +257,7 @@ struct PlatformAsset: Hashable {
|
|||||||
if Swift.type(of: lhs) != Swift.type(of: rhs) {
|
if Swift.type(of: lhs) != Swift.type(of: rhs) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return deepEqualsMessages(lhs.id, rhs.id) && deepEqualsMessages(lhs.name, rhs.name) && deepEqualsMessages(lhs.type, rhs.type) && deepEqualsMessages(lhs.createdAt, rhs.createdAt) && deepEqualsMessages(lhs.updatedAt, rhs.updatedAt) && deepEqualsMessages(lhs.width, rhs.width) && deepEqualsMessages(lhs.height, rhs.height) && deepEqualsMessages(lhs.durationInSeconds, rhs.durationInSeconds) && deepEqualsMessages(lhs.orientation, rhs.orientation) && deepEqualsMessages(lhs.isFavorite, rhs.isFavorite) && deepEqualsMessages(lhs.adjustmentTime, rhs.adjustmentTime) && deepEqualsMessages(lhs.latitude, rhs.latitude) && deepEqualsMessages(lhs.longitude, rhs.longitude) && deepEqualsMessages(lhs.playbackStyle, rhs.playbackStyle)
|
return deepEqualsMessages(lhs.id, rhs.id) && deepEqualsMessages(lhs.name, rhs.name) && deepEqualsMessages(lhs.type, rhs.type) && deepEqualsMessages(lhs.createdAt, rhs.createdAt) && deepEqualsMessages(lhs.updatedAt, rhs.updatedAt) && deepEqualsMessages(lhs.width, rhs.width) && deepEqualsMessages(lhs.height, rhs.height) && deepEqualsMessages(lhs.durationMs, rhs.durationMs) && deepEqualsMessages(lhs.orientation, rhs.orientation) && deepEqualsMessages(lhs.isFavorite, rhs.isFavorite) && deepEqualsMessages(lhs.adjustmentTime, rhs.adjustmentTime) && deepEqualsMessages(lhs.latitude, rhs.latitude) && deepEqualsMessages(lhs.longitude, rhs.longitude) && deepEqualsMessages(lhs.playbackStyle, rhs.playbackStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
func hash(into hasher: inout Hasher) {
|
||||||
@@ -269,7 +269,7 @@ struct PlatformAsset: Hashable {
|
|||||||
deepHashMessages(value: updatedAt, hasher: &hasher)
|
deepHashMessages(value: updatedAt, hasher: &hasher)
|
||||||
deepHashMessages(value: width, hasher: &hasher)
|
deepHashMessages(value: width, hasher: &hasher)
|
||||||
deepHashMessages(value: height, hasher: &hasher)
|
deepHashMessages(value: height, hasher: &hasher)
|
||||||
deepHashMessages(value: durationInSeconds, hasher: &hasher)
|
deepHashMessages(value: durationMs, hasher: &hasher)
|
||||||
deepHashMessages(value: orientation, hasher: &hasher)
|
deepHashMessages(value: orientation, hasher: &hasher)
|
||||||
deepHashMessages(value: isFavorite, hasher: &hasher)
|
deepHashMessages(value: isFavorite, hasher: &hasher)
|
||||||
deepHashMessages(value: adjustmentTime, hasher: &hasher)
|
deepHashMessages(value: adjustmentTime, hasher: &hasher)
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
|
|||||||
id: asset.localIdentifier,
|
id: asset.localIdentifier,
|
||||||
name: "",
|
name: "",
|
||||||
type: 0,
|
type: 0,
|
||||||
durationInSeconds: 0,
|
durationMs: 0,
|
||||||
orientation: 0,
|
orientation: 0,
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
playbackStyle: .unknown
|
playbackStyle: .unknown
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ extension PHAsset {
|
|||||||
updatedAt: modificationDate.map { Int64($0.timeIntervalSince1970) },
|
updatedAt: modificationDate.map { Int64($0.timeIntervalSince1970) },
|
||||||
width: Int64(pixelWidth),
|
width: Int64(pixelWidth),
|
||||||
height: Int64(pixelHeight),
|
height: Int64(pixelHeight),
|
||||||
durationInSeconds: Int64(duration),
|
durationMs: Int64(duration * 1000),
|
||||||
orientation: 0,
|
orientation: 0,
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
adjustmentTime: adjustmentTimestamp,
|
adjustmentTime: adjustmentTimestamp,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
enum ImmichColorPreset { indigo, deepPurple, pink, red, orange, yellow, lime, green, cyan, slateGray }
|
enum ImmichColorPreset { indigo, deepPurple, pink, red, orange, yellow, lime, green, cyan, slateGray }
|
||||||
|
|
||||||
const ImmichColorPreset defaultColorPreset = ImmichColorPreset.indigo;
|
|
||||||
const String defaultColorPresetName = "indigo";
|
|
||||||
|
|
||||||
const Color immichBrandColorLight = Color(0xFF4150AF);
|
const Color immichBrandColorLight = Color(0xFF4150AF);
|
||||||
const Color immichBrandColorDark = Color(0xFFACCBFA);
|
const Color immichBrandColorDark = Color(0xFFACCBFA);
|
||||||
const Color whiteOpacity75 = Color.fromRGBO(255, 255, 255, 0.75);
|
const Color whiteOpacity75 = Color.fromRGBO(255, 255, 255, 0.75);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ sealed class BaseAsset {
|
|||||||
final DateTime updatedAt;
|
final DateTime updatedAt;
|
||||||
final int? width;
|
final int? width;
|
||||||
final int? height;
|
final int? height;
|
||||||
final int? durationInSeconds;
|
final int? durationMs;
|
||||||
final bool isFavorite;
|
final bool isFavorite;
|
||||||
final String? livePhotoVideoId;
|
final String? livePhotoVideoId;
|
||||||
final bool isEdited;
|
final bool isEdited;
|
||||||
@@ -38,7 +38,7 @@ sealed class BaseAsset {
|
|||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
this.width,
|
this.width,
|
||||||
this.height,
|
this.height,
|
||||||
this.durationInSeconds,
|
this.durationMs,
|
||||||
this.isFavorite = false,
|
this.isFavorite = false,
|
||||||
this.livePhotoVideoId,
|
this.livePhotoVideoId,
|
||||||
required this.isEdited,
|
required this.isEdited,
|
||||||
@@ -53,15 +53,17 @@ sealed class BaseAsset {
|
|||||||
AssetPlaybackStyle get playbackStyle {
|
AssetPlaybackStyle get playbackStyle {
|
||||||
if (isVideo) return AssetPlaybackStyle.video;
|
if (isVideo) return AssetPlaybackStyle.video;
|
||||||
if (isMotionPhoto) return AssetPlaybackStyle.livePhoto;
|
if (isMotionPhoto) return AssetPlaybackStyle.livePhoto;
|
||||||
if (isImage && durationInSeconds != null && durationInSeconds! > 0) return AssetPlaybackStyle.imageAnimated;
|
if (isImage && durationMs != null && durationMs! > 0) {
|
||||||
|
return AssetPlaybackStyle.imageAnimated;
|
||||||
|
}
|
||||||
if (isImage) return AssetPlaybackStyle.image;
|
if (isImage) return AssetPlaybackStyle.image;
|
||||||
return AssetPlaybackStyle.unknown;
|
return AssetPlaybackStyle.unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
Duration get duration {
|
Duration get duration {
|
||||||
final durationInSeconds = this.durationInSeconds;
|
final durationMs = this.durationMs;
|
||||||
if (durationInSeconds != null) {
|
if (durationMs != null) {
|
||||||
return Duration(seconds: durationInSeconds);
|
return Duration(milliseconds: durationMs);
|
||||||
}
|
}
|
||||||
return const Duration();
|
return const Duration();
|
||||||
}
|
}
|
||||||
@@ -88,7 +90,7 @@ sealed class BaseAsset {
|
|||||||
updatedAt: $updatedAt,
|
updatedAt: $updatedAt,
|
||||||
width: ${width ?? "<NA>"},
|
width: ${width ?? "<NA>"},
|
||||||
height: ${height ?? "<NA>"},
|
height: ${height ?? "<NA>"},
|
||||||
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
durationMs: ${durationMs ?? "<NA>"},
|
||||||
isFavorite: $isFavorite,
|
isFavorite: $isFavorite,
|
||||||
isEdited: $isEdited,
|
isEdited: $isEdited,
|
||||||
}''';
|
}''';
|
||||||
@@ -104,7 +106,7 @@ sealed class BaseAsset {
|
|||||||
updatedAt == other.updatedAt &&
|
updatedAt == other.updatedAt &&
|
||||||
width == other.width &&
|
width == other.width &&
|
||||||
height == other.height &&
|
height == other.height &&
|
||||||
durationInSeconds == other.durationInSeconds &&
|
durationMs == other.durationMs &&
|
||||||
isFavorite == other.isFavorite &&
|
isFavorite == other.isFavorite &&
|
||||||
isEdited == other.isEdited;
|
isEdited == other.isEdited;
|
||||||
}
|
}
|
||||||
@@ -119,7 +121,7 @@ sealed class BaseAsset {
|
|||||||
updatedAt.hashCode ^
|
updatedAt.hashCode ^
|
||||||
width.hashCode ^
|
width.hashCode ^
|
||||||
height.hashCode ^
|
height.hashCode ^
|
||||||
durationInSeconds.hashCode ^
|
durationMs.hashCode ^
|
||||||
isFavorite.hashCode ^
|
isFavorite.hashCode ^
|
||||||
isEdited.hashCode;
|
isEdited.hashCode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
required super.updatedAt,
|
required super.updatedAt,
|
||||||
super.width,
|
super.width,
|
||||||
super.height,
|
super.height,
|
||||||
super.durationInSeconds,
|
super.durationMs,
|
||||||
super.isFavorite = false,
|
super.isFavorite = false,
|
||||||
super.livePhotoVideoId,
|
super.livePhotoVideoId,
|
||||||
this.orientation = 0,
|
this.orientation = 0,
|
||||||
@@ -58,7 +58,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
updatedAt: $updatedAt,
|
updatedAt: $updatedAt,
|
||||||
width: ${width ?? "<NA>"},
|
width: ${width ?? "<NA>"},
|
||||||
height: ${height ?? "<NA>"},
|
height: ${height ?? "<NA>"},
|
||||||
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
durationMs: ${durationMs ?? "<NA>"},
|
||||||
playbackStyle: $playbackStyle,
|
playbackStyle: $playbackStyle,
|
||||||
remoteId: ${remoteId ?? "<NA>"},
|
remoteId: ${remoteId ?? "<NA>"},
|
||||||
cloudId: ${cloudId ?? "<NA>"},
|
cloudId: ${cloudId ?? "<NA>"},
|
||||||
@@ -108,7 +108,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
int? width,
|
int? width,
|
||||||
int? height,
|
int? height,
|
||||||
int? durationInSeconds,
|
int? durationMs,
|
||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
int? orientation,
|
int? orientation,
|
||||||
AssetPlaybackStyle? playbackStyle,
|
AssetPlaybackStyle? playbackStyle,
|
||||||
@@ -128,7 +128,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
width: width ?? this.width,
|
width: width ?? this.width,
|
||||||
height: height ?? this.height,
|
height: height ?? this.height,
|
||||||
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
durationMs: durationMs ?? this.durationMs,
|
||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
playbackStyle: playbackStyle ?? this.playbackStyle,
|
playbackStyle: playbackStyle ?? this.playbackStyle,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class RemoteAsset extends BaseAsset {
|
|||||||
required super.updatedAt,
|
required super.updatedAt,
|
||||||
super.width,
|
super.width,
|
||||||
super.height,
|
super.height,
|
||||||
super.durationInSeconds,
|
super.durationMs,
|
||||||
super.isFavorite = false,
|
super.isFavorite = false,
|
||||||
this.thumbHash,
|
this.thumbHash,
|
||||||
this.visibility = AssetVisibility.timeline,
|
this.visibility = AssetVisibility.timeline,
|
||||||
@@ -57,7 +57,7 @@ class RemoteAsset extends BaseAsset {
|
|||||||
updatedAt: $updatedAt,
|
updatedAt: $updatedAt,
|
||||||
width: ${width ?? "<NA>"},
|
width: ${width ?? "<NA>"},
|
||||||
height: ${height ?? "<NA>"},
|
height: ${height ?? "<NA>"},
|
||||||
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
durationMs: ${durationMs ?? "<NA>"},
|
||||||
localId: ${localId ?? "<NA>"},
|
localId: ${localId ?? "<NA>"},
|
||||||
isFavorite: $isFavorite,
|
isFavorite: $isFavorite,
|
||||||
thumbHash: ${thumbHash ?? "<NA>"},
|
thumbHash: ${thumbHash ?? "<NA>"},
|
||||||
@@ -102,7 +102,7 @@ class RemoteAsset extends BaseAsset {
|
|||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
int? width,
|
int? width,
|
||||||
int? height,
|
int? height,
|
||||||
int? durationInSeconds,
|
int? durationMs,
|
||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
String? thumbHash,
|
String? thumbHash,
|
||||||
AssetVisibility? visibility,
|
AssetVisibility? visibility,
|
||||||
@@ -121,7 +121,7 @@ class RemoteAsset extends BaseAsset {
|
|||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
width: width ?? this.width,
|
width: width ?? this.width,
|
||||||
height: height ?? this.height,
|
height: height ?? this.height,
|
||||||
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
durationMs: durationMs ?? this.durationMs,
|
||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
thumbHash: thumbHash ?? this.thumbHash,
|
thumbHash: thumbHash ?? this.thumbHash,
|
||||||
visibility: visibility ?? this.visibility,
|
visibility: visibility ?? this.visibility,
|
||||||
@@ -146,7 +146,7 @@ class RemoteAssetExif extends RemoteAsset {
|
|||||||
required super.updatedAt,
|
required super.updatedAt,
|
||||||
super.width,
|
super.width,
|
||||||
super.height,
|
super.height,
|
||||||
super.durationInSeconds,
|
super.durationMs,
|
||||||
super.isFavorite = false,
|
super.isFavorite = false,
|
||||||
super.thumbHash,
|
super.thumbHash,
|
||||||
super.visibility = AssetVisibility.timeline,
|
super.visibility = AssetVisibility.timeline,
|
||||||
@@ -178,7 +178,7 @@ class RemoteAssetExif extends RemoteAsset {
|
|||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
int? width,
|
int? width,
|
||||||
int? height,
|
int? height,
|
||||||
int? durationInSeconds,
|
int? durationMs,
|
||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
String? thumbHash,
|
String? thumbHash,
|
||||||
AssetVisibility? visibility,
|
AssetVisibility? visibility,
|
||||||
@@ -198,7 +198,7 @@ class RemoteAssetExif extends RemoteAsset {
|
|||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
width: width ?? this.width,
|
width: width ?? this.width,
|
||||||
height: height ?? this.height,
|
height: height ?? this.height,
|
||||||
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
durationMs: durationMs ?? this.durationMs,
|
||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
thumbHash: thumbHash ?? this.thumbHash,
|
thumbHash: thumbHash ?? this.thumbHash,
|
||||||
visibility: visibility ?? this.visibility,
|
visibility: visibility ?? this.visibility,
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/config/cleanup_config.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/config/theme_config.dart';
|
||||||
|
|
||||||
|
class AppConfig {
|
||||||
|
final ThemeConfig theme;
|
||||||
|
final CleanupConfig cleanup;
|
||||||
|
|
||||||
|
const AppConfig({this.theme = const .new(), this.cleanup = const .new()});
|
||||||
|
|
||||||
|
AppConfig copyWith({ThemeConfig? theme, CleanupConfig? cleanup}) =>
|
||||||
|
.new(theme: theme ?? this.theme, cleanup: cleanup ?? this.cleanup);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) || (other is AppConfig && other.theme == theme && other.cleanup == cleanup);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(theme, cleanup);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AppConfig(theme: $theme, cleanup: $cleanup)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
|
|
||||||
|
class CleanupConfig {
|
||||||
|
final bool keepFavorites;
|
||||||
|
final AssetKeepType keepMediaType;
|
||||||
|
final List<String> keepAlbumIds;
|
||||||
|
final int cutoffDaysAgo;
|
||||||
|
final bool defaultsInitialized;
|
||||||
|
|
||||||
|
const CleanupConfig({
|
||||||
|
this.keepFavorites = true,
|
||||||
|
this.keepMediaType = AssetKeepType.none,
|
||||||
|
this.keepAlbumIds = const [],
|
||||||
|
this.cutoffDaysAgo = -1,
|
||||||
|
this.defaultsInitialized = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
CleanupConfig copyWith({
|
||||||
|
bool? keepFavorites,
|
||||||
|
AssetKeepType? keepMediaType,
|
||||||
|
List<String>? keepAlbumIds,
|
||||||
|
int? cutoffDaysAgo,
|
||||||
|
bool? defaultsInitialized,
|
||||||
|
}) => .new(
|
||||||
|
keepFavorites: keepFavorites ?? this.keepFavorites,
|
||||||
|
keepMediaType: keepMediaType ?? this.keepMediaType,
|
||||||
|
keepAlbumIds: keepAlbumIds ?? this.keepAlbumIds,
|
||||||
|
cutoffDaysAgo: cutoffDaysAgo ?? this.cutoffDaysAgo,
|
||||||
|
defaultsInitialized: defaultsInitialized ?? this.defaultsInitialized,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is CleanupConfig &&
|
||||||
|
other.keepFavorites == keepFavorites &&
|
||||||
|
other.keepMediaType == keepMediaType &&
|
||||||
|
other.keepAlbumIds == keepAlbumIds &&
|
||||||
|
other.cutoffDaysAgo == cutoffDaysAgo &&
|
||||||
|
other.defaultsInitialized == defaultsInitialized);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(keepFavorites, keepMediaType, keepAlbumIds, cutoffDaysAgo, defaultsInitialized);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'CleanupConfig(keepFavorites: $keepFavorites, keepMediaType: $keepMediaType, keepAlbumIds: $keepAlbumIds, cutoffDaysAgo: $cutoffDaysAgo, defaultsInitialized: $defaultsInitialized)';
|
||||||
|
}
|
||||||
@@ -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,44 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:immich_mobile/constants/colors.dart';
|
||||||
|
|
||||||
|
class ThemeConfig {
|
||||||
|
final ThemeMode mode;
|
||||||
|
final ImmichColorPreset primaryColor;
|
||||||
|
final bool dynamicTheme;
|
||||||
|
final bool colorfulInterface;
|
||||||
|
|
||||||
|
const ThemeConfig({
|
||||||
|
this.mode = .system,
|
||||||
|
this.primaryColor = .indigo,
|
||||||
|
this.dynamicTheme = false,
|
||||||
|
this.colorfulInterface = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
ThemeConfig copyWith({
|
||||||
|
ThemeMode? mode,
|
||||||
|
ImmichColorPreset? primaryColor,
|
||||||
|
bool? dynamicTheme,
|
||||||
|
bool? colorfulInterface,
|
||||||
|
}) => .new(
|
||||||
|
mode: mode ?? this.mode,
|
||||||
|
primaryColor: primaryColor ?? this.primaryColor,
|
||||||
|
dynamicTheme: dynamicTheme ?? this.dynamicTheme,
|
||||||
|
colorfulInterface: colorfulInterface ?? this.colorfulInterface,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is ThemeConfig &&
|
||||||
|
other.mode == mode &&
|
||||||
|
other.primaryColor == primaryColor &&
|
||||||
|
other.dynamicTheme == dynamicTheme &&
|
||||||
|
other.colorfulInterface == colorfulInterface);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(mode, primaryColor, dynamicTheme, colorfulInterface);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'ThemeConfig(mode: $mode, primaryColor: $primaryColor, dynamicTheme: $dynamicTheme, colorfulInterface: $colorfulInterface)';
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ class ExifInfo {
|
|||||||
bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0;
|
bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0;
|
||||||
|
|
||||||
String get exposureTime {
|
String get exposureTime {
|
||||||
if (exposureSeconds == null) {
|
if (exposureSeconds == null || exposureSeconds! <= 0 || exposureSeconds!.isNaN) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
if (exposureSeconds! < 1) {
|
if (exposureSeconds! < 1) {
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:immich_mobile/constants/colors.dart';
|
||||||
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/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> {
|
||||||
|
// Theme
|
||||||
|
themePrimaryColor<ImmichColorPreset>(.appConfig, 'theme.primaryColor', .indigo, _EnumCodec(ImmichColorPreset.values)),
|
||||||
|
themeMode<ThemeMode>(.appConfig, 'theme.mode', .system, _EnumCodec(ThemeMode.values)),
|
||||||
|
themeDynamic<bool>(.appConfig, 'theme.dynamic', false),
|
||||||
|
themeColorfulInterface<bool>(.appConfig, 'theme.colorfulInterface', true),
|
||||||
|
|
||||||
|
// Log
|
||||||
|
logLevel<LogLevel>(.systemConfig, 'log.level', .info, _EnumCodec(LogLevel.values)),
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
cleanupKeepFavorites<bool>(.appConfig, 'cleanup.keepFavorites', true),
|
||||||
|
cleanupKeepMediaType<AssetKeepType>(
|
||||||
|
.appConfig,
|
||||||
|
'cleanup.keepMediaType',
|
||||||
|
AssetKeepType.none,
|
||||||
|
_EnumCodec(AssetKeepType.values),
|
||||||
|
),
|
||||||
|
cleanupKeepAlbumIds<List<String>>(.appConfig, 'cleanup.keepAlbumIds', [], _ListCodec(_PrimitiveCodec.string)),
|
||||||
|
cleanupCutoffDaysAgo<int>(.appConfig, 'cleanup.cutoffDaysAgo', -1),
|
||||||
|
cleanupDefaultsInitialized<bool>(.appConfig, 'cleanup.defaultsInitialized', false);
|
||||||
|
|
||||||
|
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 _ListCodec<T extends Object> extends _MetadataCodec<List<T>> {
|
||||||
|
final _MetadataCodec<T> _elementCodec;
|
||||||
|
|
||||||
|
const _ListCodec(this._elementCodec);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(List<T> value) => jsonEncode(value.map(_elementCodec.encode).toList());
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<T>? decode(String raw) {
|
||||||
|
try {
|
||||||
|
final decoded = jsonDecode(raw);
|
||||||
|
if (decoded is! List) return null;
|
||||||
|
final result = <T>[];
|
||||||
|
for (final item in decoded) {
|
||||||
|
if (item is! String) return null;
|
||||||
|
final element = _elementCodec.decode(item);
|
||||||
|
if (element == null) return null;
|
||||||
|
result.add(element);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} on FormatException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -50,11 +48,6 @@ enum StoreKey<T> {
|
|||||||
enableHapticFeedback<bool>._(126),
|
enableHapticFeedback<bool>._(126),
|
||||||
customHeaders<String>._(127),
|
customHeaders<String>._(127),
|
||||||
|
|
||||||
// theme settings
|
|
||||||
primaryColor<String>._(128),
|
|
||||||
dynamicTheme<bool>._(129),
|
|
||||||
colorfulInterface<bool>._(130),
|
|
||||||
|
|
||||||
syncAlbums<bool>._(131),
|
syncAlbums<bool>._(131),
|
||||||
|
|
||||||
// Auto endpoint switching
|
// Auto endpoint switching
|
||||||
@@ -88,13 +81,19 @@ enum StoreKey<T> {
|
|||||||
shouldResetSync<bool>._(1007),
|
shouldResetSync<bool>._(1007),
|
||||||
|
|
||||||
// Free up space
|
// Free up space
|
||||||
cleanupKeepFavorites<bool>._(1008),
|
syncMigrationStatus<String>._(1013),
|
||||||
cleanupKeepMediaType<int>._(1009),
|
|
||||||
cleanupKeepAlbumIds<String>._(1010),
|
|
||||||
cleanupCutoffDaysAgo<int>._(1011),
|
|
||||||
cleanupDefaultsInitialized<bool>._(1012),
|
|
||||||
|
|
||||||
syncMigrationStatus<String>._(1013);
|
// Legacy keys that have been migrated to the new metadata store
|
||||||
|
legacyPrimaryColor<String>._(128),
|
||||||
|
legacyDynamicTheme<bool>._(129),
|
||||||
|
legacyColorfulInterface<bool>._(130),
|
||||||
|
legacyThemeMode<String>._(102),
|
||||||
|
legacyCleanupKeepFavorites<bool>._(1008),
|
||||||
|
legacyCleanupKeepMediaType<int>._(1009),
|
||||||
|
legacyCleanupKeepAlbumIds<String>._(1010),
|
||||||
|
legacyCleanupCutoffDaysAgo<int>._(1011),
|
||||||
|
legacyCleanupDefaultsInitialized<bool>._(1012),
|
||||||
|
legacyLogLevel<int>._(115);
|
||||||
|
|
||||||
const StoreKey._(this.id);
|
const StoreKey._(this.id);
|
||||||
final int id;
|
final int id;
|
||||||
|
|||||||
@@ -176,15 +176,8 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
final backgroundSyncManager = _ref?.read(backgroundSyncProvider);
|
final backgroundSyncManager = _ref?.read(backgroundSyncProvider);
|
||||||
final nativeSyncApi = _ref?.read(nativeSyncApiProvider);
|
final nativeSyncApi = _ref?.read(nativeSyncApiProvider);
|
||||||
|
|
||||||
await _drift.close();
|
|
||||||
await _driftLogger.close();
|
|
||||||
|
|
||||||
_ref?.dispose();
|
|
||||||
_ref = null;
|
|
||||||
|
|
||||||
_cancellationToken.complete();
|
|
||||||
_logger.info("Cleaning up background worker");
|
_logger.info("Cleaning up background worker");
|
||||||
|
_cancellationToken.complete();
|
||||||
final cleanupFutures = [
|
final cleanupFutures = [
|
||||||
nativeSyncApi?.cancelHashing(),
|
nativeSyncApi?.cancelHashing(),
|
||||||
workerManagerPatch.dispose().catchError((_) async {
|
workerManagerPatch.dispose().catchError((_) async {
|
||||||
@@ -195,10 +188,15 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
Store.dispose(),
|
Store.dispose(),
|
||||||
|
|
||||||
backgroundSyncManager?.cancel(),
|
backgroundSyncManager?.cancel(),
|
||||||
|
_drift.optimize(allTables: true),
|
||||||
];
|
];
|
||||||
|
|
||||||
await Future.wait(cleanupFutures.nonNulls);
|
await Future.wait(cleanupFutures.nonNulls);
|
||||||
_logger.info("Background worker resources cleaned up");
|
await _drift.close();
|
||||||
|
await _driftLogger.close();
|
||||||
|
|
||||||
|
_ref?.dispose();
|
||||||
|
_ref = null;
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
dPrint(() => 'Failed to cleanup background worker: $error with stack: $stack');
|
dPrint(() => 'Failed to cleanup background worker: $error with stack: $stack');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ class LocalSyncService {
|
|||||||
a.createdAt.isAtSameMomentAs(b.createdAt) &&
|
a.createdAt.isAtSameMomentAs(b.createdAt) &&
|
||||||
a.width == b.width &&
|
a.width == b.width &&
|
||||||
a.height == b.height &&
|
a.height == b.height &&
|
||||||
a.durationInSeconds == b.durationInSeconds;
|
a.durationMs == b.durationMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
final firstAdjustment = a.adjustmentTime?.millisecondsSinceEpoch ?? 0;
|
final firstAdjustment = a.adjustmentTime?.millisecondsSinceEpoch ?? 0;
|
||||||
@@ -346,7 +346,7 @@ class LocalSyncService {
|
|||||||
a.createdAt.isAtSameMomentAs(b.createdAt) &&
|
a.createdAt.isAtSameMomentAs(b.createdAt) &&
|
||||||
a.width == b.width &&
|
a.width == b.width &&
|
||||||
a.height == b.height &&
|
a.height == b.height &&
|
||||||
a.durationInSeconds == b.durationInSeconds &&
|
a.durationMs == b.durationMs &&
|
||||||
a.latitude == b.latitude &&
|
a.latitude == b.latitude &&
|
||||||
a.longitude == b.longitude;
|
a.longitude == b.longitude;
|
||||||
}
|
}
|
||||||
@@ -432,7 +432,7 @@ extension PlatformToLocalAsset on PlatformAsset {
|
|||||||
updatedAt: tryFromSecondsSinceEpoch(updatedAt, isUtc: true) ?? DateTime.timestamp(),
|
updatedAt: tryFromSecondsSinceEpoch(updatedAt, isUtc: true) ?? DateTime.timestamp(),
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
durationInSeconds: durationInSeconds,
|
durationMs: durationMs,
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
playbackStyle: _toPlaybackStyle(playbackStyle),
|
playbackStyle: _toPlaybackStyle(playbackStyle),
|
||||||
|
|||||||
@@ -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(),
|
||||||
durationInSeconds: duration?.toDuration()?.inSeconds ?? 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(),
|
||||||
durationInSeconds: duration?.toDuration()?.inSeconds ?? 0,
|
durationMs: duration,
|
||||||
height: height?.toInt(),
|
height: height?.toInt(),
|
||||||
width: width?.toInt(),
|
width: width?.toInt(),
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user