Merge remote-tracking branch 'origin/main' into keynav_timeline

This commit is contained in:
Min Idzelis 2025-05-08 02:20:25 +00:00
commit 212329aa29
216 changed files with 5067 additions and 4100 deletions
.github/workflows
cli
docker
docs
e2e
i18n
machine-learning
mobile
open-api
server

View file

@ -50,7 +50,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@28deaeda66b76a05916b6923827895f2b14ab387 # v3 uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3
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.
@ -63,7 +63,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@28deaeda66b76a05916b6923827895f2b14ab387 # v3 uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3
# 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
@ -76,6 +76,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@28deaeda66b76a05916b6923827895f2b14ab387 # v3 uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3
with: with:
category: '/language:${{matrix.language}}' category: '/language:${{matrix.language}}'

View file

@ -16,7 +16,7 @@ jobs:
steps: steps:
- name: Generate a token - name: Generate a token
id: generate-token id: generate-token
uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2 uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}

View file

@ -32,7 +32,7 @@ jobs:
steps: steps:
- name: Generate a token - name: Generate a token
id: generate-token id: generate-token
uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2 uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@ -83,7 +83,7 @@ jobs:
steps: steps:
- name: Generate a token - name: Generate a token
id: generate-token id: generate-token
uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2 uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}

View file

@ -118,7 +118,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF file - name: Upload SARIF file
uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3 uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3
with: with:
sarif_file: results.sarif sarif_file: results.sarif
category: zizmor category: zizmor

View file

@ -346,7 +346,7 @@ jobs:
working-directory: ./e2e working-directory: ./e2e
strategy: strategy:
matrix: matrix:
runner: [mich, ubuntu-24.04-arm] runner: [ubuntu-latest, ubuntu-24.04-arm]
steps: steps:
- name: Checkout code - name: Checkout code
@ -394,7 +394,7 @@ jobs:
working-directory: ./e2e working-directory: ./e2e
strategy: strategy:
matrix: matrix:
runner: [mich, ubuntu-24.04-arm] runner: [ubuntu-latest, ubuntu-24.04-arm]
steps: steps:
- name: Checkout code - name: Checkout code
@ -593,8 +593,8 @@ jobs:
echo "Changed files: ${CHANGED_FILES}" echo "Changed files: ${CHANGED_FILES}"
exit 1 exit 1
generated-typeorm-migrations-up-to-date: sql-schema-up-to-date:
name: TypeORM Checks name: SQL Schema Checks
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@ -641,7 +641,7 @@ jobs:
- name: Generate new migrations - name: Generate new migrations
continue-on-error: true continue-on-error: true
run: npm run migrations:generate TestMigration run: npm run migrations:generate src/TestMigration
- name: Find file changes - name: Find file changes
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20 uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20

102
cli/package-lock.json generated
View file

@ -1367,17 +1367,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz",
"integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==", "integrity": "sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.31.1",
"@typescript-eslint/type-utils": "8.31.0", "@typescript-eslint/type-utils": "8.31.1",
"@typescript-eslint/utils": "8.31.0", "@typescript-eslint/utils": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.1",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@ -1397,16 +1397,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.1.tgz",
"integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==", "integrity": "sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.31.1",
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/typescript-estree": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.1",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -1422,14 +1422,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz",
"integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==", "integrity": "sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.0" "@typescript-eslint/visitor-keys": "8.31.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1440,14 +1440,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz",
"integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==", "integrity": "sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/typescript-estree": "8.31.1",
"@typescript-eslint/utils": "8.31.0", "@typescript-eslint/utils": "8.31.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^2.0.1"
}, },
@ -1464,9 +1464,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.1.tgz",
"integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==", "integrity": "sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -1478,14 +1478,14 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz",
"integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==", "integrity": "sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -1531,16 +1531,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.1.tgz",
"integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==", "integrity": "sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.31.1",
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"@typescript-eslint/typescript-estree": "8.31.0" "@typescript-eslint/typescript-estree": "8.31.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1555,13 +1555,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz",
"integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==", "integrity": "sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"eslint-visitor-keys": "^4.2.0" "eslint-visitor-keys": "^4.2.0"
}, },
"engines": { "engines": {
@ -4184,15 +4184,15 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.0.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.1.tgz",
"integrity": "sha512-u+93F0sB0An8WEAPtwxVhFby573E8ckdjwUUQUj9QA4v8JAvgtoDdIyYR3XFwFHq2W1KJ1AurwJCO+w+Y1ixyQ==", "integrity": "sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.31.0", "@typescript-eslint/eslint-plugin": "8.31.1",
"@typescript-eslint/parser": "8.31.0", "@typescript-eslint/parser": "8.31.1",
"@typescript-eslint/utils": "8.31.0" "@typescript-eslint/utils": "8.31.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -4279,9 +4279,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "6.3.3", "version": "6.3.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz",
"integrity": "sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==", "integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View file

@ -116,7 +116,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:8-bookworm@sha256:c855f98e09d558a0d7cc1a4e56473231206a4c54c0114ada9c485b47aeb92ec8 image: docker.io/valkey/valkey:8-bookworm@sha256:4a9f847af90037d59b34cd4d4ad14c6e055f46540cf4ff757aaafb266060fa28
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1

View file

@ -56,7 +56,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:8-bookworm@sha256:c855f98e09d558a0d7cc1a4e56473231206a4c54c0114ada9c485b47aeb92ec8 image: docker.io/valkey/valkey:8-bookworm@sha256:4a9f847af90037d59b34cd4d4ad14c6e055f46540cf4ff757aaafb266060fa28
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
restart: always restart: always
@ -90,7 +90,7 @@ services:
container_name: immich_prometheus container_name: immich_prometheus
ports: ports:
- 9090:9090 - 9090:9090
image: prom/prometheus@sha256:339ce86a59413be18d0e445472891d022725b4803fab609069110205e79fb2f1 image: prom/prometheus@sha256:e2b8aa62b64855956e3ec1e18b4f9387fb6203174a4471936f4662f437f04405
volumes: volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml - ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus - prometheus-data:/prometheus

View file

@ -49,7 +49,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:8-bookworm@sha256:c855f98e09d558a0d7cc1a4e56473231206a4c54c0114ada9c485b47aeb92ec8 image: docker.io/valkey/valkey:8-bookworm@sha256:4a9f847af90037d59b34cd4d4ad14c6e055f46540cf4ff757aaafb266060fa28
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
restart: always restart: always

View file

@ -22,7 +22,7 @@ server {
client_max_body_size 50000M; client_max_body_size 50000M;
# Set headers # Set headers
proxy_set_header Host $http_host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;

View file

@ -80,6 +80,7 @@ Information on the current workers can be found [here](/docs/administration/jobs
| `DB_USERNAME` | Database user | `postgres` | server, database<sup>\*1</sup> | | `DB_USERNAME` | Database user | `postgres` | server, database<sup>\*1</sup> |
| `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_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`pgvector`, `pgvecto.rs`]) | `pgvecto.rs` | server | | `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`pgvector`, `pgvecto.rs`]) | `pgvecto.rs` | 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 |

View file

@ -29,7 +29,7 @@ Download [`docker-compose.yml`](https://github.com/immich-app/immich/releases/la
## Step 2 - Populate the .env file with custom values ## Step 2 - Populate the .env file with custom values
Follow [Step 2 in Docker Compose](./docker-compose#step-2---populate-the-env-file-with-custom-values) for instructions on customizing the `.env` file, and then return back to this guide to continue. Follow [Step 2 in Docker Compose](/docs/install/docker-compose#step-2---populate-the-env-file-with-custom-values) for instructions on customizing the `.env` file, and then return back to this guide to continue.
## Step 3 - Create a new project in Container Manager ## Step 3 - Create a new project in Container Manager

View file

@ -2,6 +2,7 @@ import {
mdiBug, mdiBug,
mdiCalendarToday, mdiCalendarToday,
mdiCrosshairsOff, mdiCrosshairsOff,
mdiCrop,
mdiDatabase, mdiDatabase,
mdiLeadPencil, mdiLeadPencil,
mdiLockOff, mdiLockOff,
@ -22,6 +23,18 @@ const withLanguage = (date: Date) => (language: string) => date.toLocaleDateStri
type Item = Omit<TimelineItem, 'done' | 'getDateLabel'> & { date: Date }; type Item = Omit<TimelineItem, 'done' | 'getDateLabel'> & { date: Date };
const items: Item[] = [ const items: Item[] = [
{
icon: mdiCrop,
iconColor: 'tomato',
title: 'Image dimensions in EXIF metadata are cursed',
description:
'The dimensions in EXIF metadata can be different from the actual dimensions of the image, causing issues with cropping and resizing.',
link: {
url: 'https://github.com/immich-app/immich/pull/17974',
text: '#17974',
},
date: new Date(2025, 5, 5),
},
{ {
icon: mdiMicrosoftWindows, icon: mdiMicrosoftWindows,
iconColor: '#357EC7', iconColor: '#357EC7',

View file

@ -3,50 +3,14 @@
"label": "v1.132.3", "label": "v1.132.3",
"url": "https://v1.132.3.archive.immich.app" "url": "https://v1.132.3.archive.immich.app"
}, },
{
"label": "v1.132.2",
"url": "https://v1.132.2.archive.immich.app"
},
{
"label": "v1.132.1",
"url": "https://v1.132.1.archive.immich.app"
},
{
"label": "v1.132.0",
"url": "https://v1.132.0.archive.immich.app"
},
{ {
"label": "v1.131.3", "label": "v1.131.3",
"url": "https://v1.131.3.archive.immich.app" "url": "https://v1.131.3.archive.immich.app"
}, },
{
"label": "v1.131.2",
"url": "https://v1.131.2.archive.immich.app"
},
{
"label": "v1.131.1",
"url": "https://v1.131.1.archive.immich.app"
},
{
"label": "v1.131.0",
"url": "https://v1.131.0.archive.immich.app"
},
{ {
"label": "v1.130.3", "label": "v1.130.3",
"url": "https://v1.130.3.archive.immich.app" "url": "https://v1.130.3.archive.immich.app"
}, },
{
"label": "v1.130.2",
"url": "https://v1.130.2.archive.immich.app"
},
{
"label": "v1.130.1",
"url": "https://v1.130.1.archive.immich.app"
},
{
"label": "v1.130.0",
"url": "https://v1.130.0.archive.immich.app"
},
{ {
"label": "v1.129.0", "label": "v1.129.0",
"url": "https://v1.129.0.archive.immich.app" "url": "https://v1.129.0.archive.immich.app"
@ -63,46 +27,14 @@
"label": "v1.126.1", "label": "v1.126.1",
"url": "https://v1.126.1.archive.immich.app" "url": "https://v1.126.1.archive.immich.app"
}, },
{
"label": "v1.126.0",
"url": "https://v1.126.0.archive.immich.app"
},
{ {
"label": "v1.125.7", "label": "v1.125.7",
"url": "https://v1.125.7.archive.immich.app" "url": "https://v1.125.7.archive.immich.app"
}, },
{
"label": "v1.125.6",
"url": "https://v1.125.6.archive.immich.app"
},
{
"label": "v1.125.5",
"url": "https://v1.125.5.archive.immich.app"
},
{
"label": "v1.125.3",
"url": "https://v1.125.3.archive.immich.app"
},
{
"label": "v1.125.2",
"url": "https://v1.125.2.archive.immich.app"
},
{
"label": "v1.125.1",
"url": "https://v1.125.1.archive.immich.app"
},
{ {
"label": "v1.124.2", "label": "v1.124.2",
"url": "https://v1.124.2.archive.immich.app" "url": "https://v1.124.2.archive.immich.app"
}, },
{
"label": "v1.124.1",
"url": "https://v1.124.1.archive.immich.app"
},
{
"label": "v1.124.0",
"url": "https://v1.124.0.archive.immich.app"
},
{ {
"label": "v1.123.0", "label": "v1.123.0",
"url": "https://v1.123.0.archive.immich.app" "url": "https://v1.123.0.archive.immich.app"
@ -111,18 +43,6 @@
"label": "v1.122.3", "label": "v1.122.3",
"url": "https://v1.122.3.archive.immich.app" "url": "https://v1.122.3.archive.immich.app"
}, },
{
"label": "v1.122.2",
"url": "https://v1.122.2.archive.immich.app"
},
{
"label": "v1.122.1",
"url": "https://v1.122.1.archive.immich.app"
},
{
"label": "v1.122.0",
"url": "https://v1.122.0.archive.immich.app"
},
{ {
"label": "v1.121.0", "label": "v1.121.0",
"url": "https://v1.121.0.archive.immich.app" "url": "https://v1.121.0.archive.immich.app"
@ -131,34 +51,14 @@
"label": "v1.120.2", "label": "v1.120.2",
"url": "https://v1.120.2.archive.immich.app" "url": "https://v1.120.2.archive.immich.app"
}, },
{
"label": "v1.120.1",
"url": "https://v1.120.1.archive.immich.app"
},
{
"label": "v1.120.0",
"url": "https://v1.120.0.archive.immich.app"
},
{ {
"label": "v1.119.1", "label": "v1.119.1",
"url": "https://v1.119.1.archive.immich.app" "url": "https://v1.119.1.archive.immich.app"
}, },
{
"label": "v1.119.0",
"url": "https://v1.119.0.archive.immich.app"
},
{ {
"label": "v1.118.2", "label": "v1.118.2",
"url": "https://v1.118.2.archive.immich.app" "url": "https://v1.118.2.archive.immich.app"
}, },
{
"label": "v1.118.1",
"url": "https://v1.118.1.archive.immich.app"
},
{
"label": "v1.118.0",
"url": "https://v1.118.0.archive.immich.app"
},
{ {
"label": "v1.117.0", "label": "v1.117.0",
"url": "https://v1.117.0.archive.immich.app" "url": "https://v1.117.0.archive.immich.app"
@ -167,14 +67,6 @@
"label": "v1.116.2", "label": "v1.116.2",
"url": "https://v1.116.2.archive.immich.app" "url": "https://v1.116.2.archive.immich.app"
}, },
{
"label": "v1.116.1",
"url": "https://v1.116.1.archive.immich.app"
},
{
"label": "v1.116.0",
"url": "https://v1.116.0.archive.immich.app"
},
{ {
"label": "v1.115.0", "label": "v1.115.0",
"url": "https://v1.115.0.archive.immich.app" "url": "https://v1.115.0.archive.immich.app"
@ -187,18 +79,10 @@
"label": "v1.113.1", "label": "v1.113.1",
"url": "https://v1.113.1.archive.immich.app" "url": "https://v1.113.1.archive.immich.app"
}, },
{
"label": "v1.113.0",
"url": "https://v1.113.0.archive.immich.app"
},
{ {
"label": "v1.112.1", "label": "v1.112.1",
"url": "https://v1.112.1.archive.immich.app" "url": "https://v1.112.1.archive.immich.app"
}, },
{
"label": "v1.112.0",
"url": "https://v1.112.0.archive.immich.app"
},
{ {
"label": "v1.111.0", "label": "v1.111.0",
"url": "https://v1.111.0.archive.immich.app" "url": "https://v1.111.0.archive.immich.app"
@ -211,14 +95,6 @@
"label": "v1.109.2", "label": "v1.109.2",
"url": "https://v1.109.2.archive.immich.app" "url": "https://v1.109.2.archive.immich.app"
}, },
{
"label": "v1.109.1",
"url": "https://v1.109.1.archive.immich.app"
},
{
"label": "v1.109.0",
"url": "https://v1.109.0.archive.immich.app"
},
{ {
"label": "v1.108.0", "label": "v1.108.0",
"url": "https://v1.108.0.archive.immich.app" "url": "https://v1.108.0.archive.immich.app"
@ -227,38 +103,14 @@
"label": "v1.107.2", "label": "v1.107.2",
"url": "https://v1.107.2.archive.immich.app" "url": "https://v1.107.2.archive.immich.app"
}, },
{
"label": "v1.107.1",
"url": "https://v1.107.1.archive.immich.app"
},
{
"label": "v1.107.0",
"url": "https://v1.107.0.archive.immich.app"
},
{ {
"label": "v1.106.4", "label": "v1.106.4",
"url": "https://v1.106.4.archive.immich.app" "url": "https://v1.106.4.archive.immich.app"
}, },
{
"label": "v1.106.3",
"url": "https://v1.106.3.archive.immich.app"
},
{
"label": "v1.106.2",
"url": "https://v1.106.2.archive.immich.app"
},
{
"label": "v1.106.1",
"url": "https://v1.106.1.archive.immich.app"
},
{ {
"label": "v1.105.1", "label": "v1.105.1",
"url": "https://v1.105.1.archive.immich.app" "url": "https://v1.105.1.archive.immich.app"
}, },
{
"label": "v1.105.0",
"url": "https://v1.105.0.archive.immich.app"
},
{ {
"label": "v1.104.0", "label": "v1.104.0",
"url": "https://v1.104.0.archive.immich.app" "url": "https://v1.104.0.archive.immich.app"
@ -267,26 +119,10 @@
"label": "v1.103.1", "label": "v1.103.1",
"url": "https://v1.103.1.archive.immich.app" "url": "https://v1.103.1.archive.immich.app"
}, },
{
"label": "v1.103.0",
"url": "https://v1.103.0.archive.immich.app"
},
{ {
"label": "v1.102.3", "label": "v1.102.3",
"url": "https://v1.102.3.archive.immich.app" "url": "https://v1.102.3.archive.immich.app"
}, },
{
"label": "v1.102.2",
"url": "https://v1.102.2.archive.immich.app"
},
{
"label": "v1.102.1",
"url": "https://v1.102.1.archive.immich.app"
},
{
"label": "v1.102.0",
"url": "https://v1.102.0.archive.immich.app"
},
{ {
"label": "v1.101.0", "label": "v1.101.0",
"url": "https://v1.101.0.archive.immich.app" "url": "https://v1.101.0.archive.immich.app"

104
e2e/package-lock.json generated
View file

@ -1683,17 +1683,17 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz",
"integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==", "integrity": "sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.31.1",
"@typescript-eslint/type-utils": "8.31.0", "@typescript-eslint/type-utils": "8.31.1",
"@typescript-eslint/utils": "8.31.0", "@typescript-eslint/utils": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.1",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@ -1713,16 +1713,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.1.tgz",
"integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==", "integrity": "sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.31.1",
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/typescript-estree": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.1",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -1738,14 +1738,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz",
"integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==", "integrity": "sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.0" "@typescript-eslint/visitor-keys": "8.31.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1756,14 +1756,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz",
"integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==", "integrity": "sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/typescript-estree": "8.31.1",
"@typescript-eslint/utils": "8.31.0", "@typescript-eslint/utils": "8.31.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^2.0.1"
}, },
@ -1780,9 +1780,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.1.tgz",
"integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==", "integrity": "sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -1794,14 +1794,14 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz",
"integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==", "integrity": "sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -1847,16 +1847,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.1.tgz",
"integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==", "integrity": "sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.31.1",
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"@typescript-eslint/typescript-estree": "8.31.0" "@typescript-eslint/typescript-estree": "8.31.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1871,13 +1871,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz",
"integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==", "integrity": "sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"eslint-visitor-keys": "^4.2.0" "eslint-visitor-keys": "^4.2.0"
}, },
"engines": { "engines": {
@ -5222,14 +5222,14 @@
} }
}, },
"node_modules/pg": { "node_modules/pg": {
"version": "8.15.5", "version": "8.15.6",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.15.5.tgz", "resolved": "https://registry.npmjs.org/pg/-/pg-8.15.6.tgz",
"integrity": "sha512-EpAhHFQc+aH9VfeffWIVC+XXk6lmAhS9W1FxtxcPXs94yxhrI1I6w/zkWfIOII/OkBv3Be04X3xMOj0kQ78l6w==", "integrity": "sha512-yvao7YI3GdmmrslNVsZgx9PfntfWrnXwtR+K/DjI0I/sTKif4Z623um+sjVZ1hk5670B+ODjvHDAckKdjmPTsg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"pg-connection-string": "^2.8.5", "pg-connection-string": "^2.8.5",
"pg-pool": "^3.9.5", "pg-pool": "^3.9.6",
"pg-protocol": "^1.9.5", "pg-protocol": "^1.9.5",
"pg-types": "^2.1.0", "pg-types": "^2.1.0",
"pgpass": "1.x" "pgpass": "1.x"
@ -6747,15 +6747,15 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.0.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.1.tgz",
"integrity": "sha512-u+93F0sB0An8WEAPtwxVhFby573E8ckdjwUUQUj9QA4v8JAvgtoDdIyYR3XFwFHq2W1KJ1AurwJCO+w+Y1ixyQ==", "integrity": "sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.31.0", "@typescript-eslint/eslint-plugin": "8.31.1",
"@typescript-eslint/parser": "8.31.0", "@typescript-eslint/parser": "8.31.1",
"@typescript-eslint/utils": "8.31.0" "@typescript-eslint/utils": "8.31.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"

View file

@ -3,6 +3,7 @@ import {
AssetMediaStatus, AssetMediaStatus,
AssetResponseDto, AssetResponseDto,
AssetTypeEnum, AssetTypeEnum,
AssetVisibility,
getAssetInfo, getAssetInfo,
getMyUser, getMyUser,
LoginResponseDto, LoginResponseDto,
@ -24,7 +25,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`; const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
const ratingAssetFilepath = `${testAssetDir}/metadata/rating/mongolels.jpg`; const ratingAssetFilepath = `${testAssetDir}/metadata/rating/mongolels.jpg`;
const facesAssetFilepath = `${testAssetDir}/metadata/faces/portrait.jpg`; const facesAssetDir = `${testAssetDir}/metadata/faces`;
const readTags = async (bytes: Buffer, filename: string) => { const readTags = async (bytes: Buffer, filename: string) => {
const filepath = join(tempDir, filename); const filepath = join(tempDir, filename);
@ -119,9 +120,9 @@ describe('/asset', () => {
// stats // stats
utils.createAsset(statsUser.accessToken), utils.createAsset(statsUser.accessToken),
utils.createAsset(statsUser.accessToken, { isFavorite: true }), utils.createAsset(statsUser.accessToken, { isFavorite: true }),
utils.createAsset(statsUser.accessToken, { isArchived: true }), utils.createAsset(statsUser.accessToken, { visibility: AssetVisibility.Archive }),
utils.createAsset(statsUser.accessToken, { utils.createAsset(statsUser.accessToken, {
isArchived: true, visibility: AssetVisibility.Archive,
isFavorite: true, isFavorite: true,
assetData: { filename: 'example.mp4' }, assetData: { filename: 'example.mp4' },
}), }),
@ -185,27 +186,19 @@ describe('/asset', () => {
}); });
}); });
it('should get the asset faces', async () => { describe('faces', () => {
const config = await utils.getSystemConfig(admin.accessToken); const metadataFaceTests = [
config.metadata.faces.import = true; {
await updateConfig({ systemConfigDto: config }, { headers: asBearerAuth(admin.accessToken) }); description: 'without orientation',
// asset faces
const facesAsset = await utils.createAsset(admin.accessToken, {
assetData: {
filename: 'portrait.jpg', filename: 'portrait.jpg',
bytes: await readFile(facesAssetFilepath),
}, },
}); {
description: 'adjusting face regions to orientation',
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: facesAsset.id }); filename: 'portrait-orientation-6.jpg',
},
const { status, body } = await request(app) ];
.get(`/assets/${facesAsset.id}`) // should produce same resulting face region coordinates for any orientation
.set('Authorization', `Bearer ${admin.accessToken}`); const expectedFaces = [
expect(status).toBe(200);
expect(body.id).toEqual(facesAsset.id);
expect(body.people).toMatchObject([
{ {
name: 'Marie Curie', name: 'Marie Curie',
birthDate: null, birthDate: null,
@ -240,7 +233,30 @@ describe('/asset', () => {
}, },
], ],
}, },
]); ];
it.each(metadataFaceTests)('should get the asset faces from $filename $description', async ({ filename }) => {
const config = await utils.getSystemConfig(admin.accessToken);
config.metadata.faces.import = true;
await updateConfig({ systemConfigDto: config }, { headers: asBearerAuth(admin.accessToken) });
const facesAsset = await utils.createAsset(admin.accessToken, {
assetData: {
filename,
bytes: await readFile(`${facesAssetDir}/${filename}`),
},
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: facesAsset.id });
const { status, body } = await request(app)
.get(`/assets/${facesAsset.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body.id).toEqual(facesAsset.id);
expect(body.people).toMatchObject(expectedFaces);
});
}); });
it('should work with a shared link', async () => { it('should work with a shared link', async () => {
@ -294,7 +310,7 @@ describe('/asset', () => {
}); });
it('disallows viewing archived assets', async () => { it('disallows viewing archived assets', async () => {
const asset = await utils.createAsset(user1.accessToken, { isArchived: true }); const asset = await utils.createAsset(user1.accessToken, { visibility: AssetVisibility.Archive });
const { status } = await request(app) const { status } = await request(app)
.get(`/assets/${asset.id}`) .get(`/assets/${asset.id}`)
@ -338,7 +354,7 @@ describe('/asset', () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.get('/assets/statistics') .get('/assets/statistics')
.set('Authorization', `Bearer ${statsUser.accessToken}`) .set('Authorization', `Bearer ${statsUser.accessToken}`)
.query({ isArchived: true }); .query({ visibility: AssetVisibility.Archive });
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ images: 1, videos: 1, total: 2 }); expect(body).toEqual({ images: 1, videos: 1, total: 2 });
@ -348,7 +364,7 @@ describe('/asset', () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.get('/assets/statistics') .get('/assets/statistics')
.set('Authorization', `Bearer ${statsUser.accessToken}`) .set('Authorization', `Bearer ${statsUser.accessToken}`)
.query({ isFavorite: true, isArchived: true }); .query({ isFavorite: true, visibility: AssetVisibility.Archive });
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ images: 0, videos: 1, total: 1 }); expect(body).toEqual({ images: 0, videos: 1, total: 1 });
@ -358,7 +374,7 @@ describe('/asset', () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.get('/assets/statistics') .get('/assets/statistics')
.set('Authorization', `Bearer ${statsUser.accessToken}`) .set('Authorization', `Bearer ${statsUser.accessToken}`)
.query({ isFavorite: false, isArchived: false }); .query({ isFavorite: false, visibility: AssetVisibility.Timeline });
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ images: 1, videos: 0, total: 1 }); expect(body).toEqual({ images: 1, videos: 0, total: 1 });
@ -417,20 +433,6 @@ describe('/asset', () => {
}); });
describe('PUT /assets/:id', () => { describe('PUT /assets/:id', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put(`/assets/:${uuidDto.notFound}`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require a valid id', async () => {
const { status, body } = await request(app)
.put(`/assets/${uuidDto.invalid}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
});
it('should require access', async () => { it('should require access', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.put(`/assets/${user2Assets[0].id}`) .put(`/assets/${user2Assets[0].id}`)
@ -458,7 +460,7 @@ describe('/asset', () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.put(`/assets/${user1Assets[0].id}`) .put(`/assets/${user1Assets[0].id}`)
.set('Authorization', `Bearer ${user1.accessToken}`) .set('Authorization', `Bearer ${user1.accessToken}`)
.send({ isArchived: true }); .send({ visibility: AssetVisibility.Archive });
expect(body).toMatchObject({ id: user1Assets[0].id, isArchived: true }); expect(body).toMatchObject({ id: user1Assets[0].id, isArchived: true });
expect(status).toEqual(200); expect(status).toEqual(200);
}); });
@ -1083,31 +1085,21 @@ describe('/asset', () => {
}, },
]; ];
it(`should upload and generate a thumbnail for different file types`, async () => { it.each(tests)(`should upload and generate a thumbnail for different file types`, async ({ input, expected }) => {
// upload in parallel
const assets = await Promise.all(
tests.map(async ({ input }) => {
const filepath = join(testAssetDir, input); const filepath = join(testAssetDir, input);
return utils.createAsset(admin.accessToken, { const response = await utils.createAsset(admin.accessToken, {
assetData: { bytes: await readFile(filepath), filename: basename(filepath) }, assetData: { bytes: await readFile(filepath), filename: basename(filepath) },
}); });
}),
);
for (const { id, status } of assets) { expect(response.status).toBe(AssetMediaStatus.Created);
expect(status).toBe(AssetMediaStatus.Created); const id = response.id;
// longer timeout as the thumbnail generation from full-size raw files can take a while // longer timeout as the thumbnail generation from full-size raw files can take a while
await utils.waitForWebsocketEvent({ event: 'assetUpload', id }); await utils.waitForWebsocketEvent({ event: 'assetUpload', id });
}
for (const [i, { id }] of assets.entries()) {
const { expected } = tests[i];
const asset = await utils.getAssetInfo(admin.accessToken, id); const asset = await utils.getAssetInfo(admin.accessToken, id);
expect(asset.exifInfo).toBeDefined(); expect(asset.exifInfo).toBeDefined();
expect(asset.exifInfo).toMatchObject(expected.exifInfo); expect(asset.exifInfo).toMatchObject(expected.exifInfo);
expect(asset).toMatchObject(expected); expect(asset).toMatchObject(expected);
}
}); });
it('should handle a duplicate', async () => { it('should handle a duplicate', async () => {

View file

@ -19,17 +19,6 @@ describe(`/auth/admin-sign-up`, () => {
expect(body).toEqual(signupResponseDto.admin); expect(body).toEqual(signupResponseDto.admin);
}); });
it('should sign up the admin with a local domain', async () => {
const { status, body } = await request(app)
.post('/auth/admin-sign-up')
.send({ ...signupDto.admin, email: 'admin@local' });
expect(status).toEqual(201);
expect(body).toEqual({
...signupResponseDto.admin,
email: 'admin@local',
});
});
it('should not allow a second admin to sign up', async () => { it('should not allow a second admin to sign up', async () => {
await signUpAdmin({ signUpDto: signupDto.admin }); await signUpAdmin({ signUpDto: signupDto.admin });
@ -57,22 +46,6 @@ describe('/auth/*', () => {
expect(body).toEqual(errorDto.incorrectLogin); expect(body).toEqual(errorDto.incorrectLogin);
}); });
for (const key of Object.keys(loginDto.admin)) {
it(`should not allow null ${key}`, async () => {
const { status, body } = await request(app)
.post('/auth/login')
.send({ ...loginDto.admin, [key]: null });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest());
});
it('should reject an invalid email', async () => {
const { status, body } = await request(app).post('/auth/login').send({ email: [], password });
expect(status).toBe(400);
expect(body).toEqual(errorDto.invalidEmail);
});
}
it('should accept a correct password', async () => { it('should accept a correct password', async () => {
const { status, body, headers } = await request(app).post('/auth/login').send({ email, password }); const { status, body, headers } = await request(app).post('/auth/login').send({ email, password });
expect(status).toBe(201); expect(status).toBe(201);
@ -127,14 +100,6 @@ describe('/auth/*', () => {
}); });
describe('POST /auth/change-password', () => { describe('POST /auth/change-password', () => {
it('should require authentication', async () => {
const { status, body } = await request(app)
.post(`/auth/change-password`)
.send({ password, newPassword: 'Password1234' });
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require the current password', async () => { it('should require the current password', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.post(`/auth/change-password`) .post(`/auth/change-password`)

View file

@ -1,6 +1,5 @@
import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk'; import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk';
import { readFile, writeFile } from 'node:fs/promises'; import { readFile, writeFile } from 'node:fs/promises';
import { errorDto } from 'src/responses';
import { app, tempDir, utils } from 'src/utils'; import { app, tempDir, utils } from 'src/utils';
import request from 'supertest'; import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest'; import { beforeAll, describe, expect, it } from 'vitest';
@ -17,15 +16,6 @@ describe('/download', () => {
}); });
describe('POST /download/info', () => { describe('POST /download/info', () => {
it('should require authentication', async () => {
const { status, body } = await request(app)
.post(`/download/info`)
.send({ assetIds: [asset1.id] });
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should download info', async () => { it('should download info', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.post('/download/info') .post('/download/info')
@ -42,15 +32,6 @@ describe('/download', () => {
}); });
describe('POST /download/archive', () => { describe('POST /download/archive', () => {
it('should require authentication', async () => {
const { status, body } = await request(app)
.post(`/download/archive`)
.send({ assetIds: [asset1.id, asset2.id] });
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should download an archive', async () => { it('should download an archive', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.post('/download/archive') .post('/download/archive')

View file

@ -1,4 +1,4 @@
import { LoginResponseDto } from '@immich/sdk'; import { AssetVisibility, LoginResponseDto } from '@immich/sdk';
import { readFile } from 'node:fs/promises'; import { readFile } from 'node:fs/promises';
import { basename, join } from 'node:path'; import { basename, join } from 'node:path';
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
@ -44,7 +44,7 @@ describe('/map', () => {
it('should get map markers for all non-archived assets', async () => { it('should get map markers for all non-archived assets', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.get('/map/markers') .get('/map/markers')
.query({ isArchived: false }) .query({ visibility: AssetVisibility.Timeline })
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200); expect(status).toBe(200);

View file

@ -1,4 +1,11 @@
import { AssetMediaResponseDto, AssetResponseDto, deleteAssets, LoginResponseDto, updateAsset } from '@immich/sdk'; import {
AssetMediaResponseDto,
AssetResponseDto,
AssetVisibility,
deleteAssets,
LoginResponseDto,
updateAsset,
} from '@immich/sdk';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { readFile } from 'node:fs/promises'; import { readFile } from 'node:fs/promises';
import { join } from 'node:path'; import { join } from 'node:path';
@ -49,7 +56,7 @@ describe('/search', () => {
{ filename: '/formats/motionphoto/samsung-one-ui-6.heic' }, { filename: '/formats/motionphoto/samsung-one-ui-6.heic' },
{ filename: '/formats/motionphoto/samsung-one-ui-5.jpg' }, { filename: '/formats/motionphoto/samsung-one-ui-5.jpg' },
{ filename: '/metadata/gps-position/thompson-springs.jpg', dto: { isArchived: true } }, { filename: '/metadata/gps-position/thompson-springs.jpg', dto: { visibility: AssetVisibility.Archive } },
// used for search suggestions // used for search suggestions
{ filename: '/formats/png/density_plot.png' }, { filename: '/formats/png/density_plot.png' },
@ -171,12 +178,12 @@ describe('/search', () => {
deferred: () => ({ dto: { size: 1, isFavorite: false }, assets: [assetLast] }), deferred: () => ({ dto: { size: 1, isFavorite: false }, assets: [assetLast] }),
}, },
{ {
should: 'should search by isArchived (true)', should: 'should search by visibility (AssetVisibility.Archive)',
deferred: () => ({ dto: { isArchived: true }, assets: [assetSprings] }), deferred: () => ({ dto: { visibility: AssetVisibility.Archive }, assets: [assetSprings] }),
}, },
{ {
should: 'should search by isArchived (false)', should: 'should search by visibility (AssetVisibility.Timeline)',
deferred: () => ({ dto: { size: 1, isArchived: false }, assets: [assetLast] }), deferred: () => ({ dto: { size: 1, visibility: AssetVisibility.Timeline }, assets: [assetLast] }),
}, },
{ {
should: 'should search by type (image)', should: 'should search by type (image)',
@ -185,7 +192,7 @@ describe('/search', () => {
{ {
should: 'should search by type (video)', should: 'should search by type (video)',
deferred: () => ({ deferred: () => ({
dto: { type: 'VIDEO' }, dto: { type: 'VIDEO', visibility: AssetVisibility.Hidden },
assets: [ assets: [
// the three live motion photos // the three live motion photos
{ id: expect.any(String) }, { id: expect.any(String) },
@ -229,13 +236,6 @@ describe('/search', () => {
should: 'should search by takenAfter (no results)', should: 'should search by takenAfter (no results)',
deferred: () => ({ dto: { takenAfter: today.plus({ hour: 1 }).toJSDate() }, assets: [] }), deferred: () => ({ dto: { takenAfter: today.plus({ hour: 1 }).toJSDate() }, assets: [] }),
}, },
// {
// should: 'should search by originalPath',
// deferred: () => ({
// dto: { originalPath: asset1.originalPath },
// assets: [asset1],
// }),
// },
{ {
should: 'should search by originalFilename', should: 'should search by originalFilename',
deferred: () => ({ deferred: () => ({
@ -265,7 +265,7 @@ describe('/search', () => {
deferred: () => ({ deferred: () => ({
dto: { dto: {
city: '', city: '',
isVisible: true, visibility: AssetVisibility.Timeline,
includeNull: true, includeNull: true,
}, },
assets: [assetLast], assets: [assetLast],
@ -276,7 +276,7 @@ describe('/search', () => {
deferred: () => ({ deferred: () => ({
dto: { dto: {
city: null, city: null,
isVisible: true, visibility: AssetVisibility.Timeline,
includeNull: true, includeNull: true,
}, },
assets: [assetLast], assets: [assetLast],
@ -297,7 +297,7 @@ describe('/search', () => {
deferred: () => ({ deferred: () => ({
dto: { dto: {
state: '', state: '',
isVisible: true, visibility: AssetVisibility.Timeline,
withExif: true, withExif: true,
includeNull: true, includeNull: true,
}, },
@ -309,7 +309,7 @@ describe('/search', () => {
deferred: () => ({ deferred: () => ({
dto: { dto: {
state: null, state: null,
isVisible: true, visibility: AssetVisibility.Timeline,
includeNull: true, includeNull: true,
}, },
assets: [assetLast, assetNotocactus], assets: [assetLast, assetNotocactus],
@ -330,7 +330,7 @@ describe('/search', () => {
deferred: () => ({ deferred: () => ({
dto: { dto: {
country: '', country: '',
isVisible: true, visibility: AssetVisibility.Timeline,
includeNull: true, includeNull: true,
}, },
assets: [assetLast], assets: [assetLast],
@ -341,7 +341,7 @@ describe('/search', () => {
deferred: () => ({ deferred: () => ({
dto: { dto: {
country: null, country: null,
isVisible: true, visibility: AssetVisibility.Timeline,
includeNull: true, includeNull: true,
}, },
assets: [assetLast], assets: [assetLast],

View file

@ -1,4 +1,4 @@
import { AssetMediaResponseDto, LoginResponseDto, SharedLinkType, TimeBucketSize } from '@immich/sdk'; import { AssetMediaResponseDto, AssetVisibility, LoginResponseDto, SharedLinkType, TimeBucketSize } from '@immich/sdk';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { createUserDto } from 'src/fixtures'; import { createUserDto } from 'src/fixtures';
import { errorDto } from 'src/responses'; import { errorDto } from 'src/responses';
@ -104,7 +104,7 @@ describe('/timeline', () => {
const req1 = await request(app) const req1 = await request(app)
.get('/timeline/buckets') .get('/timeline/buckets')
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`) .set('Authorization', `Bearer ${timeBucketUser.accessToken}`)
.query({ size: TimeBucketSize.Month, withPartners: true, isArchived: true }); .query({ size: TimeBucketSize.Month, withPartners: true, visibility: AssetVisibility.Archive });
expect(req1.status).toBe(400); expect(req1.status).toBe(400);
expect(req1.body).toEqual(errorDto.badRequest()); expect(req1.body).toEqual(errorDto.badRequest());
@ -112,7 +112,7 @@ describe('/timeline', () => {
const req2 = await request(app) const req2 = await request(app)
.get('/timeline/buckets') .get('/timeline/buckets')
.set('Authorization', `Bearer ${user.accessToken}`) .set('Authorization', `Bearer ${user.accessToken}`)
.query({ size: TimeBucketSize.Month, withPartners: true, isArchived: undefined }); .query({ size: TimeBucketSize.Month, withPartners: true, visibility: undefined });
expect(req2.status).toBe(400); expect(req2.status).toBe(400);
expect(req2.body).toEqual(errorDto.badRequest()); expect(req2.body).toEqual(errorDto.badRequest());

View file

@ -3,6 +3,7 @@ import {
AssetMediaCreateDto, AssetMediaCreateDto,
AssetMediaResponseDto, AssetMediaResponseDto,
AssetResponseDto, AssetResponseDto,
AssetVisibility,
CheckExistingAssetsDto, CheckExistingAssetsDto,
CreateAlbumDto, CreateAlbumDto,
CreateLibraryDto, CreateLibraryDto,
@ -429,7 +430,10 @@ export const utils = {
}, },
archiveAssets: (accessToken: string, ids: string[]) => archiveAssets: (accessToken: string, ids: string[]) =>
updateAssets({ assetBulkUpdateDto: { ids, isArchived: true } }, { headers: asBearerAuth(accessToken) }), updateAssets(
{ assetBulkUpdateDto: { ids, visibility: AssetVisibility.Archive } },
{ headers: asBearerAuth(accessToken) },
),
deleteAssets: (accessToken: string, ids: string[]) => deleteAssets: (accessToken: string, ids: string[]) =>
deleteAssets({ assetBulkDeleteDto: { ids } }, { headers: asBearerAuth(accessToken) }), deleteAssets({ assetBulkDeleteDto: { ids } }, { headers: asBearerAuth(accessToken) }),

View file

@ -47,15 +47,13 @@ test.describe('Shared Links', () => {
await page.locator(`[data-asset-id="${asset.id}"]`).hover(); await page.locator(`[data-asset-id="${asset.id}"]`).hover();
await page.waitForSelector('[data-group] svg'); await page.waitForSelector('[data-group] svg');
await page.getByRole('checkbox').click(); await page.getByRole('checkbox').click();
await page.getByRole('button', { name: 'Download' }).click(); await Promise.all([page.waitForEvent('download'), page.getByRole('button', { name: 'Download' }).click()]);
await page.waitForEvent('download');
}); });
test('download all from shared link', async ({ page }) => { test('download all from shared link', async ({ page }) => {
await page.goto(`/share/${sharedLink.key}`); await page.goto(`/share/${sharedLink.key}`);
await page.getByRole('heading', { name: 'Test Album' }).waitFor(); await page.getByRole('heading', { name: 'Test Album' }).waitFor();
await page.getByRole('button', { name: 'Download' }).click(); await Promise.all([page.waitForEvent('download'), page.getByRole('button', { name: 'Download' }).click()]);
await page.waitForEvent('download');
}); });
test('enter password for a shared link', async ({ page }) => { test('enter password for a shared link', async ({ page }) => {

@ -1 +1 @@
Subproject commit 9e3b964b080dca6f035b29b86e66454ae8aeda78 Subproject commit 8885d6d01c12242785b6ea68f4a277334f60bc90

View file

@ -369,7 +369,7 @@
"advanced": "Advanced", "advanced": "Advanced",
"advanced_settings_enable_alternate_media_filter_subtitle": "Use this option to filter media during sync based on alternate criteria. Only try this if you have issues with the app detecting all albums.", "advanced_settings_enable_alternate_media_filter_subtitle": "Use this option to filter media during sync based on alternate criteria. Only try this if you have issues with the app detecting all albums.",
"advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Use alternate device album sync filter", "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Use alternate device album sync filter",
"advanced_settings_log_level_title": "Log level: {}", "advanced_settings_log_level_title": "Log level: {level}",
"advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.", "advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.",
"advanced_settings_prefer_remote_title": "Prefer remote images", "advanced_settings_prefer_remote_title": "Prefer remote images",
"advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request", "advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request",
@ -400,9 +400,9 @@
"album_remove_user_confirmation": "Are you sure you want to remove {user}?", "album_remove_user_confirmation": "Are you sure you want to remove {user}?",
"album_share_no_users": "Looks like you have shared this album with all users or you don't have any user to share with.", "album_share_no_users": "Looks like you have shared this album with all users or you don't have any user to share with.",
"album_thumbnail_card_item": "1 item", "album_thumbnail_card_item": "1 item",
"album_thumbnail_card_items": "{} items", "album_thumbnail_card_items": "{count} items",
"album_thumbnail_card_shared": " · Shared", "album_thumbnail_card_shared": " · Shared",
"album_thumbnail_shared_by": "Shared by {}", "album_thumbnail_shared_by": "Shared by {user}",
"album_updated": "Album updated", "album_updated": "Album updated",
"album_updated_setting_description": "Receive an email notification when a shared album has new assets", "album_updated_setting_description": "Receive an email notification when a shared album has new assets",
"album_user_left": "Left {album}", "album_user_left": "Left {album}",
@ -440,7 +440,7 @@
"archive": "Archive", "archive": "Archive",
"archive_or_unarchive_photo": "Archive or unarchive photo", "archive_or_unarchive_photo": "Archive or unarchive photo",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
"archive_page_title": "Archive ({})", "archive_page_title": "Archive ({count})",
"archive_size": "Archive size", "archive_size": "Archive size",
"archive_size_description": "Configure the archive size for downloads (in GiB)", "archive_size_description": "Configure the archive size for downloads (in GiB)",
"archived": "Archived", "archived": "Archived",
@ -477,18 +477,18 @@
"assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album", "assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album",
"assets_added_to_name_count": "Added {count, plural, one {# asset} other {# assets}} to {hasName, select, true {<b>{name}</b>} other {new album}}", "assets_added_to_name_count": "Added {count, plural, one {# asset} other {# assets}} to {hasName, select, true {<b>{name}</b>} other {new album}}",
"assets_count": "{count, plural, one {# asset} other {# assets}}", "assets_count": "{count, plural, one {# asset} other {# assets}}",
"assets_deleted_permanently": "{} asset(s) deleted permanently", "assets_deleted_permanently": "{count} asset(s) deleted permanently",
"assets_deleted_permanently_from_server": "{} asset(s) deleted permanently from the Immich server", "assets_deleted_permanently_from_server": "{count} asset(s) deleted permanently from the Immich server",
"assets_moved_to_trash_count": "Moved {count, plural, one {# asset} other {# assets}} to trash", "assets_moved_to_trash_count": "Moved {count, plural, one {# asset} other {# assets}} to trash",
"assets_permanently_deleted_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}", "assets_permanently_deleted_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}",
"assets_removed_count": "Removed {count, plural, one {# asset} other {# assets}}", "assets_removed_count": "Removed {count, plural, one {# asset} other {# assets}}",
"assets_removed_permanently_from_device": "{} asset(s) removed permanently from your device", "assets_removed_permanently_from_device": "{count} asset(s) removed permanently from your device",
"assets_restore_confirmation": "Are you sure you want to restore all your trashed assets? You cannot undo this action! Note that any offline assets cannot be restored this way.", "assets_restore_confirmation": "Are you sure you want to restore all your trashed assets? You cannot undo this action! Note that any offline assets cannot be restored this way.",
"assets_restored_count": "Restored {count, plural, one {# asset} other {# assets}}", "assets_restored_count": "Restored {count, plural, one {# asset} other {# assets}}",
"assets_restored_successfully": "{} asset(s) restored successfully", "assets_restored_successfully": "{count} asset(s) restored successfully",
"assets_trashed": "{} asset(s) trashed", "assets_trashed": "{count} asset(s) trashed",
"assets_trashed_count": "Trashed {count, plural, one {# asset} other {# assets}}", "assets_trashed_count": "Trashed {count, plural, one {# asset} other {# assets}}",
"assets_trashed_from_server": "{} asset(s) trashed from the Immich server", "assets_trashed_from_server": "{count} asset(s) trashed from the Immich server",
"assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} already part of the album", "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} already part of the album",
"authorized_devices": "Authorized Devices", "authorized_devices": "Authorized Devices",
"automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere", "automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere",
@ -497,7 +497,7 @@
"back_close_deselect": "Back, close, or deselect", "back_close_deselect": "Back, close, or deselect",
"background_location_permission": "Background location permission", "background_location_permission": "Background location permission",
"background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name", "background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name",
"backup_album_selection_page_albums_device": "Albums on device ({})", "backup_album_selection_page_albums_device": "Albums on device ({count})",
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude", "backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.", "backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
"backup_album_selection_page_select_albums": "Select albums", "backup_album_selection_page_select_albums": "Select albums",
@ -506,11 +506,11 @@
"backup_all": "All", "backup_all": "All",
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…", "backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…", "backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
"backup_background_service_current_upload_notification": "Uploading {}", "backup_background_service_current_upload_notification": "Uploading {filename}",
"backup_background_service_default_notification": "Checking for new assets…", "backup_background_service_default_notification": "Checking for new assets…",
"backup_background_service_error_title": "Backup error", "backup_background_service_error_title": "Backup error",
"backup_background_service_in_progress_notification": "Backing up your assets…", "backup_background_service_in_progress_notification": "Backing up your assets…",
"backup_background_service_upload_failure_notification": "Failed to upload {}", "backup_background_service_upload_failure_notification": "Failed to upload {filename}",
"backup_controller_page_albums": "Backup Albums", "backup_controller_page_albums": "Backup Albums",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.", "backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled", "backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
@ -521,7 +521,7 @@
"backup_controller_page_background_battery_info_title": "Battery optimizations", "backup_controller_page_background_battery_info_title": "Battery optimizations",
"backup_controller_page_background_charging": "Only while charging", "backup_controller_page_background_charging": "Only while charging",
"backup_controller_page_background_configure_error": "Failed to configure the background service", "backup_controller_page_background_configure_error": "Failed to configure the background service",
"backup_controller_page_background_delay": "Delay new assets backup: {}", "backup_controller_page_background_delay": "Delay new assets backup: {duration}",
"backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app", "backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
"backup_controller_page_background_is_off": "Automatic background backup is off", "backup_controller_page_background_is_off": "Automatic background backup is off",
"backup_controller_page_background_is_on": "Automatic background backup is on", "backup_controller_page_background_is_on": "Automatic background backup is on",
@ -531,12 +531,12 @@
"backup_controller_page_backup": "Backup", "backup_controller_page_backup": "Backup",
"backup_controller_page_backup_selected": "Selected: ", "backup_controller_page_backup_selected": "Selected: ",
"backup_controller_page_backup_sub": "Backed up photos and videos", "backup_controller_page_backup_sub": "Backed up photos and videos",
"backup_controller_page_created": "Created on: {}", "backup_controller_page_created": "Created on: {date}",
"backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.", "backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.",
"backup_controller_page_excluded": "Excluded: ", "backup_controller_page_excluded": "Excluded: ",
"backup_controller_page_failed": "Failed ({})", "backup_controller_page_failed": "Failed ({count})",
"backup_controller_page_filename": "File name: {} [{}]", "backup_controller_page_filename": "File name: {filename} [{size}]",
"backup_controller_page_id": "ID: {}", "backup_controller_page_id": "ID: {id}",
"backup_controller_page_info": "Backup Information", "backup_controller_page_info": "Backup Information",
"backup_controller_page_none_selected": "None selected", "backup_controller_page_none_selected": "None selected",
"backup_controller_page_remainder": "Remainder", "backup_controller_page_remainder": "Remainder",
@ -545,7 +545,7 @@
"backup_controller_page_start_backup": "Start Backup", "backup_controller_page_start_backup": "Start Backup",
"backup_controller_page_status_off": "Automatic foreground backup is off", "backup_controller_page_status_off": "Automatic foreground backup is off",
"backup_controller_page_status_on": "Automatic foreground backup is on", "backup_controller_page_status_on": "Automatic foreground backup is on",
"backup_controller_page_storage_format": "{} of {} used", "backup_controller_page_storage_format": "{used} of {total} used",
"backup_controller_page_to_backup": "Albums to be backed up", "backup_controller_page_to_backup": "Albums to be backed up",
"backup_controller_page_total_sub": "All unique photos and videos from selected albums", "backup_controller_page_total_sub": "All unique photos and videos from selected albums",
"backup_controller_page_turn_off": "Turn off foreground backup", "backup_controller_page_turn_off": "Turn off foreground backup",
@ -570,21 +570,21 @@
"bulk_keep_duplicates_confirmation": "Are you sure you want to keep {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will resolve all duplicate groups without deleting anything.", "bulk_keep_duplicates_confirmation": "Are you sure you want to keep {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will resolve all duplicate groups without deleting anything.",
"bulk_trash_duplicates_confirmation": "Are you sure you want to bulk trash {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will keep the largest asset of each group and trash all other duplicates.", "bulk_trash_duplicates_confirmation": "Are you sure you want to bulk trash {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will keep the largest asset of each group and trash all other duplicates.",
"buy": "Purchase Immich", "buy": "Purchase Immich",
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)", "cache_settings_album_thumbnails": "Library page thumbnails ({count} assets)",
"cache_settings_clear_cache_button": "Clear cache", "cache_settings_clear_cache_button": "Clear cache",
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.", "cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
"cache_settings_duplicated_assets_clear_button": "CLEAR", "cache_settings_duplicated_assets_clear_button": "CLEAR",
"cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app", "cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app",
"cache_settings_duplicated_assets_title": "Duplicated Assets ({})", "cache_settings_duplicated_assets_title": "Duplicated Assets ({count})",
"cache_settings_image_cache_size": "Image cache size ({} assets)", "cache_settings_image_cache_size": "Image cache size ({count} assets)",
"cache_settings_statistics_album": "Library thumbnails", "cache_settings_statistics_album": "Library thumbnails",
"cache_settings_statistics_assets": "{} assets ({})", "cache_settings_statistics_assets": "{count} assets ({size})",
"cache_settings_statistics_full": "Full images", "cache_settings_statistics_full": "Full images",
"cache_settings_statistics_shared": "Shared album thumbnails", "cache_settings_statistics_shared": "Shared album thumbnails",
"cache_settings_statistics_thumbnail": "Thumbnails", "cache_settings_statistics_thumbnail": "Thumbnails",
"cache_settings_statistics_title": "Cache usage", "cache_settings_statistics_title": "Cache usage",
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application", "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)", "cache_settings_thumbnail_size": "Thumbnail cache size ({count} assets)",
"cache_settings_tile_subtitle": "Control the local storage behaviour", "cache_settings_tile_subtitle": "Control the local storage behaviour",
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
@ -654,7 +654,7 @@
"contain": "Contain", "contain": "Contain",
"context": "Context", "context": "Context",
"continue": "Continue", "continue": "Continue",
"control_bottom_app_bar_album_info_shared": "{} items · Shared", "control_bottom_app_bar_album_info_shared": "{count} items · Shared",
"control_bottom_app_bar_create_new_album": "Create new album", "control_bottom_app_bar_create_new_album": "Create new album",
"control_bottom_app_bar_delete_from_immich": "Delete from Immich", "control_bottom_app_bar_delete_from_immich": "Delete from Immich",
"control_bottom_app_bar_delete_from_local": "Delete from device", "control_bottom_app_bar_delete_from_local": "Delete from device",
@ -763,7 +763,7 @@
"download_enqueue": "Download enqueued", "download_enqueue": "Download enqueued",
"download_error": "Download Error", "download_error": "Download Error",
"download_failed": "Download failed", "download_failed": "Download failed",
"download_filename": "file: {}", "download_filename": "file: {filename}",
"download_finished": "Download finished", "download_finished": "Download finished",
"download_include_embedded_motion_videos": "Embedded videos", "download_include_embedded_motion_videos": "Embedded videos",
"download_include_embedded_motion_videos_description": "Include videos embedded in motion photos as a separate file", "download_include_embedded_motion_videos_description": "Include videos embedded in motion photos as a separate file",
@ -819,7 +819,7 @@
"error_change_sort_album": "Failed to change album sort order", "error_change_sort_album": "Failed to change album sort order",
"error_delete_face": "Error deleting face from asset", "error_delete_face": "Error deleting face from asset",
"error_loading_image": "Error loading image", "error_loading_image": "Error loading image",
"error_saving_image": "Error: {}", "error_saving_image": "Error: {error}",
"error_title": "Error - Something went wrong", "error_title": "Error - Something went wrong",
"errors": { "errors": {
"cannot_navigate_next_asset": "Cannot navigate to the next asset", "cannot_navigate_next_asset": "Cannot navigate to the next asset",
@ -955,10 +955,10 @@
"exif_bottom_sheet_location": "LOCATION", "exif_bottom_sheet_location": "LOCATION",
"exif_bottom_sheet_people": "PEOPLE", "exif_bottom_sheet_people": "PEOPLE",
"exif_bottom_sheet_person_add_person": "Add name", "exif_bottom_sheet_person_add_person": "Add name",
"exif_bottom_sheet_person_age": "Age {}", "exif_bottom_sheet_person_age": "Age {age}",
"exif_bottom_sheet_person_age_months": "Age {} months", "exif_bottom_sheet_person_age_months": "Age {months} months",
"exif_bottom_sheet_person_age_year_months": "Age 1 year, {} months", "exif_bottom_sheet_person_age_year_months": "Age 1 year, {months} months",
"exif_bottom_sheet_person_age_years": "Age {}", "exif_bottom_sheet_person_age_years": "Age {years}",
"exit_slideshow": "Exit Slideshow", "exit_slideshow": "Exit Slideshow",
"expand_all": "Expand all", "expand_all": "Expand all",
"experimental_settings_new_asset_list_subtitle": "Work in progress", "experimental_settings_new_asset_list_subtitle": "Work in progress",
@ -1173,8 +1173,8 @@
"manage_your_devices": "Manage your logged-in devices", "manage_your_devices": "Manage your logged-in devices",
"manage_your_oauth_connection": "Manage your OAuth connection", "manage_your_oauth_connection": "Manage your OAuth connection",
"map": "Map", "map": "Map",
"map_assets_in_bound": "{} photo", "map_assets_in_bound": "{count} photo",
"map_assets_in_bounds": "{} photos", "map_assets_in_bounds": "{count} photos",
"map_cannot_get_user_location": "Cannot get user's location", "map_cannot_get_user_location": "Cannot get user's location",
"map_location_dialog_yes": "Yes", "map_location_dialog_yes": "Yes",
"map_location_picker_page_use_location": "Use this location", "map_location_picker_page_use_location": "Use this location",
@ -1188,9 +1188,9 @@
"map_settings": "Map settings", "map_settings": "Map settings",
"map_settings_dark_mode": "Dark mode", "map_settings_dark_mode": "Dark mode",
"map_settings_date_range_option_day": "Past 24 hours", "map_settings_date_range_option_day": "Past 24 hours",
"map_settings_date_range_option_days": "Past {} days", "map_settings_date_range_option_days": "Past {days} days",
"map_settings_date_range_option_year": "Past year", "map_settings_date_range_option_year": "Past year",
"map_settings_date_range_option_years": "Past {} years", "map_settings_date_range_option_years": "Past {years} years",
"map_settings_dialog_title": "Map Settings", "map_settings_dialog_title": "Map Settings",
"map_settings_include_show_archived": "Include Archived", "map_settings_include_show_archived": "Include Archived",
"map_settings_include_show_partners": "Include Partners", "map_settings_include_show_partners": "Include Partners",
@ -1209,7 +1209,7 @@
"memories_start_over": "Start Over", "memories_start_over": "Start Over",
"memories_swipe_to_close": "Swipe up to close", "memories_swipe_to_close": "Swipe up to close",
"memories_year_ago": "A year ago", "memories_year_ago": "A year ago",
"memories_years_ago": "{} years ago", "memories_years_ago": "{years} years ago",
"memory": "Memory", "memory": "Memory",
"memory_lane_title": "Memory Lane {title}", "memory_lane_title": "Memory Lane {title}",
"menu": "Menu", "menu": "Menu",
@ -1316,7 +1316,7 @@
"partner_page_partner_add_failed": "Failed to add partner", "partner_page_partner_add_failed": "Failed to add partner",
"partner_page_select_partner": "Select partner", "partner_page_select_partner": "Select partner",
"partner_page_shared_to_title": "Shared to", "partner_page_shared_to_title": "Shared to",
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.", "partner_page_stop_sharing_content": "{partner} will no longer be able to access your photos.",
"partner_sharing": "Partner Sharing", "partner_sharing": "Partner Sharing",
"partners": "Partners", "partners": "Partners",
"password": "Password", "password": "Password",
@ -1604,12 +1604,12 @@
"setting_languages_apply": "Apply", "setting_languages_apply": "Apply",
"setting_languages_subtitle": "Change the app's language", "setting_languages_subtitle": "Change the app's language",
"setting_languages_title": "Languages", "setting_languages_title": "Languages",
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}", "setting_notifications_notify_failures_grace_period": "Notify background backup failures: {duration}",
"setting_notifications_notify_hours": "{} hours", "setting_notifications_notify_hours": "{count} hours",
"setting_notifications_notify_immediately": "immediately", "setting_notifications_notify_immediately": "immediately",
"setting_notifications_notify_minutes": "{} minutes", "setting_notifications_notify_minutes": "{count} minutes",
"setting_notifications_notify_never": "never", "setting_notifications_notify_never": "never",
"setting_notifications_notify_seconds": "{} seconds", "setting_notifications_notify_seconds": "{count} seconds",
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset", "setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
"setting_notifications_single_progress_title": "Show background backup detail progress", "setting_notifications_single_progress_title": "Show background backup detail progress",
"setting_notifications_subtitle": "Adjust your notification preferences", "setting_notifications_subtitle": "Adjust your notification preferences",
@ -1623,7 +1623,7 @@
"settings_saved": "Settings saved", "settings_saved": "Settings saved",
"share": "Share", "share": "Share",
"share_add_photos": "Add photos", "share_add_photos": "Add photos",
"share_assets_selected": "{} selected", "share_assets_selected": "{count} selected",
"share_dialog_preparing": "Preparing...", "share_dialog_preparing": "Preparing...",
"shared": "Shared", "shared": "Shared",
"shared_album_activities_input_disable": "Comment is disabled", "shared_album_activities_input_disable": "Comment is disabled",
@ -1637,32 +1637,32 @@
"shared_by_user": "Shared by {user}", "shared_by_user": "Shared by {user}",
"shared_by_you": "Shared by you", "shared_by_you": "Shared by you",
"shared_from_partner": "Photos from {partner}", "shared_from_partner": "Photos from {partner}",
"shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_intent_upload_button_progress_text": "{current} / {total} Uploaded",
"shared_link_app_bar_title": "Shared Links", "shared_link_app_bar_title": "Shared Links",
"shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_copied_massage": "Copied to clipboard",
"shared_link_clipboard_text": "Link: {}\nPassword: {}", "shared_link_clipboard_text": "Link: {link}\nPassword: {password}",
"shared_link_create_error": "Error while creating shared link", "shared_link_create_error": "Error while creating shared link",
"shared_link_edit_description_hint": "Enter the share description", "shared_link_edit_description_hint": "Enter the share description",
"shared_link_edit_expire_after_option_day": "1 day", "shared_link_edit_expire_after_option_day": "1 day",
"shared_link_edit_expire_after_option_days": "{} days", "shared_link_edit_expire_after_option_days": "{count} days",
"shared_link_edit_expire_after_option_hour": "1 hour", "shared_link_edit_expire_after_option_hour": "1 hour",
"shared_link_edit_expire_after_option_hours": "{} hours", "shared_link_edit_expire_after_option_hours": "{count} hours",
"shared_link_edit_expire_after_option_minute": "1 minute", "shared_link_edit_expire_after_option_minute": "1 minute",
"shared_link_edit_expire_after_option_minutes": "{} minutes", "shared_link_edit_expire_after_option_minutes": "{count} minutes",
"shared_link_edit_expire_after_option_months": "{} months", "shared_link_edit_expire_after_option_months": "{count} months",
"shared_link_edit_expire_after_option_year": "{} year", "shared_link_edit_expire_after_option_year": "{count} year",
"shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_password_hint": "Enter the share password",
"shared_link_edit_submit_button": "Update link", "shared_link_edit_submit_button": "Update link",
"shared_link_error_server_url_fetch": "Cannot fetch the server url", "shared_link_error_server_url_fetch": "Cannot fetch the server url",
"shared_link_expires_day": "Expires in {} day", "shared_link_expires_day": "Expires in {count} day",
"shared_link_expires_days": "Expires in {} days", "shared_link_expires_days": "Expires in {count} days",
"shared_link_expires_hour": "Expires in {} hour", "shared_link_expires_hour": "Expires in {count} hour",
"shared_link_expires_hours": "Expires in {} hours", "shared_link_expires_hours": "Expires in {count} hours",
"shared_link_expires_minute": "Expires in {} minute", "shared_link_expires_minute": "Expires in {count} minute",
"shared_link_expires_minutes": "Expires in {} minutes", "shared_link_expires_minutes": "Expires in {count} minutes",
"shared_link_expires_never": "Expires ∞", "shared_link_expires_never": "Expires ∞",
"shared_link_expires_second": "Expires in {} second", "shared_link_expires_second": "Expires in {count} second",
"shared_link_expires_seconds": "Expires in {} seconds", "shared_link_expires_seconds": "Expires in {count} seconds",
"shared_link_individual_shared": "Individual shared", "shared_link_individual_shared": "Individual shared",
"shared_link_info_chip_metadata": "EXIF", "shared_link_info_chip_metadata": "EXIF",
"shared_link_manage_links": "Manage Shared links", "shared_link_manage_links": "Manage Shared links",
@ -1763,7 +1763,7 @@
"theme_selection": "Theme selection", "theme_selection": "Theme selection",
"theme_selection_description": "Automatically set the theme to light or dark based on your browser's system preference", "theme_selection_description": "Automatically set the theme to light or dark based on your browser's system preference",
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles", "theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})", "theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({count})",
"theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.", "theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.",
"theme_setting_colorful_interface_title": "Colorful interface", "theme_setting_colorful_interface_title": "Colorful interface",
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer", "theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
@ -1798,11 +1798,11 @@
"trash_no_results_message": "Trashed photos and videos will show up here.", "trash_no_results_message": "Trashed photos and videos will show up here.",
"trash_page_delete_all": "Delete All", "trash_page_delete_all": "Delete All",
"trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich",
"trash_page_info": "Trashed items will be permanently deleted after {} days", "trash_page_info": "Trashed items will be permanently deleted after {days} days",
"trash_page_no_assets": "No trashed assets", "trash_page_no_assets": "No trashed assets",
"trash_page_restore_all": "Restore All", "trash_page_restore_all": "Restore All",
"trash_page_select_assets_btn": "Select assets", "trash_page_select_assets_btn": "Select assets",
"trash_page_title": "Trash ({})", "trash_page_title": "Trash ({count})",
"trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.", "trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.",
"type": "Type", "type": "Type",
"unarchive": "Unarchive", "unarchive": "Unarchive",
@ -1840,7 +1840,7 @@
"upload_status_errors": "Errors", "upload_status_errors": "Errors",
"upload_status_uploaded": "Uploaded", "upload_status_uploaded": "Uploaded",
"upload_success": "Upload success, refresh the page to see new upload assets.", "upload_success": "Upload success, refresh the page to see new upload assets.",
"upload_to_immich": "Upload to Immich ({})", "upload_to_immich": "Upload to Immich ({count})",
"uploading": "Uploading", "uploading": "Uploading",
"url": "URL", "url": "URL",
"usage": "Usage", "usage": "Usage",

1
i18n/ml.json Normal file
View file

@ -0,0 +1 @@
{}

2788
machine-learning/uv.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,13 +3,13 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart'; import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart';
import 'package:immich_mobile/providers/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline.provider.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
@RoutePage() @RoutePage()
class AlbumAssetSelectionPage extends HookConsumerWidget { class AlbumAssetSelectionPage extends HookConsumerWidget {
@ -59,7 +59,7 @@ class AlbumAssetSelectionPage extends HookConsumerWidget {
: const Text( : const Text(
'share_assets_selected', 'share_assets_selected',
style: TextStyle(fontSize: 18), style: TextStyle(fontSize: 18),
).tr(args: [selected.value.length.toString()]), ).tr(namedArgs: {'count': selected.value.length.toString()}),
centerTitle: false, centerTitle: false,
actions: [ actions: [
if (selected.value.isNotEmpty || canDeselect) if (selected.value.isNotEmpty || canDeselect)

View file

@ -229,11 +229,13 @@ class AlbumsPage extends HookConsumerWidget {
), ),
subtitle: sorted[index].ownerId != null subtitle: sorted[index].ownerId != null
? Text( ? Text(
'${(sorted[index].assetCount == 1 ? 'album_thumbnail_card_item'.tr( '${(sorted[index].assetCount == 1 ? 'album_thumbnail_card_item'.tr() : 'album_thumbnail_card_items'.tr(
args: ['${sorted[index].assetCount}'], namedArgs: {
) : 'album_thumbnail_card_items'.tr( 'count': sorted[index]
args: ['${sorted[index].assetCount}'], .assetCount
))} ${sorted[index].ownerId != userId ? 'album_thumbnail_shared_by'.tr(args: [sorted[index].ownerName!]) : 'owned'.tr()}', .toString(),
},
))} ${sorted[index].ownerId != userId ? 'album_thumbnail_shared_by'.tr(namedArgs: {'user': sorted[index].ownerName!}) : 'owned'.tr()}',
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: style:
context.textTheme.bodyMedium?.copyWith( context.textTheme.bodyMedium?.copyWith(

View file

@ -214,13 +214,13 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
ListTile( ListTile(
title: Text( title: Text(
"backup_album_selection_page_albums_device".tr( "backup_album_selection_page_albums_device".tr(
args: [ namedArgs: {
ref 'count': ref
.watch(backupProvider) .watch(backupProvider)
.availableAlbums .availableAlbums
.length .length
.toString(), .toString(),
], },
), ),
style: context.textTheme.titleSmall, style: context.textTheme.titleSmall,
), ),

View file

@ -1,18 +1,18 @@
import 'dart:typed_data';
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'dart:ui'; import 'dart:ui';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:auto_route/auto_route.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/album/album.provider.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
/// A stateless widget that provides functionality for editing an image. /// A stateless widget that provides functionality for editing an image.
@ -81,7 +81,7 @@ class EditImagePage extends ConsumerWidget {
ImmichToast.show( ImmichToast.show(
durationInSecond: 6, durationInSecond: 6,
context: context, context: context,
msg: "error_saving_image".tr(args: [e.toString()]), msg: "error_saving_image".tr(namedArgs: {'error': e.toString()}),
gravity: ToastGravity.CENTER, gravity: ToastGravity.CENTER,
); );
} }

View file

@ -24,7 +24,7 @@ class ArchivePage extends HookConsumerWidget {
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
title: const Text( title: const Text(
'archive_page_title', 'archive_page_title',
).tr(args: [count]), ).tr(namedArgs: {'count': count}),
); );
} }

View file

@ -73,7 +73,8 @@ class PartnerPage extends HookConsumerWidget {
builder: (BuildContext context) { builder: (BuildContext context) {
return ConfirmDialog( return ConfirmDialog(
title: "stop_photo_sharing", title: "stop_photo_sharing",
content: "partner_page_stop_sharing_content".tr(args: [u.name]), content: "partner_page_stop_sharing_content"
.tr(namedArgs: {'partner': u.name}),
onOk: () => ref.read(partnerServiceProvider).removePartner(u), onOk: () => ref.read(partnerServiceProvider).removePartner(u),
); );
}, },

View file

@ -7,11 +7,11 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/shared_link.provider.dart'; import 'package:immich_mobile/providers/shared_link.provider.dart';
import 'package:immich_mobile/services/shared_link.service.dart'; import 'package:immich_mobile/services/shared_link.service.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/utils/url_helper.dart'; import 'package:immich_mobile/utils/url_helper.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
@RoutePage() @RoutePage()
class SharedLinkEditPage extends HookConsumerWidget { class SharedLinkEditPage extends HookConsumerWidget {
@ -241,8 +241,8 @@ class SharedLinkEditPage extends HookConsumerWidget {
), ),
DropdownMenuEntry( DropdownMenuEntry(
value: 30, value: 30,
label: label: "shared_link_edit_expire_after_option_minutes"
"shared_link_edit_expire_after_option_minutes".tr(args: ["30"]), .tr(namedArgs: {'count': "30"}),
), ),
DropdownMenuEntry( DropdownMenuEntry(
value: 60, value: 60,
@ -250,7 +250,8 @@ class SharedLinkEditPage extends HookConsumerWidget {
), ),
DropdownMenuEntry( DropdownMenuEntry(
value: 60 * 6, value: 60 * 6,
label: "shared_link_edit_expire_after_option_hours".tr(args: ["6"]), label: "shared_link_edit_expire_after_option_hours"
.tr(namedArgs: {'count': "6"}),
), ),
DropdownMenuEntry( DropdownMenuEntry(
value: 60 * 24, value: 60 * 24,
@ -258,20 +259,23 @@ class SharedLinkEditPage extends HookConsumerWidget {
), ),
DropdownMenuEntry( DropdownMenuEntry(
value: 60 * 24 * 7, value: 60 * 24 * 7,
label: "shared_link_edit_expire_after_option_days".tr(args: ["7"]), label: "shared_link_edit_expire_after_option_days"
.tr(namedArgs: {'count': "7"}),
), ),
DropdownMenuEntry( DropdownMenuEntry(
value: 60 * 24 * 30, value: 60 * 24 * 30,
label: "shared_link_edit_expire_after_option_days".tr(args: ["30"]), label: "shared_link_edit_expire_after_option_days"
.tr(namedArgs: {'count': "30"}),
), ),
DropdownMenuEntry( DropdownMenuEntry(
value: 60 * 24 * 30 * 3, value: 60 * 24 * 30 * 3,
label: label: "shared_link_edit_expire_after_option_months"
"shared_link_edit_expire_after_option_months".tr(args: ["3"]), .tr(namedArgs: {'count': "3"}),
), ),
DropdownMenuEntry( DropdownMenuEntry(
value: 60 * 24 * 30 * 12, value: 60 * 24 * 30 * 12,
label: "shared_link_edit_expire_after_option_year".tr(args: ["1"]), label: "shared_link_edit_expire_after_option_year"
.tr(namedArgs: {'count': "1"}),
), ),
], ],
); );

View file

@ -4,18 +4,18 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/providers/timeline.provider.dart';
import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart';
import 'package:immich_mobile/providers/trash.provider.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/timeline.provider.dart';
import 'package:immich_mobile/providers/trash.provider.dart';
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart';
import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
@RoutePage() @RoutePage()
class TrashPage extends HookConsumerWidget { class TrashPage extends HookConsumerWidget {
@ -77,7 +77,7 @@ class TrashPage extends HookConsumerWidget {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: 'assets_deleted_permanently' msg: 'assets_deleted_permanently'
.tr(args: ["${selection.value.length}"]), .tr(namedArgs: {'count': "${selection.value.length}"}),
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
} }
@ -118,7 +118,7 @@ class TrashPage extends HookConsumerWidget {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: 'assets_restored_successfully' msg: 'assets_restored_successfully'
.tr(args: ["${selection.value.length}"]), .tr(namedArgs: {'count': "${selection.value.length}"}),
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
} }
@ -135,7 +135,7 @@ class TrashPage extends HookConsumerWidget {
? "${selection.value.length}" ? "${selection.value.length}"
: "trash_page_select_assets_btn".tr(); : "trash_page_select_assets_btn".tr();
} }
return 'trash_page_title'.tr(args: [count]); return 'trash_page_title'.tr(namedArgs: {'count': count});
} }
AppBar buildAppBar(String count) { AppBar buildAppBar(String count) {
@ -260,7 +260,7 @@ class TrashPage extends HookConsumerWidget {
), ),
child: const Text( child: const Text(
"trash_page_info", "trash_page_info",
).tr(args: ["$trashDays"]), ).tr(namedArgs: {"days": "$trashDays"}),
), ),
), ),
), ),

View file

@ -56,9 +56,7 @@ class ShareIntentPage extends HookConsumerWidget {
title: Column( title: Column(
children: [ children: [
const Text('upload_to_immich').tr( const Text('upload_to_immich').tr(
args: [ namedArgs: {'count': candidates.length.toString()},
candidates.length.toString(),
],
), ),
Text( Text(
currentEndpoint, currentEndpoint,
@ -176,8 +174,12 @@ class UploadingText extends StatelessWidget {
return element.status == UploadStatus.complete; return element.status == UploadStatus.complete;
}).length; }).length;
return const Text("shared_intent_upload_button_progress_text") return const Text("shared_intent_upload_button_progress_text").tr(
.tr(args: [uploadedCount.toString(), candidates.length.toString()]); namedArgs: {
'current': uploadedCount.toString(),
'total': candidates.length.toString(),
},
);
} }
} }

View file

@ -6,27 +6,27 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
import 'package:immich_mobile/models/backup/success_upload_asset.model.dart';
import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/services/background.service.dart';
import 'package:immich_mobile/models/backup/backup_state.model.dart'; import 'package:immich_mobile/models/backup/backup_state.model.dart';
import 'package:immich_mobile/models/backup/current_upload_asset.model.dart'; import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
import 'package:immich_mobile/models/backup/error_upload_asset.model.dart'; import 'package:immich_mobile/models/backup/error_upload_asset.model.dart';
import 'package:immich_mobile/models/backup/manual_upload_state.model.dart'; import 'package:immich_mobile/models/backup/manual_upload_state.model.dart';
import 'package:immich_mobile/models/backup/success_upload_asset.model.dart';
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart';
import 'package:immich_mobile/services/backup.service.dart';
import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/services/background.service.dart';
import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; import 'package:immich_mobile/services/backup.service.dart';
import 'package:immich_mobile/services/backup_album.service.dart'; import 'package:immich_mobile/services/backup_album.service.dart';
import 'package:immich_mobile/services/local_notification.service.dart'; import 'package:immich_mobile/services/local_notification.service.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/utils/backup_progress.dart'; import 'package:immich_mobile/utils/backup_progress.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; import 'package:photo_manager/photo_manager.dart' show PMProgressHandler;
@ -170,7 +170,7 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
if (state.showDetailedNotification) { if (state.showDetailedNotification) {
final title = "backup_background_service_current_upload_notification" final title = "backup_background_service_current_upload_notification"
.tr(args: [state.currentUploadAsset.fileName]); .tr(namedArgs: {'filename': state.currentUploadAsset.fileName});
_throttledDetailNotify(title: title, progress: sent, total: total); _throttledDetailNotify(title: title, progress: sent, total: total);
} }
} }
@ -186,7 +186,7 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
if (state.showDetailedNotification) { if (state.showDetailedNotification) {
_throttledDetailNotify.title = _throttledDetailNotify.title =
"backup_background_service_current_upload_notification" "backup_background_service_current_upload_notification"
.tr(args: [currentUploadAsset.fileName]); .tr(namedArgs: {'filename': currentUploadAsset.fileName});
_throttledDetailNotify.progress = 0; _throttledDetailNotify.progress = 0;
_throttledDetailNotify.total = 0; _throttledDetailNotify.total = 0;
} }

View file

@ -197,7 +197,7 @@ class AssetService {
ids: assets.map((e) => e.remoteId!).toList(), ids: assets.map((e) => e.remoteId!).toList(),
dateTimeOriginal: updateAssetDto.dateTimeOriginal, dateTimeOriginal: updateAssetDto.dateTimeOriginal,
isFavorite: updateAssetDto.isFavorite, isFavorite: updateAssetDto.isFavorite,
isArchived: updateAssetDto.isArchived, visibility: updateAssetDto.visibility,
latitude: updateAssetDto.latitude, latitude: updateAssetDto.latitude,
longitude: updateAssetDto.longitude, longitude: updateAssetDto.longitude,
), ),
@ -229,7 +229,13 @@ class AssetService {
bool isArchived, bool isArchived,
) async { ) async {
try { try {
await updateAssets(assets, UpdateAssetDto(isArchived: isArchived)); await updateAssets(
assets,
UpdateAssetDto(
visibility:
isArchived ? AssetVisibility.archive : AssetVisibility.timeline,
),
);
for (var element in assets) { for (var element in assets) {
element.isArchived = isArchived; element.isArchived = isArchived;

View file

@ -562,7 +562,7 @@ class BackgroundService {
void _onBackupError(ErrorUploadAsset errorAssetInfo) { void _onBackupError(ErrorUploadAsset errorAssetInfo) {
_showErrorNotification( _showErrorNotification(
title: "backup_background_service_upload_failure_notification" title: "backup_background_service_upload_failure_notification"
.tr(args: [errorAssetInfo.fileName]), .tr(namedArgs: {'filename': errorAssetInfo.fileName}),
individualTag: errorAssetInfo.id, individualTag: errorAssetInfo.id,
); );
} }
@ -577,7 +577,7 @@ class BackgroundService {
_throttledDetailNotify.title = _throttledDetailNotify.title =
"backup_background_service_current_upload_notification" "backup_background_service_current_upload_notification"
.tr(args: [currentUploadAsset.fileName]); .tr(namedArgs: {'filename': currentUploadAsset.fileName});
_throttledDetailNotify.progress = 0; _throttledDetailNotify.progress = 0;
_throttledDetailNotify.total = 0; _throttledDetailNotify.total = 0;
} }

View file

@ -42,7 +42,8 @@ class MemoryService {
if (dbAssets.isNotEmpty) { if (dbAssets.isNotEmpty) {
final String title = yearsAgo <= 1 final String title = yearsAgo <= 1
? 'memories_year_ago'.tr() ? 'memories_year_ago'.tr()
: 'memories_years_ago'.tr(args: [yearsAgo.toString()]); : 'memories_years_ago'
.tr(namedArgs: {'years': yearsAgo.toString()});
memories.add( memories.add(
Memory( Memory(
title: title, title: title,

View file

@ -68,7 +68,9 @@ class SearchService {
model: filter.camera.model, model: filter.camera.model,
takenAfter: filter.date.takenAfter, takenAfter: filter.date.takenAfter,
takenBefore: filter.date.takenBefore, takenBefore: filter.date.takenBefore,
isArchived: filter.display.isArchive ? true : null, visibility: filter.display.isArchive
? AssetVisibility.archive
: AssetVisibility.timeline,
isFavorite: filter.display.isFavorite ? true : null, isFavorite: filter.display.isFavorite ? true : null,
isNotInAlbum: filter.display.isNotInAlbum ? true : null, isNotInAlbum: filter.display.isNotInAlbum ? true : null,
personIds: filter.people.map((e) => e.id).toList(), personIds: filter.people.map((e) => e.id).toList(),
@ -95,7 +97,9 @@ class SearchService {
model: filter.camera.model, model: filter.camera.model,
takenAfter: filter.date.takenAfter, takenAfter: filter.date.takenAfter,
takenBefore: filter.date.takenBefore, takenBefore: filter.date.takenBefore,
isArchived: filter.display.isArchive ? true : null, visibility: filter.display.isArchive
? AssetVisibility.archive
: AssetVisibility.timeline,
isFavorite: filter.display.isFavorite ? true : null, isFavorite: filter.display.isFavorite ? true : null,
isNotInAlbum: filter.display.isNotInAlbum ? true : null, isNotInAlbum: filter.display.isNotInAlbum ? true : null,
personIds: filter.people.map((e) => e.id).toList(), personIds: filter.people.map((e) => e.id).toList(),

View file

@ -61,7 +61,8 @@ class AlbumThumbnailCard extends ConsumerWidget {
if (album.ownerId == ref.read(currentUserProvider)?.id) { if (album.ownerId == ref.read(currentUserProvider)?.id) {
owner = 'owned'.tr(); owner = 'owned'.tr();
} else if (album.ownerName != null) { } else if (album.ownerName != null) {
owner = 'album_thumbnail_shared_by'.tr(args: [album.ownerName!]); owner = 'album_thumbnail_shared_by'
.tr(namedArgs: {'user': album.ownerName!});
} }
} }
@ -74,10 +75,9 @@ class AlbumThumbnailCard extends ConsumerWidget {
children: [ children: [
TextSpan( TextSpan(
text: album.assetCount == 1 text: album.assetCount == 1
? 'album_thumbnail_card_item' ? 'album_thumbnail_card_item'.tr()
.tr(args: ['${album.assetCount}'])
: 'album_thumbnail_card_items' : 'album_thumbnail_card_items'
.tr(args: ['${album.assetCount}']), .tr(namedArgs: {'count': '${album.assetCount}'}),
), ),
if (owner != null) const TextSpan(text: ''), if (owner != null) const TextSpan(text: ''),
if (owner != null) TextSpan(text: owner), if (owner != null) TextSpan(text: owner),

View file

@ -2,9 +2,9 @@ import 'package:auto_route/auto_route.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
@ -96,7 +96,7 @@ class AlbumThumbnailListTile extends StatelessWidget {
style: const TextStyle( style: const TextStyle(
fontSize: 12, fontSize: 12,
), ),
).tr(args: ['${album.assetCount}']), ).tr(namedArgs: {'count': '${album.assetCount}'}),
if (album.shared) if (album.shared)
const Text( const Text(
'album_thumbnail_card_shared', 'album_thumbnail_card_shared',

View file

@ -7,24 +7,24 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/collection_extensions.dart';
import 'package:immich_mobile/providers/album/album.provider.dart';
import 'package:immich_mobile/services/album.service.dart';
import 'package:immich_mobile/services/stack.service.dart';
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
import 'package:immich_mobile/models/asset_selection_state.dart';
import 'package:immich_mobile/providers/multiselect.provider.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/widgets/asset_grid/control_bottom_app_bar.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/collection_extensions.dart';
import 'package:immich_mobile/models/asset_selection_state.dart';
import 'package:immich_mobile/providers/album/album.provider.dart';
import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
import 'package:immich_mobile/providers/multiselect.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/album.service.dart';
import 'package:immich_mobile/services/stack.service.dart';
import 'package:immich_mobile/utils/immich_loading_overlay.dart'; import 'package:immich_mobile/utils/immich_loading_overlay.dart';
import 'package:immich_mobile/utils/selection_handlers.dart'; import 'package:immich_mobile/utils/selection_handlers.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/widgets/asset_grid/control_bottom_app_bar.dart';
import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
class MultiselectGrid extends HookConsumerWidget { class MultiselectGrid extends HookConsumerWidget {
const MultiselectGrid({ const MultiselectGrid({
@ -190,8 +190,9 @@ class MultiselectGrid extends HookConsumerWidget {
context: context, context: context,
msg: force msg: force
? 'assets_deleted_permanently' ? 'assets_deleted_permanently'
.tr(args: ["${selection.value.length}"]) .tr(namedArgs: {'count': "${selection.value.length}"})
: 'assets_trashed'.tr(args: ["${selection.value.length}"]), : 'assets_trashed'
.tr(namedArgs: {'count': "${selection.value.length}"}),
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
selectionEnabledHook.value = false; selectionEnabledHook.value = false;
@ -225,7 +226,7 @@ class MultiselectGrid extends HookConsumerWidget {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: 'assets_removed_permanently_from_device' msg: 'assets_removed_permanently_from_device'
.tr(args: ["$deletedCount"]), .tr(namedArgs: {'count': "$deletedCount"}),
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
@ -254,8 +255,9 @@ class MultiselectGrid extends HookConsumerWidget {
context: context, context: context,
msg: shouldDeletePermanently msg: shouldDeletePermanently
? 'assets_deleted_permanently_from_server' ? 'assets_deleted_permanently_from_server'
.tr(args: ["${toDelete.length}"]) .tr(namedArgs: {'count': "${toDelete.length}"})
: 'assets_trashed_from_server'.tr(args: ["${toDelete.length}"]), : 'assets_trashed_from_server'
.tr(namedArgs: {'count': "${toDelete.length}"}),
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
} }

View file

@ -2,13 +2,13 @@ import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_people.provider.dart';
import 'package:immich_mobile/models/search/search_curated_content.model.dart'; import 'package:immich_mobile/models/search/search_curated_content.model.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_people.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/search/curated_people_row.dart'; import 'package:immich_mobile/widgets/search/curated_people_row.dart';
import 'package:immich_mobile/widgets/search/person_name_edit_form.dart'; import 'package:immich_mobile/widgets/search/person_name_edit_form.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
class PeopleInfo extends ConsumerWidget { class PeopleInfo extends ConsumerWidget {
final Asset asset; final Asset asset;
@ -109,13 +109,13 @@ class PeopleInfo extends ConsumerWidget {
if (ageInMonths <= 11) { if (ageInMonths <= 11) {
return "exif_bottom_sheet_person_age_months" return "exif_bottom_sheet_person_age_months"
.tr(args: [ageInMonths.toString()]); .tr(namedArgs: {'months': ageInMonths.toString()});
} else if (ageInMonths > 12 && ageInMonths <= 23) { } else if (ageInMonths > 12 && ageInMonths <= 23) {
return "exif_bottom_sheet_person_age_year_months" return "exif_bottom_sheet_person_age_year_months"
.tr(args: [(ageInMonths - 12).toString()]); .tr(namedArgs: {'months': (ageInMonths - 12).toString()});
} else { } else {
return "exif_bottom_sheet_person_age_years" return "exif_bottom_sheet_person_age_years"
.tr(args: [ageInYears.toString()]); .tr(namedArgs: {'years': ageInYears.toString()});
} }
} }

View file

@ -56,9 +56,12 @@ class BackupAssetInfoTable extends ConsumerWidget {
fontSize: 10.0, fontSize: 10.0,
), ),
).tr( ).tr(
args: isUploadInProgress namedArgs: isUploadInProgress
? [asset.fileName, asset.fileType.toLowerCase()] ? {
: ["-", "-"], 'filename': asset.fileName,
'size': asset.fileType.toLowerCase(),
}
: {'filename': "-", 'size': "-"},
), ),
), ),
), ),
@ -78,9 +81,11 @@ class BackupAssetInfoTable extends ConsumerWidget {
fontSize: 10.0, fontSize: 10.0,
), ),
).tr( ).tr(
args: [ namedArgs: {
isUploadInProgress ? _getAssetCreationDate(asset) : "-", 'date': isUploadInProgress
], ? _getAssetCreationDate(asset)
: "-",
},
), ),
), ),
), ),
@ -99,9 +104,7 @@ class BackupAssetInfoTable extends ConsumerWidget {
fontSize: 10.0, fontSize: 10.0,
), ),
).tr( ).tr(
args: [ namedArgs: {'id': isUploadInProgress ? asset.id : "-"},
isUploadInProgress ? asset.id : "-",
],
), ),
), ),
), ),

View file

@ -21,8 +21,6 @@ class BackupErrorChipText extends ConsumerWidget {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 11, fontSize: 11,
), ),
).tr( ).tr(namedArgs: {'count': count.toString()});
args: [count.toString()],
);
} }
} }

View file

@ -5,17 +5,17 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/backup/backup_state.model.dart'; import 'package:immich_mobile/models/backup/backup_state.model.dart';
import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/bytes_units.dart';
import 'package:immich_mobile/widgets/common/app_bar_dialog/app_bar_profile_info.dart'; import 'package:immich_mobile/widgets/common/app_bar_dialog/app_bar_profile_info.dart';
import 'package:immich_mobile/widgets/common/app_bar_dialog/app_bar_server_info.dart'; import 'package:immich_mobile/widgets/common/app_bar_dialog/app_bar_server_info.dart';
import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
import 'package:immich_mobile/utils/bytes_units.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class ImmichAppBarDialog extends HookConsumerWidget { class ImmichAppBarDialog extends HookConsumerWidget {
@ -200,10 +200,10 @@ class ImmichAppBarDialog extends HookConsumerWidget {
padding: const EdgeInsets.only(top: 12.0), padding: const EdgeInsets.only(top: 12.0),
child: child:
const Text('backup_controller_page_storage_format').tr( const Text('backup_controller_page_storage_format').tr(
args: [ namedArgs: {
usedDiskSpace, 'used': usedDiskSpace,
totalDiskSpace, 'total': totalDiskSpace,
], },
), ),
), ),
], ],

View file

@ -1,20 +1,21 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/collection_extensions.dart'; import 'package:immich_mobile/extensions/collection_extensions.dart';
import 'package:immich_mobile/providers/timeline.provider.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/models/map/map_event.model.dart'; import 'package:immich_mobile/models/map/map_event.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/widgets/common/drag_sheet.dart'; import 'package:immich_mobile/providers/timeline.provider.dart';
import 'package:immich_mobile/utils/color_filter_generator.dart'; import 'package:immich_mobile/utils/color_filter_generator.dart';
import 'package:immich_mobile/utils/throttle.dart'; import 'package:immich_mobile/utils/throttle.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/widgets/common/drag_sheet.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
@ -252,7 +253,8 @@ class _MapSheetDragRegion extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final assetsInBoundsText = assetsInBoundCount > 0 final assetsInBoundsText = assetsInBoundCount > 0
? "map_assets_in_bounds".tr(args: [assetsInBoundCount.toString()]) ? "map_assets_in_bounds"
.tr(namedArgs: {'count': assetsInBoundCount.toString()})
: "map_no_assets_in_bounds".tr(); : "map_no_assets_in_bounds".tr();
return SingleChildScrollView( return SingleChildScrollView(

View file

@ -44,13 +44,13 @@ class MapTimeDropDown extends StatelessWidget {
DropdownMenuEntry( DropdownMenuEntry(
value: 7, value: 7,
label: "map_settings_date_range_option_days".tr( label: "map_settings_date_range_option_days".tr(
args: ["7"], namedArgs: {'days': "7"},
), ),
), ),
DropdownMenuEntry( DropdownMenuEntry(
value: 30, value: 30,
label: "map_settings_date_range_option_days".tr( label: "map_settings_date_range_option_days".tr(
args: ["30"], namedArgs: {'days': "30"},
), ),
), ),
DropdownMenuEntry( DropdownMenuEntry(
@ -81,7 +81,8 @@ class MapTimeDropDown extends StatelessWidget {
), ),
) )
.inDays, .inDays,
label: "map_settings_date_range_option_years".tr(args: ["3"]), label: "map_settings_date_range_option_years"
.tr(namedArgs: {'years': "3"}),
), ),
], ],
), ),

View file

@ -85,7 +85,8 @@ class AdvancedSettings extends HookConsumerWidget {
}, },
), ),
SettingsSliderListTile( SettingsSliderListTile(
text: "advanced_settings_log_level_title".tr(args: [logLevel]), text: "advanced_settings_log_level_title"
.tr(namedArgs: {'level': logLevel}),
valueNotifier: levelId, valueNotifier: levelId,
maxValue: 8, maxValue: 8,
minValue: 1, minValue: 1,

View file

@ -3,10 +3,10 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
class LayoutSettings extends HookConsumerWidget { class LayoutSettings extends HookConsumerWidget {
const LayoutSettings({ const LayoutSettings({
@ -30,7 +30,7 @@ class LayoutSettings extends HookConsumerWidget {
SettingsSliderListTile( SettingsSliderListTile(
valueNotifier: tilesPerRow, valueNotifier: tilesPerRow,
text: 'theme_setting_asset_list_tiles_per_row_title' text: 'theme_setting_asset_list_tiles_per_row_title'
.tr(args: ["${tilesPerRow.value}"]), .tr(namedArgs: {'count': "${tilesPerRow.value}"}),
label: "${tilesPerRow.value}", label: "${tilesPerRow.value}",
maxValue: 6, maxValue: 6,
minValue: 2, minValue: 2,

View file

@ -164,10 +164,14 @@ class _BackgroundSettingsEnabled extends HookConsumerWidget {
switch (v) { 0 => 5000, 1 => 30000, 2 => 120000, _ => 600000 }; switch (v) { 0 => 5000, 1 => 30000, 2 => 120000, _ => 600000 };
String formatBackupDelaySliderValue(int v) => switch (v) { String formatBackupDelaySliderValue(int v) => switch (v) {
0 => 'setting_notifications_notify_seconds'.tr(args: const ['5']), 0 => 'setting_notifications_notify_seconds'
1 => 'setting_notifications_notify_seconds'.tr(args: const ['30']), .tr(namedArgs: {'count': '5'}),
2 => 'setting_notifications_notify_minutes'.tr(args: const ['2']), 1 => 'setting_notifications_notify_seconds'
_ => 'setting_notifications_notify_minutes'.tr(args: const ['10']), .tr(namedArgs: {'count': '30'}),
2 => 'setting_notifications_notify_minutes'
.tr(namedArgs: {'count': '2'}),
_ => 'setting_notifications_notify_minutes'
.tr(namedArgs: {'count': '10'}),
}; };
final backupTriggerDelay = final backupTriggerDelay =
@ -221,7 +225,9 @@ class _BackgroundSettingsEnabled extends HookConsumerWidget {
SettingsSliderListTile( SettingsSliderListTile(
valueNotifier: triggerDelay, valueNotifier: triggerDelay,
text: 'backup_controller_page_background_delay'.tr( text: 'backup_controller_page_background_delay'.tr(
args: [formatBackupDelaySliderValue(triggerDelay.value)], namedArgs: {
'duration': formatBackupDelaySliderValue(triggerDelay.value),
},
), ),
maxValue: 3.0, maxValue: 3.0,
noDivisons: 3, noDivisons: 3,

View file

@ -1,9 +1,9 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' show useEffect, useState; import 'package:flutter_hooks/flutter_hooks.dart' show useEffect, useState;
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/entities/duplicated_asset.entity.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/duplicated_asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
@ -35,7 +35,7 @@ class LocalStorageSettings extends HookConsumerWidget {
style: context.textTheme.bodyLarge?.copyWith( style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
).tr(args: ["${cacheItemCount.value}"]), ).tr(namedArgs: {'count': "${cacheItemCount.value}"}),
subtitle: Text( subtitle: Text(
"cache_settings_duplicated_assets_subtitle", "cache_settings_duplicated_assets_subtitle",
style: context.textTheme.bodyMedium?.copyWith( style: context.textTheme.bodyMedium?.copyWith(

View file

@ -4,11 +4,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/notification_permission.provider.dart'; import 'package:immich_mobile/providers/notification_permission.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
class NotificationSetting extends HookConsumerWidget { class NotificationSetting extends HookConsumerWidget {
@ -90,7 +90,7 @@ class NotificationSetting extends HookConsumerWidget {
enabled: hasPermission, enabled: hasPermission,
valueNotifier: sliderValue, valueNotifier: sliderValue,
text: 'setting_notifications_notify_failures_grace_period' text: 'setting_notifications_notify_failures_grace_period'
.tr(args: [formattedValue]), .tr(namedArgs: {'duration': formattedValue}),
maxValue: 5.0, maxValue: 5.0,
noDivisons: 5, noDivisons: 5,
label: formattedValue, label: formattedValue,
@ -105,13 +105,14 @@ String _formatSliderValue(double v) {
if (v == 0.0) { if (v == 0.0) {
return 'setting_notifications_notify_immediately'.tr(); return 'setting_notifications_notify_immediately'.tr();
} else if (v == 1.0) { } else if (v == 1.0) {
return 'setting_notifications_notify_minutes'.tr(args: const ['30']); return 'setting_notifications_notify_minutes'
.tr(namedArgs: {'count': '30'});
} else if (v == 2.0) { } else if (v == 2.0) {
return 'setting_notifications_notify_hours'.tr(args: const ['2']); return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '2'});
} else if (v == 3.0) { } else if (v == 3.0) {
return 'setting_notifications_notify_hours'.tr(args: const ['8']); return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '8'});
} else if (v == 4.0) { } else if (v == 4.0) {
return 'setting_notifications_notify_hours'.tr(args: const ['24']); return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '24'});
} else { } else {
return 'setting_notifications_notify_never'.tr(); return 'setting_notifications_notify_never'.tr();
} }

View file

@ -1,4 +1,5 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -6,15 +7,15 @@ import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/widgets/search/thumbnail_with_info.dart';
import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/shared_link.provider.dart'; import 'package:immich_mobile/providers/shared_link.provider.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:immich_mobile/utils/url_helper.dart'; import 'package:immich_mobile/utils/url_helper.dart';
import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/widgets/search/thumbnail_with_info.dart';
class SharedLinkItem extends ConsumerWidget { class SharedLinkItem extends ConsumerWidget {
final SharedLink sharedLink; final SharedLink sharedLink;
@ -44,17 +45,17 @@ class SharedLinkItem extends ConsumerWidget {
if (difference.inHours % 24 > 12) { if (difference.inHours % 24 > 12) {
dayDifference += 1; dayDifference += 1;
} }
expiresText = expiresText = "shared_link_expires_days"
"shared_link_expires_days".tr(args: [dayDifference.toString()]); .tr(namedArgs: {'count': dayDifference.toString()});
} else if (difference.inHours > 0) { } else if (difference.inHours > 0) {
expiresText = "shared_link_expires_hours" expiresText = "shared_link_expires_hours"
.tr(args: [difference.inHours.toString()]); .tr(namedArgs: {'count': difference.inHours.toString()});
} else if (difference.inMinutes > 0) { } else if (difference.inMinutes > 0) {
expiresText = "shared_link_expires_minutes" expiresText = "shared_link_expires_minutes"
.tr(args: [difference.inMinutes.toString()]); .tr(namedArgs: {'count': difference.inMinutes.toString()});
} else if (difference.inSeconds > 0) { } else if (difference.inSeconds > 0) {
expiresText = "shared_link_expires_seconds" expiresText = "shared_link_expires_seconds"
.tr(args: [difference.inSeconds.toString()]); .tr(namedArgs: {'count': difference.inSeconds.toString()});
} }
} }
return Text( return Text(

View file

@ -302,6 +302,7 @@ Class | Method | HTTP request | Description
- [AssetStackResponseDto](doc//AssetStackResponseDto.md) - [AssetStackResponseDto](doc//AssetStackResponseDto.md)
- [AssetStatsResponseDto](doc//AssetStatsResponseDto.md) - [AssetStatsResponseDto](doc//AssetStatsResponseDto.md)
- [AssetTypeEnum](doc//AssetTypeEnum.md) - [AssetTypeEnum](doc//AssetTypeEnum.md)
- [AssetVisibility](doc//AssetVisibility.md)
- [AudioCodec](doc//AudioCodec.md) - [AudioCodec](doc//AudioCodec.md)
- [AvatarUpdate](doc//AvatarUpdate.md) - [AvatarUpdate](doc//AvatarUpdate.md)
- [BulkIdResponseDto](doc//BulkIdResponseDto.md) - [BulkIdResponseDto](doc//BulkIdResponseDto.md)

View file

@ -106,6 +106,7 @@ part 'model/asset_response_dto.dart';
part 'model/asset_stack_response_dto.dart'; part 'model/asset_stack_response_dto.dart';
part 'model/asset_stats_response_dto.dart'; part 'model/asset_stats_response_dto.dart';
part 'model/asset_type_enum.dart'; part 'model/asset_type_enum.dart';
part 'model/asset_visibility.dart';
part 'model/audio_codec.dart'; part 'model/audio_codec.dart';
part 'model/avatar_update.dart'; part 'model/avatar_update.dart';
part 'model/bulk_id_response_dto.dart'; part 'model/bulk_id_response_dto.dart';

View file

@ -342,12 +342,12 @@ class AssetsApi {
/// Performs an HTTP 'GET /assets/statistics' operation and returns the [Response]. /// Performs an HTTP 'GET /assets/statistics' operation and returns the [Response].
/// Parameters: /// Parameters:
/// ///
/// * [bool] isArchived:
///
/// * [bool] isFavorite: /// * [bool] isFavorite:
/// ///
/// * [bool] isTrashed: /// * [bool] isTrashed:
Future<Response> getAssetStatisticsWithHttpInfo({ bool? isArchived, bool? isFavorite, bool? isTrashed, }) async { ///
/// * [AssetVisibility] visibility:
Future<Response> getAssetStatisticsWithHttpInfo({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final apiPath = r'/assets/statistics'; final apiPath = r'/assets/statistics';
@ -358,15 +358,15 @@ class AssetsApi {
final headerParams = <String, String>{}; final headerParams = <String, String>{};
final formParams = <String, String>{}; final formParams = <String, String>{};
if (isArchived != null) {
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
}
if (isFavorite != null) { if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
} }
if (isTrashed != null) { if (isTrashed != null) {
queryParams.addAll(_queryParams('', 'isTrashed', isTrashed)); queryParams.addAll(_queryParams('', 'isTrashed', isTrashed));
} }
if (visibility != null) {
queryParams.addAll(_queryParams('', 'visibility', visibility));
}
const contentTypes = <String>[]; const contentTypes = <String>[];
@ -384,13 +384,13 @@ class AssetsApi {
/// Parameters: /// Parameters:
/// ///
/// * [bool] isArchived:
///
/// * [bool] isFavorite: /// * [bool] isFavorite:
/// ///
/// * [bool] isTrashed: /// * [bool] isTrashed:
Future<AssetStatsResponseDto?> getAssetStatistics({ bool? isArchived, bool? isFavorite, bool? isTrashed, }) async { ///
final response = await getAssetStatisticsWithHttpInfo( isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, ); /// * [AssetVisibility] visibility:
Future<AssetStatsResponseDto?> getAssetStatistics({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async {
final response = await getAssetStatisticsWithHttpInfo( isFavorite: isFavorite, isTrashed: isTrashed, visibility: visibility, );
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
} }
@ -788,16 +788,14 @@ class AssetsApi {
/// ///
/// * [String] duration: /// * [String] duration:
/// ///
/// * [bool] isArchived:
///
/// * [bool] isFavorite: /// * [bool] isFavorite:
/// ///
/// * [bool] isVisible:
///
/// * [String] livePhotoVideoId: /// * [String] livePhotoVideoId:
/// ///
/// * [MultipartFile] sidecarData: /// * [MultipartFile] sidecarData:
Future<Response> uploadAssetWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isArchived, bool? isFavorite, bool? isVisible, String? livePhotoVideoId, MultipartFile? sidecarData, }) async { ///
/// * [AssetVisibility] visibility:
Future<Response> uploadAssetWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isFavorite, String? livePhotoVideoId, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final apiPath = r'/assets'; final apiPath = r'/assets';
@ -845,18 +843,10 @@ class AssetsApi {
hasFields = true; hasFields = true;
mp.fields[r'fileModifiedAt'] = parameterToString(fileModifiedAt); mp.fields[r'fileModifiedAt'] = parameterToString(fileModifiedAt);
} }
if (isArchived != null) {
hasFields = true;
mp.fields[r'isArchived'] = parameterToString(isArchived);
}
if (isFavorite != null) { if (isFavorite != null) {
hasFields = true; hasFields = true;
mp.fields[r'isFavorite'] = parameterToString(isFavorite); mp.fields[r'isFavorite'] = parameterToString(isFavorite);
} }
if (isVisible != null) {
hasFields = true;
mp.fields[r'isVisible'] = parameterToString(isVisible);
}
if (livePhotoVideoId != null) { if (livePhotoVideoId != null) {
hasFields = true; hasFields = true;
mp.fields[r'livePhotoVideoId'] = parameterToString(livePhotoVideoId); mp.fields[r'livePhotoVideoId'] = parameterToString(livePhotoVideoId);
@ -866,6 +856,10 @@ class AssetsApi {
mp.fields[r'sidecarData'] = sidecarData.field; mp.fields[r'sidecarData'] = sidecarData.field;
mp.files.add(sidecarData); mp.files.add(sidecarData);
} }
if (visibility != null) {
hasFields = true;
mp.fields[r'visibility'] = parameterToString(visibility);
}
if (hasFields) { if (hasFields) {
postBody = mp; postBody = mp;
} }
@ -900,17 +894,15 @@ class AssetsApi {
/// ///
/// * [String] duration: /// * [String] duration:
/// ///
/// * [bool] isArchived:
///
/// * [bool] isFavorite: /// * [bool] isFavorite:
/// ///
/// * [bool] isVisible:
///
/// * [String] livePhotoVideoId: /// * [String] livePhotoVideoId:
/// ///
/// * [MultipartFile] sidecarData: /// * [MultipartFile] sidecarData:
Future<AssetMediaResponseDto?> uploadAsset(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isArchived, bool? isFavorite, bool? isVisible, String? livePhotoVideoId, MultipartFile? sidecarData, }) async { ///
final response = await uploadAssetWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, xImmichChecksum: xImmichChecksum, duration: duration, isArchived: isArchived, isFavorite: isFavorite, isVisible: isVisible, livePhotoVideoId: livePhotoVideoId, sidecarData: sidecarData, ); /// * [AssetVisibility] visibility:
Future<AssetMediaResponseDto?> uploadAsset(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isFavorite, String? livePhotoVideoId, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
final response = await uploadAssetWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, xImmichChecksum: xImmichChecksum, duration: duration, isFavorite: isFavorite, livePhotoVideoId: livePhotoVideoId, sidecarData: sidecarData, visibility: visibility, );
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
} }

View file

@ -25,8 +25,6 @@ class TimelineApi {
/// ///
/// * [String] albumId: /// * [String] albumId:
/// ///
/// * [bool] isArchived:
///
/// * [bool] isFavorite: /// * [bool] isFavorite:
/// ///
/// * [bool] isTrashed: /// * [bool] isTrashed:
@ -41,10 +39,12 @@ class TimelineApi {
/// ///
/// * [String] userId: /// * [String] userId:
/// ///
/// * [AssetVisibility] visibility:
///
/// * [bool] withPartners: /// * [bool] withPartners:
/// ///
/// * [bool] withStacked: /// * [bool] withStacked:
Future<Response> getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async { Future<Response> getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, AssetVisibility? visibility, bool? withPartners, bool? withStacked, }) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final apiPath = r'/timeline/bucket'; final apiPath = r'/timeline/bucket';
@ -58,9 +58,6 @@ class TimelineApi {
if (albumId != null) { if (albumId != null) {
queryParams.addAll(_queryParams('', 'albumId', albumId)); queryParams.addAll(_queryParams('', 'albumId', albumId));
} }
if (isArchived != null) {
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
}
if (isFavorite != null) { if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
} }
@ -84,6 +81,9 @@ class TimelineApi {
if (userId != null) { if (userId != null) {
queryParams.addAll(_queryParams('', 'userId', userId)); queryParams.addAll(_queryParams('', 'userId', userId));
} }
if (visibility != null) {
queryParams.addAll(_queryParams('', 'visibility', visibility));
}
if (withPartners != null) { if (withPartners != null) {
queryParams.addAll(_queryParams('', 'withPartners', withPartners)); queryParams.addAll(_queryParams('', 'withPartners', withPartners));
} }
@ -113,8 +113,6 @@ class TimelineApi {
/// ///
/// * [String] albumId: /// * [String] albumId:
/// ///
/// * [bool] isArchived:
///
/// * [bool] isFavorite: /// * [bool] isFavorite:
/// ///
/// * [bool] isTrashed: /// * [bool] isTrashed:
@ -129,11 +127,13 @@ class TimelineApi {
/// ///
/// * [String] userId: /// * [String] userId:
/// ///
/// * [AssetVisibility] visibility:
///
/// * [bool] withPartners: /// * [bool] withPartners:
/// ///
/// * [bool] withStacked: /// * [bool] withStacked:
Future<List<AssetResponseDto>?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async { Future<List<AssetResponseDto>?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, AssetVisibility? visibility, bool? withPartners, bool? withStacked, }) async {
final response = await getTimeBucketWithHttpInfo(size, timeBucket, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, withPartners: withPartners, withStacked: withStacked, ); final response = await getTimeBucketWithHttpInfo(size, timeBucket, albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, visibility: visibility, withPartners: withPartners, withStacked: withStacked, );
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
} }
@ -157,8 +157,6 @@ class TimelineApi {
/// ///
/// * [String] albumId: /// * [String] albumId:
/// ///
/// * [bool] isArchived:
///
/// * [bool] isFavorite: /// * [bool] isFavorite:
/// ///
/// * [bool] isTrashed: /// * [bool] isTrashed:
@ -173,10 +171,12 @@ class TimelineApi {
/// ///
/// * [String] userId: /// * [String] userId:
/// ///
/// * [AssetVisibility] visibility:
///
/// * [bool] withPartners: /// * [bool] withPartners:
/// ///
/// * [bool] withStacked: /// * [bool] withStacked:
Future<Response> getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async { Future<Response> getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, AssetVisibility? visibility, bool? withPartners, bool? withStacked, }) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final apiPath = r'/timeline/buckets'; final apiPath = r'/timeline/buckets';
@ -190,9 +190,6 @@ class TimelineApi {
if (albumId != null) { if (albumId != null) {
queryParams.addAll(_queryParams('', 'albumId', albumId)); queryParams.addAll(_queryParams('', 'albumId', albumId));
} }
if (isArchived != null) {
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
}
if (isFavorite != null) { if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
} }
@ -215,6 +212,9 @@ class TimelineApi {
if (userId != null) { if (userId != null) {
queryParams.addAll(_queryParams('', 'userId', userId)); queryParams.addAll(_queryParams('', 'userId', userId));
} }
if (visibility != null) {
queryParams.addAll(_queryParams('', 'visibility', visibility));
}
if (withPartners != null) { if (withPartners != null) {
queryParams.addAll(_queryParams('', 'withPartners', withPartners)); queryParams.addAll(_queryParams('', 'withPartners', withPartners));
} }
@ -242,8 +242,6 @@ class TimelineApi {
/// ///
/// * [String] albumId: /// * [String] albumId:
/// ///
/// * [bool] isArchived:
///
/// * [bool] isFavorite: /// * [bool] isFavorite:
/// ///
/// * [bool] isTrashed: /// * [bool] isTrashed:
@ -258,11 +256,13 @@ class TimelineApi {
/// ///
/// * [String] userId: /// * [String] userId:
/// ///
/// * [AssetVisibility] visibility:
///
/// * [bool] withPartners: /// * [bool] withPartners:
/// ///
/// * [bool] withStacked: /// * [bool] withStacked:
Future<List<TimeBucketResponseDto>?> getTimeBuckets(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async { Future<List<TimeBucketResponseDto>?> getTimeBuckets(TimeBucketSize size, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, AssetVisibility? visibility, bool? withPartners, bool? withStacked, }) async {
final response = await getTimeBucketsWithHttpInfo(size, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, withPartners: withPartners, withStacked: withStacked, ); final response = await getTimeBucketsWithHttpInfo(size, albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, visibility: visibility, withPartners: withPartners, withStacked: withStacked, );
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
} }

View file

@ -268,6 +268,8 @@ class ApiClient {
return AssetStatsResponseDto.fromJson(value); return AssetStatsResponseDto.fromJson(value);
case 'AssetTypeEnum': case 'AssetTypeEnum':
return AssetTypeEnumTypeTransformer().decode(value); return AssetTypeEnumTypeTransformer().decode(value);
case 'AssetVisibility':
return AssetVisibilityTypeTransformer().decode(value);
case 'AudioCodec': case 'AudioCodec':
return AudioCodecTypeTransformer().decode(value); return AudioCodecTypeTransformer().decode(value);
case 'AvatarUpdate': case 'AvatarUpdate':

View file

@ -73,6 +73,9 @@ String parameterToString(dynamic value) {
if (value is AssetTypeEnum) { if (value is AssetTypeEnum) {
return AssetTypeEnumTypeTransformer().encode(value).toString(); return AssetTypeEnumTypeTransformer().encode(value).toString();
} }
if (value is AssetVisibility) {
return AssetVisibilityTypeTransformer().encode(value).toString();
}
if (value is AudioCodec) { if (value is AudioCodec) {
return AudioCodecTypeTransformer().encode(value).toString(); return AudioCodecTypeTransformer().encode(value).toString();
} }

View file

@ -16,11 +16,11 @@ class AssetBulkUpdateDto {
this.dateTimeOriginal, this.dateTimeOriginal,
this.duplicateId, this.duplicateId,
this.ids = const [], this.ids = const [],
this.isArchived,
this.isFavorite, this.isFavorite,
this.latitude, this.latitude,
this.longitude, this.longitude,
this.rating, this.rating,
this.visibility,
}); });
/// ///
@ -35,14 +35,6 @@ class AssetBulkUpdateDto {
List<String> ids; List<String> ids;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isArchived;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated /// does not include a default value (using the "default:" property), however, the generated
@ -77,16 +69,24 @@ class AssetBulkUpdateDto {
/// ///
num? rating; num? rating;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
AssetVisibility? visibility;
@override @override
bool operator ==(Object other) => identical(this, other) || other is AssetBulkUpdateDto && bool operator ==(Object other) => identical(this, other) || other is AssetBulkUpdateDto &&
other.dateTimeOriginal == dateTimeOriginal && other.dateTimeOriginal == dateTimeOriginal &&
other.duplicateId == duplicateId && other.duplicateId == duplicateId &&
_deepEquality.equals(other.ids, ids) && _deepEquality.equals(other.ids, ids) &&
other.isArchived == isArchived &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
other.latitude == latitude && other.latitude == latitude &&
other.longitude == longitude && other.longitude == longitude &&
other.rating == rating; other.rating == rating &&
other.visibility == visibility;
@override @override
int get hashCode => int get hashCode =>
@ -94,14 +94,14 @@ class AssetBulkUpdateDto {
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
(duplicateId == null ? 0 : duplicateId!.hashCode) + (duplicateId == null ? 0 : duplicateId!.hashCode) +
(ids.hashCode) + (ids.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) + (isFavorite == null ? 0 : isFavorite!.hashCode) +
(latitude == null ? 0 : latitude!.hashCode) + (latitude == null ? 0 : latitude!.hashCode) +
(longitude == null ? 0 : longitude!.hashCode) + (longitude == null ? 0 : longitude!.hashCode) +
(rating == null ? 0 : rating!.hashCode); (rating == null ? 0 : rating!.hashCode) +
(visibility == null ? 0 : visibility!.hashCode);
@override @override
String toString() => 'AssetBulkUpdateDto[dateTimeOriginal=$dateTimeOriginal, duplicateId=$duplicateId, ids=$ids, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude, rating=$rating]'; String toString() => 'AssetBulkUpdateDto[dateTimeOriginal=$dateTimeOriginal, duplicateId=$duplicateId, ids=$ids, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude, rating=$rating, visibility=$visibility]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -116,11 +116,6 @@ class AssetBulkUpdateDto {
// json[r'duplicateId'] = null; // json[r'duplicateId'] = null;
} }
json[r'ids'] = this.ids; json[r'ids'] = this.ids;
if (this.isArchived != null) {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isFavorite != null) { if (this.isFavorite != null) {
json[r'isFavorite'] = this.isFavorite; json[r'isFavorite'] = this.isFavorite;
} else { } else {
@ -141,6 +136,11 @@ class AssetBulkUpdateDto {
} else { } else {
// json[r'rating'] = null; // json[r'rating'] = null;
} }
if (this.visibility != null) {
json[r'visibility'] = this.visibility;
} else {
// json[r'visibility'] = null;
}
return json; return json;
} }
@ -158,11 +158,11 @@ class AssetBulkUpdateDto {
ids: json[r'ids'] is Iterable ids: json[r'ids'] is Iterable
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false) ? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
: const [], : const [],
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'), isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
latitude: num.parse('${json[r'latitude']}'), latitude: num.parse('${json[r'latitude']}'),
longitude: num.parse('${json[r'longitude']}'), longitude: num.parse('${json[r'longitude']}'),
rating: num.parse('${json[r'rating']}'), rating: num.parse('${json[r'rating']}'),
visibility: AssetVisibility.fromJson(json[r'visibility']),
); );
} }
return null; return null;

View file

@ -0,0 +1,88 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class AssetVisibility {
/// Instantiate a new enum with the provided [value].
const AssetVisibility._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const archive = AssetVisibility._(r'archive');
static const timeline = AssetVisibility._(r'timeline');
static const hidden = AssetVisibility._(r'hidden');
/// List of all possible values in this [enum][AssetVisibility].
static const values = <AssetVisibility>[
archive,
timeline,
hidden,
];
static AssetVisibility? fromJson(dynamic value) => AssetVisibilityTypeTransformer().decode(value);
static List<AssetVisibility> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetVisibility>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetVisibility.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [AssetVisibility] to String,
/// and [decode] dynamic data back to [AssetVisibility].
class AssetVisibilityTypeTransformer {
factory AssetVisibilityTypeTransformer() => _instance ??= const AssetVisibilityTypeTransformer._();
const AssetVisibilityTypeTransformer._();
String encode(AssetVisibility data) => data.value;
/// Decodes a [dynamic value][data] to a AssetVisibility.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetVisibility? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'archive': return AssetVisibility.archive;
case r'timeline': return AssetVisibility.timeline;
case r'hidden': return AssetVisibility.hidden;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [AssetVisibilityTypeTransformer] instance.
static AssetVisibilityTypeTransformer? _instance;
}

View file

@ -23,13 +23,11 @@ class MetadataSearchDto {
this.deviceId, this.deviceId,
this.encodedVideoPath, this.encodedVideoPath,
this.id, this.id,
this.isArchived,
this.isEncoded, this.isEncoded,
this.isFavorite, this.isFavorite,
this.isMotion, this.isMotion,
this.isNotInAlbum, this.isNotInAlbum,
this.isOffline, this.isOffline,
this.isVisible,
this.lensModel, this.lensModel,
this.libraryId, this.libraryId,
this.make, this.make,
@ -52,7 +50,7 @@ class MetadataSearchDto {
this.type, this.type,
this.updatedAfter, this.updatedAfter,
this.updatedBefore, this.updatedBefore,
this.withArchived = false, this.visibility,
this.withDeleted, this.withDeleted,
this.withExif, this.withExif,
this.withPeople, this.withPeople,
@ -127,14 +125,6 @@ class MetadataSearchDto {
/// ///
String? id; String? id;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isArchived;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated /// does not include a default value (using the "default:" property), however, the generated
@ -175,14 +165,6 @@ class MetadataSearchDto {
/// ///
bool? isOffline; bool? isOffline;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isVisible;
String? lensModel; String? lensModel;
String? libraryId; String? libraryId;
@ -322,7 +304,13 @@ class MetadataSearchDto {
/// ///
DateTime? updatedBefore; DateTime? updatedBefore;
bool withArchived; ///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
AssetVisibility? visibility;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
@ -368,13 +356,11 @@ class MetadataSearchDto {
other.deviceId == deviceId && other.deviceId == deviceId &&
other.encodedVideoPath == encodedVideoPath && other.encodedVideoPath == encodedVideoPath &&
other.id == id && other.id == id &&
other.isArchived == isArchived &&
other.isEncoded == isEncoded && other.isEncoded == isEncoded &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
other.isMotion == isMotion && other.isMotion == isMotion &&
other.isNotInAlbum == isNotInAlbum && other.isNotInAlbum == isNotInAlbum &&
other.isOffline == isOffline && other.isOffline == isOffline &&
other.isVisible == isVisible &&
other.lensModel == lensModel && other.lensModel == lensModel &&
other.libraryId == libraryId && other.libraryId == libraryId &&
other.make == make && other.make == make &&
@ -397,7 +383,7 @@ class MetadataSearchDto {
other.type == type && other.type == type &&
other.updatedAfter == updatedAfter && other.updatedAfter == updatedAfter &&
other.updatedBefore == updatedBefore && other.updatedBefore == updatedBefore &&
other.withArchived == withArchived && other.visibility == visibility &&
other.withDeleted == withDeleted && other.withDeleted == withDeleted &&
other.withExif == withExif && other.withExif == withExif &&
other.withPeople == withPeople && other.withPeople == withPeople &&
@ -416,13 +402,11 @@ class MetadataSearchDto {
(deviceId == null ? 0 : deviceId!.hashCode) + (deviceId == null ? 0 : deviceId!.hashCode) +
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) + (encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
(id == null ? 0 : id!.hashCode) + (id == null ? 0 : id!.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isEncoded == null ? 0 : isEncoded!.hashCode) + (isEncoded == null ? 0 : isEncoded!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) + (isFavorite == null ? 0 : isFavorite!.hashCode) +
(isMotion == null ? 0 : isMotion!.hashCode) + (isMotion == null ? 0 : isMotion!.hashCode) +
(isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) + (isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) +
(isOffline == null ? 0 : isOffline!.hashCode) + (isOffline == null ? 0 : isOffline!.hashCode) +
(isVisible == null ? 0 : isVisible!.hashCode) +
(lensModel == null ? 0 : lensModel!.hashCode) + (lensModel == null ? 0 : lensModel!.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) + (libraryId == null ? 0 : libraryId!.hashCode) +
(make == null ? 0 : make!.hashCode) + (make == null ? 0 : make!.hashCode) +
@ -445,14 +429,14 @@ class MetadataSearchDto {
(type == null ? 0 : type!.hashCode) + (type == null ? 0 : type!.hashCode) +
(updatedAfter == null ? 0 : updatedAfter!.hashCode) + (updatedAfter == null ? 0 : updatedAfter!.hashCode) +
(updatedBefore == null ? 0 : updatedBefore!.hashCode) + (updatedBefore == null ? 0 : updatedBefore!.hashCode) +
(withArchived.hashCode) + (visibility == null ? 0 : visibility!.hashCode) +
(withDeleted == null ? 0 : withDeleted!.hashCode) + (withDeleted == null ? 0 : withDeleted!.hashCode) +
(withExif == null ? 0 : withExif!.hashCode) + (withExif == null ? 0 : withExif!.hashCode) +
(withPeople == null ? 0 : withPeople!.hashCode) + (withPeople == null ? 0 : withPeople!.hashCode) +
(withStacked == null ? 0 : withStacked!.hashCode); (withStacked == null ? 0 : withStacked!.hashCode);
@override @override
String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -506,11 +490,6 @@ class MetadataSearchDto {
} else { } else {
// json[r'id'] = null; // json[r'id'] = null;
} }
if (this.isArchived != null) {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isEncoded != null) { if (this.isEncoded != null) {
json[r'isEncoded'] = this.isEncoded; json[r'isEncoded'] = this.isEncoded;
} else { } else {
@ -536,11 +515,6 @@ class MetadataSearchDto {
} else { } else {
// json[r'isOffline'] = null; // json[r'isOffline'] = null;
} }
if (this.isVisible != null) {
json[r'isVisible'] = this.isVisible;
} else {
// json[r'isVisible'] = null;
}
if (this.lensModel != null) { if (this.lensModel != null) {
json[r'lensModel'] = this.lensModel; json[r'lensModel'] = this.lensModel;
} else { } else {
@ -639,7 +613,11 @@ class MetadataSearchDto {
} else { } else {
// json[r'updatedBefore'] = null; // json[r'updatedBefore'] = null;
} }
json[r'withArchived'] = this.withArchived; if (this.visibility != null) {
json[r'visibility'] = this.visibility;
} else {
// json[r'visibility'] = null;
}
if (this.withDeleted != null) { if (this.withDeleted != null) {
json[r'withDeleted'] = this.withDeleted; json[r'withDeleted'] = this.withDeleted;
} else { } else {
@ -682,13 +660,11 @@ class MetadataSearchDto {
deviceId: mapValueOfType<String>(json, r'deviceId'), deviceId: mapValueOfType<String>(json, r'deviceId'),
encodedVideoPath: mapValueOfType<String>(json, r'encodedVideoPath'), encodedVideoPath: mapValueOfType<String>(json, r'encodedVideoPath'),
id: mapValueOfType<String>(json, r'id'), id: mapValueOfType<String>(json, r'id'),
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isEncoded: mapValueOfType<bool>(json, r'isEncoded'), isEncoded: mapValueOfType<bool>(json, r'isEncoded'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'), isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
isMotion: mapValueOfType<bool>(json, r'isMotion'), isMotion: mapValueOfType<bool>(json, r'isMotion'),
isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'), isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'),
isOffline: mapValueOfType<bool>(json, r'isOffline'), isOffline: mapValueOfType<bool>(json, r'isOffline'),
isVisible: mapValueOfType<bool>(json, r'isVisible'),
lensModel: mapValueOfType<String>(json, r'lensModel'), lensModel: mapValueOfType<String>(json, r'lensModel'),
libraryId: mapValueOfType<String>(json, r'libraryId'), libraryId: mapValueOfType<String>(json, r'libraryId'),
make: mapValueOfType<String>(json, r'make'), make: mapValueOfType<String>(json, r'make'),
@ -715,7 +691,7 @@ class MetadataSearchDto {
type: AssetTypeEnum.fromJson(json[r'type']), type: AssetTypeEnum.fromJson(json[r'type']),
updatedAfter: mapDateTime(json, r'updatedAfter', r''), updatedAfter: mapDateTime(json, r'updatedAfter', r''),
updatedBefore: mapDateTime(json, r'updatedBefore', r''), updatedBefore: mapDateTime(json, r'updatedBefore', r''),
withArchived: mapValueOfType<bool>(json, r'withArchived') ?? false, visibility: AssetVisibility.fromJson(json[r'visibility']),
withDeleted: mapValueOfType<bool>(json, r'withDeleted'), withDeleted: mapValueOfType<bool>(json, r'withDeleted'),
withExif: mapValueOfType<bool>(json, r'withExif'), withExif: mapValueOfType<bool>(json, r'withExif'),
withPeople: mapValueOfType<bool>(json, r'withPeople'), withPeople: mapValueOfType<bool>(json, r'withPeople'),

View file

@ -18,13 +18,11 @@ class RandomSearchDto {
this.createdAfter, this.createdAfter,
this.createdBefore, this.createdBefore,
this.deviceId, this.deviceId,
this.isArchived,
this.isEncoded, this.isEncoded,
this.isFavorite, this.isFavorite,
this.isMotion, this.isMotion,
this.isNotInAlbum, this.isNotInAlbum,
this.isOffline, this.isOffline,
this.isVisible,
this.lensModel, this.lensModel,
this.libraryId, this.libraryId,
this.make, this.make,
@ -41,7 +39,7 @@ class RandomSearchDto {
this.type, this.type,
this.updatedAfter, this.updatedAfter,
this.updatedBefore, this.updatedBefore,
this.withArchived = false, this.visibility,
this.withDeleted, this.withDeleted,
this.withExif, this.withExif,
this.withPeople, this.withPeople,
@ -76,14 +74,6 @@ class RandomSearchDto {
/// ///
String? deviceId; String? deviceId;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isArchived;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated /// does not include a default value (using the "default:" property), however, the generated
@ -124,14 +114,6 @@ class RandomSearchDto {
/// ///
bool? isOffline; bool? isOffline;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isVisible;
String? lensModel; String? lensModel;
String? libraryId; String? libraryId;
@ -228,7 +210,13 @@ class RandomSearchDto {
/// ///
DateTime? updatedBefore; DateTime? updatedBefore;
bool withArchived; ///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
AssetVisibility? visibility;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
@ -269,13 +257,11 @@ class RandomSearchDto {
other.createdAfter == createdAfter && other.createdAfter == createdAfter &&
other.createdBefore == createdBefore && other.createdBefore == createdBefore &&
other.deviceId == deviceId && other.deviceId == deviceId &&
other.isArchived == isArchived &&
other.isEncoded == isEncoded && other.isEncoded == isEncoded &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
other.isMotion == isMotion && other.isMotion == isMotion &&
other.isNotInAlbum == isNotInAlbum && other.isNotInAlbum == isNotInAlbum &&
other.isOffline == isOffline && other.isOffline == isOffline &&
other.isVisible == isVisible &&
other.lensModel == lensModel && other.lensModel == lensModel &&
other.libraryId == libraryId && other.libraryId == libraryId &&
other.make == make && other.make == make &&
@ -292,7 +278,7 @@ class RandomSearchDto {
other.type == type && other.type == type &&
other.updatedAfter == updatedAfter && other.updatedAfter == updatedAfter &&
other.updatedBefore == updatedBefore && other.updatedBefore == updatedBefore &&
other.withArchived == withArchived && other.visibility == visibility &&
other.withDeleted == withDeleted && other.withDeleted == withDeleted &&
other.withExif == withExif && other.withExif == withExif &&
other.withPeople == withPeople && other.withPeople == withPeople &&
@ -306,13 +292,11 @@ class RandomSearchDto {
(createdAfter == null ? 0 : createdAfter!.hashCode) + (createdAfter == null ? 0 : createdAfter!.hashCode) +
(createdBefore == null ? 0 : createdBefore!.hashCode) + (createdBefore == null ? 0 : createdBefore!.hashCode) +
(deviceId == null ? 0 : deviceId!.hashCode) + (deviceId == null ? 0 : deviceId!.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isEncoded == null ? 0 : isEncoded!.hashCode) + (isEncoded == null ? 0 : isEncoded!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) + (isFavorite == null ? 0 : isFavorite!.hashCode) +
(isMotion == null ? 0 : isMotion!.hashCode) + (isMotion == null ? 0 : isMotion!.hashCode) +
(isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) + (isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) +
(isOffline == null ? 0 : isOffline!.hashCode) + (isOffline == null ? 0 : isOffline!.hashCode) +
(isVisible == null ? 0 : isVisible!.hashCode) +
(lensModel == null ? 0 : lensModel!.hashCode) + (lensModel == null ? 0 : lensModel!.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) + (libraryId == null ? 0 : libraryId!.hashCode) +
(make == null ? 0 : make!.hashCode) + (make == null ? 0 : make!.hashCode) +
@ -329,14 +313,14 @@ class RandomSearchDto {
(type == null ? 0 : type!.hashCode) + (type == null ? 0 : type!.hashCode) +
(updatedAfter == null ? 0 : updatedAfter!.hashCode) + (updatedAfter == null ? 0 : updatedAfter!.hashCode) +
(updatedBefore == null ? 0 : updatedBefore!.hashCode) + (updatedBefore == null ? 0 : updatedBefore!.hashCode) +
(withArchived.hashCode) + (visibility == null ? 0 : visibility!.hashCode) +
(withDeleted == null ? 0 : withDeleted!.hashCode) + (withDeleted == null ? 0 : withDeleted!.hashCode) +
(withExif == null ? 0 : withExif!.hashCode) + (withExif == null ? 0 : withExif!.hashCode) +
(withPeople == null ? 0 : withPeople!.hashCode) + (withPeople == null ? 0 : withPeople!.hashCode) +
(withStacked == null ? 0 : withStacked!.hashCode); (withStacked == null ? 0 : withStacked!.hashCode);
@override @override
String toString() => 'RandomSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, personIds=$personIds, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; String toString() => 'RandomSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, personIds=$personIds, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -365,11 +349,6 @@ class RandomSearchDto {
} else { } else {
// json[r'deviceId'] = null; // json[r'deviceId'] = null;
} }
if (this.isArchived != null) {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isEncoded != null) { if (this.isEncoded != null) {
json[r'isEncoded'] = this.isEncoded; json[r'isEncoded'] = this.isEncoded;
} else { } else {
@ -395,11 +374,6 @@ class RandomSearchDto {
} else { } else {
// json[r'isOffline'] = null; // json[r'isOffline'] = null;
} }
if (this.isVisible != null) {
json[r'isVisible'] = this.isVisible;
} else {
// json[r'isVisible'] = null;
}
if (this.lensModel != null) { if (this.lensModel != null) {
json[r'lensModel'] = this.lensModel; json[r'lensModel'] = this.lensModel;
} else { } else {
@ -472,7 +446,11 @@ class RandomSearchDto {
} else { } else {
// json[r'updatedBefore'] = null; // json[r'updatedBefore'] = null;
} }
json[r'withArchived'] = this.withArchived; if (this.visibility != null) {
json[r'visibility'] = this.visibility;
} else {
// json[r'visibility'] = null;
}
if (this.withDeleted != null) { if (this.withDeleted != null) {
json[r'withDeleted'] = this.withDeleted; json[r'withDeleted'] = this.withDeleted;
} else { } else {
@ -510,13 +488,11 @@ class RandomSearchDto {
createdAfter: mapDateTime(json, r'createdAfter', r''), createdAfter: mapDateTime(json, r'createdAfter', r''),
createdBefore: mapDateTime(json, r'createdBefore', r''), createdBefore: mapDateTime(json, r'createdBefore', r''),
deviceId: mapValueOfType<String>(json, r'deviceId'), deviceId: mapValueOfType<String>(json, r'deviceId'),
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isEncoded: mapValueOfType<bool>(json, r'isEncoded'), isEncoded: mapValueOfType<bool>(json, r'isEncoded'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'), isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
isMotion: mapValueOfType<bool>(json, r'isMotion'), isMotion: mapValueOfType<bool>(json, r'isMotion'),
isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'), isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'),
isOffline: mapValueOfType<bool>(json, r'isOffline'), isOffline: mapValueOfType<bool>(json, r'isOffline'),
isVisible: mapValueOfType<bool>(json, r'isVisible'),
lensModel: mapValueOfType<String>(json, r'lensModel'), lensModel: mapValueOfType<String>(json, r'lensModel'),
libraryId: mapValueOfType<String>(json, r'libraryId'), libraryId: mapValueOfType<String>(json, r'libraryId'),
make: mapValueOfType<String>(json, r'make'), make: mapValueOfType<String>(json, r'make'),
@ -537,7 +513,7 @@ class RandomSearchDto {
type: AssetTypeEnum.fromJson(json[r'type']), type: AssetTypeEnum.fromJson(json[r'type']),
updatedAfter: mapDateTime(json, r'updatedAfter', r''), updatedAfter: mapDateTime(json, r'updatedAfter', r''),
updatedBefore: mapDateTime(json, r'updatedBefore', r''), updatedBefore: mapDateTime(json, r'updatedBefore', r''),
withArchived: mapValueOfType<bool>(json, r'withArchived') ?? false, visibility: AssetVisibility.fromJson(json[r'visibility']),
withDeleted: mapValueOfType<bool>(json, r'withDeleted'), withDeleted: mapValueOfType<bool>(json, r'withDeleted'),
withExif: mapValueOfType<bool>(json, r'withExif'), withExif: mapValueOfType<bool>(json, r'withExif'),
withPeople: mapValueOfType<bool>(json, r'withPeople'), withPeople: mapValueOfType<bool>(json, r'withPeople'),

View file

@ -18,13 +18,11 @@ class SmartSearchDto {
this.createdAfter, this.createdAfter,
this.createdBefore, this.createdBefore,
this.deviceId, this.deviceId,
this.isArchived,
this.isEncoded, this.isEncoded,
this.isFavorite, this.isFavorite,
this.isMotion, this.isMotion,
this.isNotInAlbum, this.isNotInAlbum,
this.isOffline, this.isOffline,
this.isVisible,
this.language, this.language,
this.lensModel, this.lensModel,
this.libraryId, this.libraryId,
@ -44,7 +42,7 @@ class SmartSearchDto {
this.type, this.type,
this.updatedAfter, this.updatedAfter,
this.updatedBefore, this.updatedBefore,
this.withArchived = false, this.visibility,
this.withDeleted, this.withDeleted,
this.withExif, this.withExif,
}); });
@ -77,14 +75,6 @@ class SmartSearchDto {
/// ///
String? deviceId; String? deviceId;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isArchived;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated /// does not include a default value (using the "default:" property), however, the generated
@ -125,14 +115,6 @@ class SmartSearchDto {
/// ///
bool? isOffline; bool? isOffline;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isVisible;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated /// does not include a default value (using the "default:" property), however, the generated
@ -248,7 +230,13 @@ class SmartSearchDto {
/// ///
DateTime? updatedBefore; DateTime? updatedBefore;
bool withArchived; ///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
AssetVisibility? visibility;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
@ -273,13 +261,11 @@ class SmartSearchDto {
other.createdAfter == createdAfter && other.createdAfter == createdAfter &&
other.createdBefore == createdBefore && other.createdBefore == createdBefore &&
other.deviceId == deviceId && other.deviceId == deviceId &&
other.isArchived == isArchived &&
other.isEncoded == isEncoded && other.isEncoded == isEncoded &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
other.isMotion == isMotion && other.isMotion == isMotion &&
other.isNotInAlbum == isNotInAlbum && other.isNotInAlbum == isNotInAlbum &&
other.isOffline == isOffline && other.isOffline == isOffline &&
other.isVisible == isVisible &&
other.language == language && other.language == language &&
other.lensModel == lensModel && other.lensModel == lensModel &&
other.libraryId == libraryId && other.libraryId == libraryId &&
@ -299,7 +285,7 @@ class SmartSearchDto {
other.type == type && other.type == type &&
other.updatedAfter == updatedAfter && other.updatedAfter == updatedAfter &&
other.updatedBefore == updatedBefore && other.updatedBefore == updatedBefore &&
other.withArchived == withArchived && other.visibility == visibility &&
other.withDeleted == withDeleted && other.withDeleted == withDeleted &&
other.withExif == withExif; other.withExif == withExif;
@ -311,13 +297,11 @@ class SmartSearchDto {
(createdAfter == null ? 0 : createdAfter!.hashCode) + (createdAfter == null ? 0 : createdAfter!.hashCode) +
(createdBefore == null ? 0 : createdBefore!.hashCode) + (createdBefore == null ? 0 : createdBefore!.hashCode) +
(deviceId == null ? 0 : deviceId!.hashCode) + (deviceId == null ? 0 : deviceId!.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isEncoded == null ? 0 : isEncoded!.hashCode) + (isEncoded == null ? 0 : isEncoded!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) + (isFavorite == null ? 0 : isFavorite!.hashCode) +
(isMotion == null ? 0 : isMotion!.hashCode) + (isMotion == null ? 0 : isMotion!.hashCode) +
(isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) + (isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) +
(isOffline == null ? 0 : isOffline!.hashCode) + (isOffline == null ? 0 : isOffline!.hashCode) +
(isVisible == null ? 0 : isVisible!.hashCode) +
(language == null ? 0 : language!.hashCode) + (language == null ? 0 : language!.hashCode) +
(lensModel == null ? 0 : lensModel!.hashCode) + (lensModel == null ? 0 : lensModel!.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) + (libraryId == null ? 0 : libraryId!.hashCode) +
@ -337,12 +321,12 @@ class SmartSearchDto {
(type == null ? 0 : type!.hashCode) + (type == null ? 0 : type!.hashCode) +
(updatedAfter == null ? 0 : updatedAfter!.hashCode) + (updatedAfter == null ? 0 : updatedAfter!.hashCode) +
(updatedBefore == null ? 0 : updatedBefore!.hashCode) + (updatedBefore == null ? 0 : updatedBefore!.hashCode) +
(withArchived.hashCode) + (visibility == null ? 0 : visibility!.hashCode) +
(withDeleted == null ? 0 : withDeleted!.hashCode) + (withDeleted == null ? 0 : withDeleted!.hashCode) +
(withExif == null ? 0 : withExif!.hashCode); (withExif == null ? 0 : withExif!.hashCode);
@override @override
String toString() => 'SmartSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, page=$page, personIds=$personIds, query=$query, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif]'; String toString() => 'SmartSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, page=$page, personIds=$personIds, query=$query, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -371,11 +355,6 @@ class SmartSearchDto {
} else { } else {
// json[r'deviceId'] = null; // json[r'deviceId'] = null;
} }
if (this.isArchived != null) {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isEncoded != null) { if (this.isEncoded != null) {
json[r'isEncoded'] = this.isEncoded; json[r'isEncoded'] = this.isEncoded;
} else { } else {
@ -401,11 +380,6 @@ class SmartSearchDto {
} else { } else {
// json[r'isOffline'] = null; // json[r'isOffline'] = null;
} }
if (this.isVisible != null) {
json[r'isVisible'] = this.isVisible;
} else {
// json[r'isVisible'] = null;
}
if (this.language != null) { if (this.language != null) {
json[r'language'] = this.language; json[r'language'] = this.language;
} else { } else {
@ -489,7 +463,11 @@ class SmartSearchDto {
} else { } else {
// json[r'updatedBefore'] = null; // json[r'updatedBefore'] = null;
} }
json[r'withArchived'] = this.withArchived; if (this.visibility != null) {
json[r'visibility'] = this.visibility;
} else {
// json[r'visibility'] = null;
}
if (this.withDeleted != null) { if (this.withDeleted != null) {
json[r'withDeleted'] = this.withDeleted; json[r'withDeleted'] = this.withDeleted;
} else { } else {
@ -517,13 +495,11 @@ class SmartSearchDto {
createdAfter: mapDateTime(json, r'createdAfter', r''), createdAfter: mapDateTime(json, r'createdAfter', r''),
createdBefore: mapDateTime(json, r'createdBefore', r''), createdBefore: mapDateTime(json, r'createdBefore', r''),
deviceId: mapValueOfType<String>(json, r'deviceId'), deviceId: mapValueOfType<String>(json, r'deviceId'),
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isEncoded: mapValueOfType<bool>(json, r'isEncoded'), isEncoded: mapValueOfType<bool>(json, r'isEncoded'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'), isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
isMotion: mapValueOfType<bool>(json, r'isMotion'), isMotion: mapValueOfType<bool>(json, r'isMotion'),
isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'), isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'),
isOffline: mapValueOfType<bool>(json, r'isOffline'), isOffline: mapValueOfType<bool>(json, r'isOffline'),
isVisible: mapValueOfType<bool>(json, r'isVisible'),
language: mapValueOfType<String>(json, r'language'), language: mapValueOfType<String>(json, r'language'),
lensModel: mapValueOfType<String>(json, r'lensModel'), lensModel: mapValueOfType<String>(json, r'lensModel'),
libraryId: mapValueOfType<String>(json, r'libraryId'), libraryId: mapValueOfType<String>(json, r'libraryId'),
@ -547,7 +523,7 @@ class SmartSearchDto {
type: AssetTypeEnum.fromJson(json[r'type']), type: AssetTypeEnum.fromJson(json[r'type']),
updatedAfter: mapDateTime(json, r'updatedAfter', r''), updatedAfter: mapDateTime(json, r'updatedAfter', r''),
updatedBefore: mapDateTime(json, r'updatedBefore', r''), updatedBefore: mapDateTime(json, r'updatedBefore', r''),
withArchived: mapValueOfType<bool>(json, r'withArchived') ?? false, visibility: AssetVisibility.fromJson(json[r'visibility']),
withDeleted: mapValueOfType<bool>(json, r'withDeleted'), withDeleted: mapValueOfType<bool>(json, r'withDeleted'),
withExif: mapValueOfType<bool>(json, r'withExif'), withExif: mapValueOfType<bool>(json, r'withExif'),
); );

View file

@ -19,11 +19,11 @@ class SyncAssetV1 {
required this.fileModifiedAt, required this.fileModifiedAt,
required this.id, required this.id,
required this.isFavorite, required this.isFavorite,
required this.isVisible,
required this.localDateTime, required this.localDateTime,
required this.ownerId, required this.ownerId,
required this.thumbhash, required this.thumbhash,
required this.type, required this.type,
required this.visibility,
}); });
String checksum; String checksum;
@ -38,8 +38,6 @@ class SyncAssetV1 {
bool isFavorite; bool isFavorite;
bool isVisible;
DateTime? localDateTime; DateTime? localDateTime;
String ownerId; String ownerId;
@ -48,6 +46,8 @@ class SyncAssetV1 {
SyncAssetV1TypeEnum type; SyncAssetV1TypeEnum type;
SyncAssetV1VisibilityEnum visibility;
@override @override
bool operator ==(Object other) => identical(this, other) || other is SyncAssetV1 && bool operator ==(Object other) => identical(this, other) || other is SyncAssetV1 &&
other.checksum == checksum && other.checksum == checksum &&
@ -56,11 +56,11 @@ class SyncAssetV1 {
other.fileModifiedAt == fileModifiedAt && other.fileModifiedAt == fileModifiedAt &&
other.id == id && other.id == id &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
other.isVisible == isVisible &&
other.localDateTime == localDateTime && other.localDateTime == localDateTime &&
other.ownerId == ownerId && other.ownerId == ownerId &&
other.thumbhash == thumbhash && other.thumbhash == thumbhash &&
other.type == type; other.type == type &&
other.visibility == visibility;
@override @override
int get hashCode => int get hashCode =>
@ -71,14 +71,14 @@ class SyncAssetV1 {
(fileModifiedAt == null ? 0 : fileModifiedAt!.hashCode) + (fileModifiedAt == null ? 0 : fileModifiedAt!.hashCode) +
(id.hashCode) + (id.hashCode) +
(isFavorite.hashCode) + (isFavorite.hashCode) +
(isVisible.hashCode) +
(localDateTime == null ? 0 : localDateTime!.hashCode) + (localDateTime == null ? 0 : localDateTime!.hashCode) +
(ownerId.hashCode) + (ownerId.hashCode) +
(thumbhash == null ? 0 : thumbhash!.hashCode) + (thumbhash == null ? 0 : thumbhash!.hashCode) +
(type.hashCode); (type.hashCode) +
(visibility.hashCode);
@override @override
String toString() => 'SyncAssetV1[checksum=$checksum, deletedAt=$deletedAt, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, id=$id, isFavorite=$isFavorite, isVisible=$isVisible, localDateTime=$localDateTime, ownerId=$ownerId, thumbhash=$thumbhash, type=$type]'; String toString() => 'SyncAssetV1[checksum=$checksum, deletedAt=$deletedAt, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, id=$id, isFavorite=$isFavorite, localDateTime=$localDateTime, ownerId=$ownerId, thumbhash=$thumbhash, type=$type, visibility=$visibility]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -100,7 +100,6 @@ class SyncAssetV1 {
} }
json[r'id'] = this.id; json[r'id'] = this.id;
json[r'isFavorite'] = this.isFavorite; json[r'isFavorite'] = this.isFavorite;
json[r'isVisible'] = this.isVisible;
if (this.localDateTime != null) { if (this.localDateTime != null) {
json[r'localDateTime'] = this.localDateTime!.toUtc().toIso8601String(); json[r'localDateTime'] = this.localDateTime!.toUtc().toIso8601String();
} else { } else {
@ -113,6 +112,7 @@ class SyncAssetV1 {
// json[r'thumbhash'] = null; // json[r'thumbhash'] = null;
} }
json[r'type'] = this.type; json[r'type'] = this.type;
json[r'visibility'] = this.visibility;
return json; return json;
} }
@ -131,11 +131,11 @@ class SyncAssetV1 {
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r''), fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r''),
id: mapValueOfType<String>(json, r'id')!, id: mapValueOfType<String>(json, r'id')!,
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!, isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
isVisible: mapValueOfType<bool>(json, r'isVisible')!,
localDateTime: mapDateTime(json, r'localDateTime', r''), localDateTime: mapDateTime(json, r'localDateTime', r''),
ownerId: mapValueOfType<String>(json, r'ownerId')!, ownerId: mapValueOfType<String>(json, r'ownerId')!,
thumbhash: mapValueOfType<String>(json, r'thumbhash'), thumbhash: mapValueOfType<String>(json, r'thumbhash'),
type: SyncAssetV1TypeEnum.fromJson(json[r'type'])!, type: SyncAssetV1TypeEnum.fromJson(json[r'type'])!,
visibility: SyncAssetV1VisibilityEnum.fromJson(json[r'visibility'])!,
); );
} }
return null; return null;
@ -189,11 +189,11 @@ class SyncAssetV1 {
'fileModifiedAt', 'fileModifiedAt',
'id', 'id',
'isFavorite', 'isFavorite',
'isVisible',
'localDateTime', 'localDateTime',
'ownerId', 'ownerId',
'thumbhash', 'thumbhash',
'type', 'type',
'visibility',
}; };
} }
@ -277,3 +277,80 @@ class SyncAssetV1TypeEnumTypeTransformer {
} }
class SyncAssetV1VisibilityEnum {
/// Instantiate a new enum with the provided [value].
const SyncAssetV1VisibilityEnum._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const archive = SyncAssetV1VisibilityEnum._(r'archive');
static const timeline = SyncAssetV1VisibilityEnum._(r'timeline');
static const hidden = SyncAssetV1VisibilityEnum._(r'hidden');
/// List of all possible values in this [enum][SyncAssetV1VisibilityEnum].
static const values = <SyncAssetV1VisibilityEnum>[
archive,
timeline,
hidden,
];
static SyncAssetV1VisibilityEnum? fromJson(dynamic value) => SyncAssetV1VisibilityEnumTypeTransformer().decode(value);
static List<SyncAssetV1VisibilityEnum> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SyncAssetV1VisibilityEnum>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SyncAssetV1VisibilityEnum.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [SyncAssetV1VisibilityEnum] to String,
/// and [decode] dynamic data back to [SyncAssetV1VisibilityEnum].
class SyncAssetV1VisibilityEnumTypeTransformer {
factory SyncAssetV1VisibilityEnumTypeTransformer() => _instance ??= const SyncAssetV1VisibilityEnumTypeTransformer._();
const SyncAssetV1VisibilityEnumTypeTransformer._();
String encode(SyncAssetV1VisibilityEnum data) => data.value;
/// Decodes a [dynamic value][data] to a SyncAssetV1VisibilityEnum.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
SyncAssetV1VisibilityEnum? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'archive': return SyncAssetV1VisibilityEnum.archive;
case r'timeline': return SyncAssetV1VisibilityEnum.timeline;
case r'hidden': return SyncAssetV1VisibilityEnum.hidden;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [SyncAssetV1VisibilityEnumTypeTransformer] instance.
static SyncAssetV1VisibilityEnumTypeTransformer? _instance;
}

View file

@ -15,12 +15,12 @@ class UpdateAssetDto {
UpdateAssetDto({ UpdateAssetDto({
this.dateTimeOriginal, this.dateTimeOriginal,
this.description, this.description,
this.isArchived,
this.isFavorite, this.isFavorite,
this.latitude, this.latitude,
this.livePhotoVideoId, this.livePhotoVideoId,
this.longitude, this.longitude,
this.rating, this.rating,
this.visibility,
}); });
/// ///
@ -39,14 +39,6 @@ class UpdateAssetDto {
/// ///
String? description; String? description;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isArchived;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated /// does not include a default value (using the "default:" property), however, the generated
@ -83,31 +75,39 @@ class UpdateAssetDto {
/// ///
num? rating; num? rating;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
AssetVisibility? visibility;
@override @override
bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto && bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto &&
other.dateTimeOriginal == dateTimeOriginal && other.dateTimeOriginal == dateTimeOriginal &&
other.description == description && other.description == description &&
other.isArchived == isArchived &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
other.latitude == latitude && other.latitude == latitude &&
other.livePhotoVideoId == livePhotoVideoId && other.livePhotoVideoId == livePhotoVideoId &&
other.longitude == longitude && other.longitude == longitude &&
other.rating == rating; other.rating == rating &&
other.visibility == visibility;
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
(description == null ? 0 : description!.hashCode) + (description == null ? 0 : description!.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) + (isFavorite == null ? 0 : isFavorite!.hashCode) +
(latitude == null ? 0 : latitude!.hashCode) + (latitude == null ? 0 : latitude!.hashCode) +
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
(longitude == null ? 0 : longitude!.hashCode) + (longitude == null ? 0 : longitude!.hashCode) +
(rating == null ? 0 : rating!.hashCode); (rating == null ? 0 : rating!.hashCode) +
(visibility == null ? 0 : visibility!.hashCode);
@override @override
String toString() => 'UpdateAssetDto[dateTimeOriginal=$dateTimeOriginal, description=$description, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, livePhotoVideoId=$livePhotoVideoId, longitude=$longitude, rating=$rating]'; String toString() => 'UpdateAssetDto[dateTimeOriginal=$dateTimeOriginal, description=$description, isFavorite=$isFavorite, latitude=$latitude, livePhotoVideoId=$livePhotoVideoId, longitude=$longitude, rating=$rating, visibility=$visibility]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -121,11 +121,6 @@ class UpdateAssetDto {
} else { } else {
// json[r'description'] = null; // json[r'description'] = null;
} }
if (this.isArchived != null) {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isFavorite != null) { if (this.isFavorite != null) {
json[r'isFavorite'] = this.isFavorite; json[r'isFavorite'] = this.isFavorite;
} else { } else {
@ -151,6 +146,11 @@ class UpdateAssetDto {
} else { } else {
// json[r'rating'] = null; // json[r'rating'] = null;
} }
if (this.visibility != null) {
json[r'visibility'] = this.visibility;
} else {
// json[r'visibility'] = null;
}
return json; return json;
} }
@ -165,12 +165,12 @@ class UpdateAssetDto {
return UpdateAssetDto( return UpdateAssetDto(
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'), dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
description: mapValueOfType<String>(json, r'description'), description: mapValueOfType<String>(json, r'description'),
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'), isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
latitude: num.parse('${json[r'latitude']}'), latitude: num.parse('${json[r'latitude']}'),
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'), livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
longitude: num.parse('${json[r'longitude']}'), longitude: num.parse('${json[r'longitude']}'),
rating: num.parse('${json[r'rating']}'), rating: num.parse('${json[r'rating']}'),
visibility: AssetVisibility.fromJson(json[r'visibility']),
); );
} }
return null; return null;

View file

@ -1781,14 +1781,6 @@
"get": { "get": {
"operationId": "getAssetStatistics", "operationId": "getAssetStatistics",
"parameters": [ "parameters": [
{
"name": "isArchived",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{ {
"name": "isFavorite", "name": "isFavorite",
"required": false, "required": false,
@ -1804,6 +1796,14 @@
"schema": { "schema": {
"type": "boolean" "type": "boolean"
} }
},
{
"name": "visibility",
"required": false,
"in": "query",
"schema": {
"$ref": "#/components/schemas/AssetVisibility"
}
} }
], ],
"responses": { "responses": {
@ -6909,14 +6909,6 @@
"type": "string" "type": "string"
} }
}, },
{
"name": "isArchived",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{ {
"name": "isFavorite", "name": "isFavorite",
"required": false, "required": false,
@ -6992,6 +6984,14 @@
"type": "string" "type": "string"
} }
}, },
{
"name": "visibility",
"required": false,
"in": "query",
"schema": {
"$ref": "#/components/schemas/AssetVisibility"
}
},
{ {
"name": "withPartners", "name": "withPartners",
"required": false, "required": false,
@ -7053,14 +7053,6 @@
"type": "string" "type": "string"
} }
}, },
{
"name": "isArchived",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{ {
"name": "isFavorite", "name": "isFavorite",
"required": false, "required": false,
@ -7128,6 +7120,14 @@
"type": "string" "type": "string"
} }
}, },
{
"name": "visibility",
"required": false,
"in": "query",
"schema": {
"$ref": "#/components/schemas/AssetVisibility"
}
},
{ {
"name": "withPartners", "name": "withPartners",
"required": false, "required": false,
@ -8273,9 +8273,6 @@
}, },
"type": "array" "type": "array"
}, },
"isArchived": {
"type": "boolean"
},
"isFavorite": { "isFavorite": {
"type": "boolean" "type": "boolean"
}, },
@ -8289,6 +8286,13 @@
"maximum": 5, "maximum": 5,
"minimum": -1, "minimum": -1,
"type": "number" "type": "number"
},
"visibility": {
"allOf": [
{
"$ref": "#/components/schemas/AssetVisibility"
}
]
} }
}, },
"required": [ "required": [
@ -8713,15 +8717,9 @@
"format": "date-time", "format": "date-time",
"type": "string" "type": "string"
}, },
"isArchived": {
"type": "boolean"
},
"isFavorite": { "isFavorite": {
"type": "boolean" "type": "boolean"
}, },
"isVisible": {
"type": "boolean"
},
"livePhotoVideoId": { "livePhotoVideoId": {
"format": "uuid", "format": "uuid",
"type": "string" "type": "string"
@ -8729,6 +8727,13 @@
"sidecarData": { "sidecarData": {
"format": "binary", "format": "binary",
"type": "string" "type": "string"
},
"visibility": {
"allOf": [
{
"$ref": "#/components/schemas/AssetVisibility"
}
]
} }
}, },
"required": [ "required": [
@ -9009,6 +9014,14 @@
], ],
"type": "string" "type": "string"
}, },
"AssetVisibility": {
"enum": [
"archive",
"timeline",
"hidden"
],
"type": "string"
},
"AudioCodec": { "AudioCodec": {
"enum": [ "enum": [
"mp3", "mp3",
@ -10204,9 +10217,6 @@
"format": "uuid", "format": "uuid",
"type": "string" "type": "string"
}, },
"isArchived": {
"type": "boolean"
},
"isEncoded": { "isEncoded": {
"type": "boolean" "type": "boolean"
}, },
@ -10222,9 +10232,6 @@
"isOffline": { "isOffline": {
"type": "boolean" "type": "boolean"
}, },
"isVisible": {
"type": "boolean"
},
"lensModel": { "lensModel": {
"nullable": true, "nullable": true,
"type": "string" "type": "string"
@ -10324,9 +10331,12 @@
"format": "date-time", "format": "date-time",
"type": "string" "type": "string"
}, },
"withArchived": { "visibility": {
"default": false, "allOf": [
"type": "boolean" {
"$ref": "#/components/schemas/AssetVisibility"
}
]
}, },
"withDeleted": { "withDeleted": {
"type": "boolean" "type": "boolean"
@ -11041,9 +11051,6 @@
"deviceId": { "deviceId": {
"type": "string" "type": "string"
}, },
"isArchived": {
"type": "boolean"
},
"isEncoded": { "isEncoded": {
"type": "boolean" "type": "boolean"
}, },
@ -11059,9 +11066,6 @@
"isOffline": { "isOffline": {
"type": "boolean" "type": "boolean"
}, },
"isVisible": {
"type": "boolean"
},
"lensModel": { "lensModel": {
"nullable": true, "nullable": true,
"type": "string" "type": "string"
@ -11137,9 +11141,12 @@
"format": "date-time", "format": "date-time",
"type": "string" "type": "string"
}, },
"withArchived": { "visibility": {
"default": false, "allOf": [
"type": "boolean" {
"$ref": "#/components/schemas/AssetVisibility"
}
]
}, },
"withDeleted": { "withDeleted": {
"type": "boolean" "type": "boolean"
@ -11989,9 +11996,6 @@
"deviceId": { "deviceId": {
"type": "string" "type": "string"
}, },
"isArchived": {
"type": "boolean"
},
"isEncoded": { "isEncoded": {
"type": "boolean" "type": "boolean"
}, },
@ -12007,9 +12011,6 @@
"isOffline": { "isOffline": {
"type": "boolean" "type": "boolean"
}, },
"isVisible": {
"type": "boolean"
},
"language": { "language": {
"type": "string" "type": "string"
}, },
@ -12095,9 +12096,12 @@
"format": "date-time", "format": "date-time",
"type": "string" "type": "string"
}, },
"withArchived": { "visibility": {
"default": false, "allOf": [
"type": "boolean" {
"$ref": "#/components/schemas/AssetVisibility"
}
]
}, },
"withDeleted": { "withDeleted": {
"type": "boolean" "type": "boolean"
@ -12381,9 +12385,6 @@
"isFavorite": { "isFavorite": {
"type": "boolean" "type": "boolean"
}, },
"isVisible": {
"type": "boolean"
},
"localDateTime": { "localDateTime": {
"format": "date-time", "format": "date-time",
"nullable": true, "nullable": true,
@ -12404,6 +12405,14 @@
"OTHER" "OTHER"
], ],
"type": "string" "type": "string"
},
"visibility": {
"enum": [
"archive",
"timeline",
"hidden"
],
"type": "string"
} }
}, },
"required": [ "required": [
@ -12413,11 +12422,11 @@
"fileModifiedAt", "fileModifiedAt",
"id", "id",
"isFavorite", "isFavorite",
"isVisible",
"localDateTime", "localDateTime",
"ownerId", "ownerId",
"thumbhash", "thumbhash",
"type" "type",
"visibility"
], ],
"type": "object" "type": "object"
}, },
@ -13671,9 +13680,6 @@
"description": { "description": {
"type": "string" "type": "string"
}, },
"isArchived": {
"type": "boolean"
},
"isFavorite": { "isFavorite": {
"type": "boolean" "type": "boolean"
}, },
@ -13692,6 +13698,13 @@
"maximum": 5, "maximum": 5,
"minimum": -1, "minimum": -1,
"type": "number" "type": "number"
},
"visibility": {
"allOf": [
{
"$ref": "#/components/schemas/AssetVisibility"
}
]
} }
}, },
"type": "object" "type": "object"

View file

@ -413,11 +413,10 @@ export type AssetMediaCreateDto = {
duration?: string; duration?: string;
fileCreatedAt: string; fileCreatedAt: string;
fileModifiedAt: string; fileModifiedAt: string;
isArchived?: boolean;
isFavorite?: boolean; isFavorite?: boolean;
isVisible?: boolean;
livePhotoVideoId?: string; livePhotoVideoId?: string;
sidecarData?: Blob; sidecarData?: Blob;
visibility?: AssetVisibility;
}; };
export type AssetMediaResponseDto = { export type AssetMediaResponseDto = {
id: string; id: string;
@ -427,11 +426,11 @@ export type AssetBulkUpdateDto = {
dateTimeOriginal?: string; dateTimeOriginal?: string;
duplicateId?: string | null; duplicateId?: string | null;
ids: string[]; ids: string[];
isArchived?: boolean;
isFavorite?: boolean; isFavorite?: boolean;
latitude?: number; latitude?: number;
longitude?: number; longitude?: number;
rating?: number; rating?: number;
visibility?: AssetVisibility;
}; };
export type AssetBulkUploadCheckItem = { export type AssetBulkUploadCheckItem = {
/** base64 or hex encoded sha1 hash */ /** base64 or hex encoded sha1 hash */
@ -470,12 +469,12 @@ export type AssetStatsResponseDto = {
export type UpdateAssetDto = { export type UpdateAssetDto = {
dateTimeOriginal?: string; dateTimeOriginal?: string;
description?: string; description?: string;
isArchived?: boolean;
isFavorite?: boolean; isFavorite?: boolean;
latitude?: number; latitude?: number;
livePhotoVideoId?: string | null; livePhotoVideoId?: string | null;
longitude?: number; longitude?: number;
rating?: number; rating?: number;
visibility?: AssetVisibility;
}; };
export type AssetMediaReplaceDto = { export type AssetMediaReplaceDto = {
assetData: Blob; assetData: Blob;
@ -815,13 +814,11 @@ export type MetadataSearchDto = {
deviceId?: string; deviceId?: string;
encodedVideoPath?: string; encodedVideoPath?: string;
id?: string; id?: string;
isArchived?: boolean;
isEncoded?: boolean; isEncoded?: boolean;
isFavorite?: boolean; isFavorite?: boolean;
isMotion?: boolean; isMotion?: boolean;
isNotInAlbum?: boolean; isNotInAlbum?: boolean;
isOffline?: boolean; isOffline?: boolean;
isVisible?: boolean;
lensModel?: string | null; lensModel?: string | null;
libraryId?: string | null; libraryId?: string | null;
make?: string; make?: string;
@ -844,7 +841,7 @@ export type MetadataSearchDto = {
"type"?: AssetTypeEnum; "type"?: AssetTypeEnum;
updatedAfter?: string; updatedAfter?: string;
updatedBefore?: string; updatedBefore?: string;
withArchived?: boolean; visibility?: AssetVisibility;
withDeleted?: boolean; withDeleted?: boolean;
withExif?: boolean; withExif?: boolean;
withPeople?: boolean; withPeople?: boolean;
@ -888,13 +885,11 @@ export type RandomSearchDto = {
createdAfter?: string; createdAfter?: string;
createdBefore?: string; createdBefore?: string;
deviceId?: string; deviceId?: string;
isArchived?: boolean;
isEncoded?: boolean; isEncoded?: boolean;
isFavorite?: boolean; isFavorite?: boolean;
isMotion?: boolean; isMotion?: boolean;
isNotInAlbum?: boolean; isNotInAlbum?: boolean;
isOffline?: boolean; isOffline?: boolean;
isVisible?: boolean;
lensModel?: string | null; lensModel?: string | null;
libraryId?: string | null; libraryId?: string | null;
make?: string; make?: string;
@ -911,7 +906,7 @@ export type RandomSearchDto = {
"type"?: AssetTypeEnum; "type"?: AssetTypeEnum;
updatedAfter?: string; updatedAfter?: string;
updatedBefore?: string; updatedBefore?: string;
withArchived?: boolean; visibility?: AssetVisibility;
withDeleted?: boolean; withDeleted?: boolean;
withExif?: boolean; withExif?: boolean;
withPeople?: boolean; withPeople?: boolean;
@ -923,13 +918,11 @@ export type SmartSearchDto = {
createdAfter?: string; createdAfter?: string;
createdBefore?: string; createdBefore?: string;
deviceId?: string; deviceId?: string;
isArchived?: boolean;
isEncoded?: boolean; isEncoded?: boolean;
isFavorite?: boolean; isFavorite?: boolean;
isMotion?: boolean; isMotion?: boolean;
isNotInAlbum?: boolean; isNotInAlbum?: boolean;
isOffline?: boolean; isOffline?: boolean;
isVisible?: boolean;
language?: string; language?: string;
lensModel?: string | null; lensModel?: string | null;
libraryId?: string | null; libraryId?: string | null;
@ -949,7 +942,7 @@ export type SmartSearchDto = {
"type"?: AssetTypeEnum; "type"?: AssetTypeEnum;
updatedAfter?: string; updatedAfter?: string;
updatedBefore?: string; updatedBefore?: string;
withArchived?: boolean; visibility?: AssetVisibility;
withDeleted?: boolean; withDeleted?: boolean;
withExif?: boolean; withExif?: boolean;
}; };
@ -1877,18 +1870,18 @@ export function getRandom({ count }: {
...opts ...opts
})); }));
} }
export function getAssetStatistics({ isArchived, isFavorite, isTrashed }: { export function getAssetStatistics({ isFavorite, isTrashed, visibility }: {
isArchived?: boolean;
isFavorite?: boolean; isFavorite?: boolean;
isTrashed?: boolean; isTrashed?: boolean;
visibility?: AssetVisibility;
}, opts?: Oazapfts.RequestOpts) { }, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{ return oazapfts.ok(oazapfts.fetchJson<{
status: 200; status: 200;
data: AssetStatsResponseDto; data: AssetStatsResponseDto;
}>(`/assets/statistics${QS.query(QS.explode({ }>(`/assets/statistics${QS.query(QS.explode({
isArchived,
isFavorite, isFavorite,
isTrashed isTrashed,
visibility
}))}`, { }))}`, {
...opts ...opts
})); }));
@ -3242,9 +3235,8 @@ export function tagAssets({ id, bulkIdsDto }: {
body: bulkIdsDto body: bulkIdsDto
}))); })));
} }
export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, tagId, timeBucket, userId, withPartners, withStacked }: { export function getTimeBucket({ albumId, isFavorite, isTrashed, key, order, personId, size, tagId, timeBucket, userId, visibility, withPartners, withStacked }: {
albumId?: string; albumId?: string;
isArchived?: boolean;
isFavorite?: boolean; isFavorite?: boolean;
isTrashed?: boolean; isTrashed?: boolean;
key?: string; key?: string;
@ -3254,6 +3246,7 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
tagId?: string; tagId?: string;
timeBucket: string; timeBucket: string;
userId?: string; userId?: string;
visibility?: AssetVisibility;
withPartners?: boolean; withPartners?: boolean;
withStacked?: boolean; withStacked?: boolean;
}, opts?: Oazapfts.RequestOpts) { }, opts?: Oazapfts.RequestOpts) {
@ -3262,7 +3255,6 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
data: AssetResponseDto[]; data: AssetResponseDto[];
}>(`/timeline/bucket${QS.query(QS.explode({ }>(`/timeline/bucket${QS.query(QS.explode({
albumId, albumId,
isArchived,
isFavorite, isFavorite,
isTrashed, isTrashed,
key, key,
@ -3272,15 +3264,15 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
tagId, tagId,
timeBucket, timeBucket,
userId, userId,
visibility,
withPartners, withPartners,
withStacked withStacked
}))}`, { }))}`, {
...opts ...opts
})); }));
} }
export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, tagId, userId, withPartners, withStacked }: { export function getTimeBuckets({ albumId, isFavorite, isTrashed, key, order, personId, size, tagId, userId, visibility, withPartners, withStacked }: {
albumId?: string; albumId?: string;
isArchived?: boolean;
isFavorite?: boolean; isFavorite?: boolean;
isTrashed?: boolean; isTrashed?: boolean;
key?: string; key?: string;
@ -3289,6 +3281,7 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key
size: TimeBucketSize; size: TimeBucketSize;
tagId?: string; tagId?: string;
userId?: string; userId?: string;
visibility?: AssetVisibility;
withPartners?: boolean; withPartners?: boolean;
withStacked?: boolean; withStacked?: boolean;
}, opts?: Oazapfts.RequestOpts) { }, opts?: Oazapfts.RequestOpts) {
@ -3297,7 +3290,6 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key
data: TimeBucketResponseDto[]; data: TimeBucketResponseDto[];
}>(`/timeline/buckets${QS.query(QS.explode({ }>(`/timeline/buckets${QS.query(QS.explode({
albumId, albumId,
isArchived,
isFavorite, isFavorite,
isTrashed, isTrashed,
key, key,
@ -3306,6 +3298,7 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key
size, size,
tagId, tagId,
userId, userId,
visibility,
withPartners, withPartners,
withStacked withStacked
}))}`, { }))}`, {
@ -3620,6 +3613,11 @@ export enum Permission {
AdminUserUpdate = "admin.user.update", AdminUserUpdate = "admin.user.update",
AdminUserDelete = "admin.user.delete" AdminUserDelete = "admin.user.delete"
} }
export enum AssetVisibility {
Archive = "archive",
Timeline = "timeline",
Hidden = "hidden"
}
export enum AssetMediaStatus { export enum AssetMediaStatus {
Created = "created", Created = "created",
Replaced = "replaced", Replaced = "replaced",

370
server/package-lock.json generated
View file

@ -23,7 +23,7 @@
"@opentelemetry/context-async-hooks": "^2.0.0", "@opentelemetry/context-async-hooks": "^2.0.0",
"@opentelemetry/exporter-prometheus": "^0.200.0", "@opentelemetry/exporter-prometheus": "^0.200.0",
"@opentelemetry/sdk-node": "^0.200.0", "@opentelemetry/sdk-node": "^0.200.0",
"@react-email/components": "^0.0.36", "@react-email/components": "^0.0.37",
"@socket.io/redis-adapter": "^8.3.0", "@socket.io/redis-adapter": "^8.3.0",
"archiver": "^7.0.0", "archiver": "^7.0.0",
"async-lock": "^1.4.0", "async-lock": "^1.4.0",
@ -2608,9 +2608,9 @@
} }
}, },
"node_modules/@nestjs/swagger": { "node_modules/@nestjs/swagger": {
"version": "11.1.5", "version": "11.1.6",
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.1.5.tgz", "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.1.6.tgz",
"integrity": "sha512-qVkyUSCvEmfTVWK92hsCeOQaOODlyBGkZC4ldqb4Fi0Gg8/kOWlcPJVN6i4a9edYYSdICUkGnt6UVFgi59fSrQ==", "integrity": "sha512-W5OyhLqUHg8CDy4GC5LBYOyGIq3dxhKNliBZ7xf2Q95+oDzptb3LGp8vwwf1ItEjyGdxM+USDStQPg2oAcxEgw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@microsoft/tsdoc": "0.15.1", "@microsoft/tsdoc": "0.15.1",
@ -4407,12 +4407,12 @@
} }
}, },
"node_modules/@react-email/code-block": { "node_modules/@react-email/code-block": {
"version": "0.0.12", "version": "0.0.13",
"resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.0.12.tgz", "resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.0.13.tgz",
"integrity": "sha512-Faw3Ij9+/Qwq6moWaeHnV8Hn7ekc/EqyAzPi6yUar21dhcqYugCC4Da1x4d9nA9zC0H9KU3lYVJczh8D3cA+Eg==", "integrity": "sha512-4DE4yPSgKEOnZMzcrDvRuD6mxsNxOex0hCYEG9F9q23geYgb2WCCeGBvIUXVzK69l703Dg4Vzrd5qUjl+JfcwA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"prismjs": "1.30.0" "prismjs": "^1.30.0"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
@ -4446,14 +4446,14 @@
} }
}, },
"node_modules/@react-email/components": { "node_modules/@react-email/components": {
"version": "0.0.36", "version": "0.0.37",
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.36.tgz", "resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.37.tgz",
"integrity": "sha512-VMh+OQplAnG8JMLlJjdnjt+ThJZ+JVkp0q2YMS2NEz+T88N22bLD2p7DZO0QgtNaKgumOhJI/0a2Q7VzCrwu5g==", "integrity": "sha512-M4MKALwezAf9uVMQHsR6k/MvfQ9H3xyrOppGfOiznPnjcv+/7oWiHERzL7Ani5nrCsc+fhc70zybpwtVQ/NSHQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@react-email/body": "0.0.11", "@react-email/body": "0.0.11",
"@react-email/button": "0.0.19", "@react-email/button": "0.0.19",
"@react-email/code-block": "0.0.12", "@react-email/code-block": "0.0.13",
"@react-email/code-inline": "0.0.5", "@react-email/code-inline": "0.0.5",
"@react-email/column": "0.0.13", "@react-email/column": "0.0.13",
"@react-email/container": "0.0.15", "@react-email/container": "0.0.15",
@ -4464,13 +4464,13 @@
"@react-email/html": "0.0.11", "@react-email/html": "0.0.11",
"@react-email/img": "0.0.11", "@react-email/img": "0.0.11",
"@react-email/link": "0.0.12", "@react-email/link": "0.0.12",
"@react-email/markdown": "0.0.14", "@react-email/markdown": "0.0.15",
"@react-email/preview": "0.0.12", "@react-email/preview": "0.0.12",
"@react-email/render": "1.0.6", "@react-email/render": "1.1.0",
"@react-email/row": "0.0.12", "@react-email/row": "0.0.12",
"@react-email/section": "0.0.16", "@react-email/section": "0.0.16",
"@react-email/tailwind": "1.0.4", "@react-email/tailwind": "1.0.5",
"@react-email/text": "0.1.1" "@react-email/text": "0.1.2"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
@ -4573,12 +4573,12 @@
} }
}, },
"node_modules/@react-email/markdown": { "node_modules/@react-email/markdown": {
"version": "0.0.14", "version": "0.0.15",
"resolved": "https://registry.npmjs.org/@react-email/markdown/-/markdown-0.0.14.tgz", "resolved": "https://registry.npmjs.org/@react-email/markdown/-/markdown-0.0.15.tgz",
"integrity": "sha512-5IsobCyPkb4XwnQO8uFfGcNOxnsg3311GRXhJ3uKv51P7Jxme4ycC/MITnwIZ10w2zx7HIyTiqVzTj4XbuIHbg==", "integrity": "sha512-UQA9pVm5sbflgtg3EX3FquUP4aMBzmLReLbGJ6DZQZnAskBF36aI56cRykDq1o+1jT+CKIK1CducPYziaXliag==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"md-to-react-email": "5.0.5" "md-to-react-email": "^5.0.5"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
@ -4600,14 +4600,14 @@
} }
}, },
"node_modules/@react-email/render": { "node_modules/@react-email/render": {
"version": "1.0.6", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.6.tgz", "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.1.0.tgz",
"integrity": "sha512-zNueW5Wn/4jNC1c5LFgXzbUdv5Lhms+FWjOvWAhal7gx5YVf0q6dPJ0dnR70+ifo59gcMLwCZEaTS9EEuUhKvQ==", "integrity": "sha512-X4CsHvXi5X7kTn5NgXNGg8Y5U1VtVJmlpNLlTc2E8RVHKFS3bpr+o/ZXhEPN4yRkdY+ZYN5eqVTV922Hujqsxw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"html-to-text": "9.0.5", "html-to-text": "^9.0.5",
"prettier": "3.5.3", "prettier": "^3.5.3",
"react-promise-suspense": "0.3.4" "react-promise-suspense": "^0.3.4"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
@ -4642,9 +4642,9 @@
} }
}, },
"node_modules/@react-email/tailwind": { "node_modules/@react-email/tailwind": {
"version": "1.0.4", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-1.0.5.tgz",
"integrity": "sha512-tJdcusncdqgvTUYZIuhNC6LYTfL9vNTSQpwWdTCQhQ1lsrNCEE4OKCSdzSV3S9F32pi0i0xQ+YPJHKIzGjdTSA==", "integrity": "sha512-BH00cZSeFfP9HiDASl+sPHi7Hh77W5nzDgdnxtsVr/m3uQD9g180UwxcE3PhOfx0vRdLzQUU8PtmvvDfbztKQg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
@ -4654,9 +4654,9 @@
} }
}, },
"node_modules/@react-email/text": { "node_modules/@react-email/text": {
"version": "0.1.1", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.1.tgz", "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.2.tgz",
"integrity": "sha512-Zo9tSEzkO3fODLVH1yVhzVCiwETfeEL5wU93jXKWo2DHoMuiZ9Iabaso3T0D0UjhrCB1PBMeq2YiejqeToTyIQ==", "integrity": "sha512-B5xDDBxgYpu/j7K4PSXuGmXgEvTheMyvnVXf/Md2u9dhvBHq65CPvSqYxDM1vjDjKd39GQEI7dqT2QIjER2DGA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
@ -4804,9 +4804,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@swc/core": { "node_modules/@swc/core": {
"version": "1.11.21", "version": "1.11.24",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.21.tgz", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.24.tgz",
"integrity": "sha512-/Y3BJLcwd40pExmdar8MH2UGGvCBrqNN7hauOMckrEX2Ivcbv3IMhrbGX4od1dnF880Ed8y/E9aStZCIQi0EGw==", "integrity": "sha512-MaQEIpfcEMzx3VWWopbofKJvaraqmL6HbLlw2bFZ7qYqYw3rkhM0cQVEgyzbHtTWwCwPMFZSC2DUbhlZgrMfLg==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
@ -4822,16 +4822,16 @@
"url": "https://opencollective.com/swc" "url": "https://opencollective.com/swc"
}, },
"optionalDependencies": { "optionalDependencies": {
"@swc/core-darwin-arm64": "1.11.21", "@swc/core-darwin-arm64": "1.11.24",
"@swc/core-darwin-x64": "1.11.21", "@swc/core-darwin-x64": "1.11.24",
"@swc/core-linux-arm-gnueabihf": "1.11.21", "@swc/core-linux-arm-gnueabihf": "1.11.24",
"@swc/core-linux-arm64-gnu": "1.11.21", "@swc/core-linux-arm64-gnu": "1.11.24",
"@swc/core-linux-arm64-musl": "1.11.21", "@swc/core-linux-arm64-musl": "1.11.24",
"@swc/core-linux-x64-gnu": "1.11.21", "@swc/core-linux-x64-gnu": "1.11.24",
"@swc/core-linux-x64-musl": "1.11.21", "@swc/core-linux-x64-musl": "1.11.24",
"@swc/core-win32-arm64-msvc": "1.11.21", "@swc/core-win32-arm64-msvc": "1.11.24",
"@swc/core-win32-ia32-msvc": "1.11.21", "@swc/core-win32-ia32-msvc": "1.11.24",
"@swc/core-win32-x64-msvc": "1.11.21" "@swc/core-win32-x64-msvc": "1.11.24"
}, },
"peerDependencies": { "peerDependencies": {
"@swc/helpers": ">=0.5.17" "@swc/helpers": ">=0.5.17"
@ -4842,10 +4842,95 @@
} }
} }
}, },
"node_modules/@swc/core-darwin-arm64": {
"version": "1.11.24",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.24.tgz",
"integrity": "sha512-dhtVj0PC1APOF4fl5qT2neGjRLgHAAYfiVP8poJelhzhB/318bO+QCFWAiimcDoyMgpCXOhTp757gnoJJrheWA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.11.24",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.24.tgz",
"integrity": "sha512-H/3cPs8uxcj2Fe3SoLlofN5JG6Ny5bl8DuZ6Yc2wr7gQFBmyBkbZEz+sPVgsID7IXuz7vTP95kMm1VL74SO5AQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.11.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.24.tgz",
"integrity": "sha512-PHJgWEpCsLo/NGj+A2lXZ2mgGjsr96ULNW3+T3Bj2KTc8XtMUkE8tmY2Da20ItZOvPNC/69KroU7edyo1Flfbw==",
"cpu": [
"arm"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.11.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.24.tgz",
"integrity": "sha512-C2FJb08+n5SD4CYWCTZx1uR88BN41ZieoHvI8A55hfVf2woT8+6ZiBzt74qW2g+ntZ535Jts5VwXAKdu41HpBg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.11.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.24.tgz",
"integrity": "sha512-ypXLIdszRo0re7PNNaXN0+2lD454G8l9LPK/rbfRXnhLWDBPURxzKlLlU/YGd2zP98wPcVooMmegRSNOKfvErw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": { "node_modules/@swc/core-linux-x64-gnu": {
"version": "1.11.21", "version": "1.11.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.21.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.24.tgz",
"integrity": "sha512-NesdBXv4CvVEaFUlqKj+GA4jJMNUzK2NtKOrUNEtTbXaVyNiXjFCSaDajMTedEB0jTAd9ybB0aBvwhgkJUWkWA==", "integrity": "sha512-IM7d+STVZD48zxcgo69L0yYptfhaaE9cMZ+9OoMxirNafhKKXwoZuufol1+alEFKc+Wbwp+aUPe/DeWC/Lh3dg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4860,9 +4945,9 @@
} }
}, },
"node_modules/@swc/core-linux-x64-musl": { "node_modules/@swc/core-linux-x64-musl": {
"version": "1.11.21", "version": "1.11.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.21.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.24.tgz",
"integrity": "sha512-qFV60pwpKVOdmX67wqQzgtSrUGWX9Cibnp1CXyqZ9Mmt8UyYGvmGu7p6PMbTyX7vdpVUvWVRf8DzrW2//wmVHg==", "integrity": "sha512-DZByJaMVzSfjQKKQn3cqSeqwy6lpMaQDQQ4HPlch9FWtDx/dLcpdIhxssqZXcR2rhaQVIaRQsCqwV6orSDGAGw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4876,6 +4961,57 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.11.24",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.24.tgz",
"integrity": "sha512-Q64Ytn23y9aVDKN5iryFi8mRgyHw3/kyjTjT4qFCa8AEb5sGUuSj//AUZ6c0J7hQKMHlg9do5Etvoe61V98/JQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.11.24",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.24.tgz",
"integrity": "sha512-9pKLIisE/Hh2vJhGIPvSoTK4uBSPxNVyXHmOrtdDot4E1FUUI74Vi8tFdlwNbaj8/vusVnb8xPXsxF1uB0VgiQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.11.24",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.24.tgz",
"integrity": "sha512-sybnXtOsdB+XvzVFlBVGgRHLqp3yRpHK7CrmpuDKszhj/QhmsaZzY/GHSeALlMtLup13M0gqbcQvsTNlAHTg3w==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/counter": { "node_modules/@swc/counter": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@ -4905,23 +5041,23 @@
} }
}, },
"node_modules/@testcontainers/postgresql": { "node_modules/@testcontainers/postgresql": {
"version": "10.24.2", "version": "10.25.0",
"resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.24.2.tgz", "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.25.0.tgz",
"integrity": "sha512-s4k/QVSmpXFzxNBeft84x172Hx5tNiGeDRWwU8iOAHjtcnyXd4aBORF0GruNojTo7TOIuAM0ArBZUMqRBLJKwQ==", "integrity": "sha512-VkpqpX9YZ8aq4wfk6sJRopGTmlBdE1kErzAFWJ/1pY/XrEZ7nxdfFBG+En2icQnbv3BIFQYysEKxEFMNB+hQVw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"testcontainers": "^10.24.2" "testcontainers": "^10.25.0"
} }
}, },
"node_modules/@testcontainers/redis": { "node_modules/@testcontainers/redis": {
"version": "10.24.2", "version": "10.25.0",
"resolved": "https://registry.npmjs.org/@testcontainers/redis/-/redis-10.24.2.tgz", "resolved": "https://registry.npmjs.org/@testcontainers/redis/-/redis-10.25.0.tgz",
"integrity": "sha512-m4/FZW5ltZPaK9pQTKNipjpBk73Vdj7Ql3sFr26A9dOr0wJyM3Wnc9jeHTNRal7RDnY5rvumXAIUWbBlvKMJEw==", "integrity": "sha512-ALNrrnYnB59kV5c/EjiUkzn0roCtcnOu2KfHHF8xBi3vq3dYSqzADL8rL2BExeoFhyaEtlUT9P4ZecRB60O+/Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"testcontainers": "^10.24.2" "testcontainers": "^10.25.0"
} }
}, },
"node_modules/@tokenizer/inflate": { "node_modules/@tokenizer/inflate": {
@ -5527,17 +5663,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz",
"integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==", "integrity": "sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.31.1",
"@typescript-eslint/type-utils": "8.31.0", "@typescript-eslint/type-utils": "8.31.1",
"@typescript-eslint/utils": "8.31.0", "@typescript-eslint/utils": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.1",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@ -5557,16 +5693,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.1.tgz",
"integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==", "integrity": "sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.31.1",
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/typescript-estree": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.1",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -5582,14 +5718,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz",
"integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==", "integrity": "sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.0" "@typescript-eslint/visitor-keys": "8.31.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -5600,14 +5736,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz",
"integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==", "integrity": "sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/typescript-estree": "8.31.1",
"@typescript-eslint/utils": "8.31.0", "@typescript-eslint/utils": "8.31.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^2.0.1"
}, },
@ -5624,9 +5760,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.1.tgz",
"integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==", "integrity": "sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -5638,14 +5774,14 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz",
"integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==", "integrity": "sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -5691,16 +5827,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.1.tgz",
"integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==", "integrity": "sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.31.1",
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"@typescript-eslint/typescript-estree": "8.31.0" "@typescript-eslint/typescript-estree": "8.31.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -5715,13 +5851,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz",
"integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==", "integrity": "sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.31.1",
"eslint-visitor-keys": "^4.2.0" "eslint-visitor-keys": "^4.2.0"
}, },
"engines": { "engines": {
@ -6886,9 +7022,9 @@
} }
}, },
"node_modules/bullmq": { "node_modules/bullmq": {
"version": "5.51.0", "version": "5.52.0",
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.51.0.tgz", "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.52.0.tgz",
"integrity": "sha512-YjX+CO2U4nmbCq2ZgNb/Hnu6Xk953j8EFmp0eehTuudavPyNstoZsbnyvvM6PX9rfD9clhcc5kRLyyWoFEM3Lg==", "integrity": "sha512-2PRR7DuH4iFjrIam5kL08VLHe1FCZtr1jsL3Um/18EML9Gd7w9eFgzlriaiYUyUxU0gFVql0sijo1aBcoTrTTA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cron-parser": "^4.9.0", "cron-parser": "^4.9.0",
@ -13108,13 +13244,13 @@
} }
}, },
"node_modules/pg": { "node_modules/pg": {
"version": "8.15.5", "version": "8.15.6",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.15.5.tgz", "resolved": "https://registry.npmjs.org/pg/-/pg-8.15.6.tgz",
"integrity": "sha512-EpAhHFQc+aH9VfeffWIVC+XXk6lmAhS9W1FxtxcPXs94yxhrI1I6w/zkWfIOII/OkBv3Be04X3xMOj0kQ78l6w==", "integrity": "sha512-yvao7YI3GdmmrslNVsZgx9PfntfWrnXwtR+K/DjI0I/sTKif4Z623um+sjVZ1hk5670B+ODjvHDAckKdjmPTsg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"pg-connection-string": "^2.8.5", "pg-connection-string": "^2.8.5",
"pg-pool": "^3.9.5", "pg-pool": "^3.9.6",
"pg-protocol": "^1.9.5", "pg-protocol": "^1.9.5",
"pg-types": "^2.1.0", "pg-types": "^2.1.0",
"pgpass": "1.x" "pgpass": "1.x"
@ -16198,9 +16334,9 @@
} }
}, },
"node_modules/testcontainers": { "node_modules/testcontainers": {
"version": "10.24.2", "version": "10.25.0",
"resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.24.2.tgz", "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.25.0.tgz",
"integrity": "sha512-Don3EXEQuSw14+nFG9pj48fL9ck/jXDfR9Rb0K3acOyn/gg97+gsnfZaLzpdejl9GcPJVKxACNRe3SYVC2uWqg==", "integrity": "sha512-X3x6cjorEMgei1vVx3M7dnTMzWoWOTi4krpUf3C2iOvOcwsaMUHbca9J4yzpN65ieiWhcK2dA5dxpZyUonwC2Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -16822,15 +16958,15 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.31.0", "version": "8.31.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.0.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.1.tgz",
"integrity": "sha512-u+93F0sB0An8WEAPtwxVhFby573E8ckdjwUUQUj9QA4v8JAvgtoDdIyYR3XFwFHq2W1KJ1AurwJCO+w+Y1ixyQ==", "integrity": "sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.31.0", "@typescript-eslint/eslint-plugin": "8.31.1",
"@typescript-eslint/parser": "8.31.0", "@typescript-eslint/parser": "8.31.1",
"@typescript-eslint/utils": "8.31.0" "@typescript-eslint/utils": "8.31.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"

View file

@ -48,7 +48,7 @@
"@opentelemetry/context-async-hooks": "^2.0.0", "@opentelemetry/context-async-hooks": "^2.0.0",
"@opentelemetry/exporter-prometheus": "^0.200.0", "@opentelemetry/exporter-prometheus": "^0.200.0",
"@opentelemetry/sdk-node": "^0.200.0", "@opentelemetry/sdk-node": "^0.200.0",
"@react-email/components": "^0.0.36", "@react-email/components": "^0.0.37",
"@socket.io/redis-adapter": "^8.3.0", "@socket.io/redis-adapter": "^8.3.0",
"archiver": "^7.0.0", "archiver": "^7.0.0",
"async-lock": "^1.4.0", "async-lock": "^1.4.0",

View file

@ -6,15 +6,15 @@ import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'
describe(ActivityController.name, () => { describe(ActivityController.name, () => {
let ctx: ControllerContext; let ctx: ControllerContext;
const service = mockBaseService(ActivityService);
beforeAll(async () => { beforeAll(async () => {
ctx = await controllerSetup(ActivityController, [ ctx = await controllerSetup(ActivityController, [{ provide: ActivityService, useValue: service }]);
{ provide: ActivityService, useValue: mockBaseService(ActivityService) },
]);
return () => ctx.close(); return () => ctx.close();
}); });
beforeEach(() => { beforeEach(() => {
service.resetAllMocks();
ctx.reset(); ctx.reset();
}); });

View file

@ -6,13 +6,15 @@ import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'
describe(AlbumController.name, () => { describe(AlbumController.name, () => {
let ctx: ControllerContext; let ctx: ControllerContext;
const service = mockBaseService(AlbumService);
beforeAll(async () => { beforeAll(async () => {
ctx = await controllerSetup(AlbumController, [{ provide: AlbumService, useValue: mockBaseService(AlbumService) }]); ctx = await controllerSetup(AlbumController, [{ provide: AlbumService, useValue: service }]);
return () => ctx.close(); return () => ctx.close();
}); });
beforeEach(() => { beforeEach(() => {
service.resetAllMocks();
ctx.reset(); ctx.reset();
}); });

View file

@ -6,15 +6,15 @@ import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'
describe(APIKeyController.name, () => { describe(APIKeyController.name, () => {
let ctx: ControllerContext; let ctx: ControllerContext;
const service = mockBaseService(ApiKeyService);
beforeAll(async () => { beforeAll(async () => {
ctx = await controllerSetup(APIKeyController, [ ctx = await controllerSetup(APIKeyController, [{ provide: ApiKeyService, useValue: service }]);
{ provide: ApiKeyService, useValue: mockBaseService(ApiKeyService) },
]);
return () => ctx.close(); return () => ctx.close();
}); });
beforeEach(() => { beforeEach(() => {
service.resetAllMocks();
ctx.reset(); ctx.reset();
}); });

View file

@ -100,25 +100,15 @@ describe(AssetMediaController.name, () => {
expect(body).toEqual(factory.responses.badRequest()); expect(body).toEqual(factory.responses.badRequest());
}); });
it('should throw if `isVisible` is not a boolean', async () => { it('should throw if `visibility` is not an enum', async () => {
const { status, body } = await request(ctx.getHttpServer()) const { status, body } = await request(ctx.getHttpServer())
.post('/assets') .post('/assets')
.attach('assetData', assetData, filename) .attach('assetData', assetData, filename)
.field({ ...makeUploadDto(), isVisible: 'not-a-boolean' }); .field({ ...makeUploadDto(), visibility: 'not-a-boolean' });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(factory.responses.badRequest()); expect(body).toEqual(factory.responses.badRequest());
}); });
it('should throw if `isArchived` is not a boolean', async () => {
const { status, body } = await request(ctx.getHttpServer())
.post('/assets')
.attach('assetData', assetData, filename)
.field({ ...makeUploadDto(), isArchived: 'not-a-boolean' });
expect(status).toBe(400);
expect(body).toEqual(factory.responses.badRequest());
});
});
// TODO figure out how to deal with `sendFile` // TODO figure out how to deal with `sendFile`
describe.skip('GET /assets/:id/original', () => { describe.skip('GET /assets/:id/original', () => {
it('should be an authenticated route', async () => { it('should be an authenticated route', async () => {
@ -134,4 +124,5 @@ describe(AssetMediaController.name, () => {
expect(ctx.authenticate).toHaveBeenCalled(); expect(ctx.authenticate).toHaveBeenCalled();
}); });
}); });
});
}); });

View file

@ -1,4 +1,5 @@
import { AuthController } from 'src/controllers/auth.controller'; import { AuthController } from 'src/controllers/auth.controller';
import { LoginResponseDto } from 'src/dtos/auth.dto';
import { AuthService } from 'src/services/auth.service'; import { AuthService } from 'src/services/auth.service';
import request from 'supertest'; import request from 'supertest';
import { errorDto } from 'test/medium/responses'; import { errorDto } from 'test/medium/responses';
@ -14,6 +15,7 @@ describe(AuthController.name, () => {
}); });
beforeEach(() => { beforeEach(() => {
service.resetAllMocks();
ctx.reset(); ctx.reset();
}); });
@ -56,5 +58,88 @@ describe(AuthController.name, () => {
expect(status).toEqual(201); expect(status).toEqual(201);
expect(service.adminSignUp).toHaveBeenCalledWith(expect.objectContaining({ email: 'admin@immich.cloud' })); expect(service.adminSignUp).toHaveBeenCalledWith(expect.objectContaining({ email: 'admin@immich.cloud' }));
}); });
it('should accept an email with a local domain', async () => {
const { status } = await request(ctx.getHttpServer())
.post('/auth/admin-sign-up')
.send({ name: 'admin', password: 'password', email: 'admin@local' });
expect(status).toEqual(201);
});
});
describe('POST /auth/login', () => {
it(`should require an email and password`, async () => {
const { status, body } = await request(ctx.getHttpServer()).post('/auth/login').send({ name: 'admin' });
expect(status).toBe(400);
expect(body).toEqual(
errorDto.badRequest([
'email should not be empty',
'email must be an email',
'password should not be empty',
'password must be a string',
]),
);
});
it(`should not allow null email`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.post('/auth/login')
.send({ name: 'admin', email: null, password: 'password' });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['email should not be empty', 'email must be an email']));
});
it(`should not allow null password`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.post('/auth/login')
.send({ name: 'admin', email: 'admin@immich.cloud', password: null });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['password should not be empty', 'password must be a string']));
});
it('should reject an invalid email', async () => {
service.login.mockResolvedValue({ accessToken: 'access-token' } as LoginResponseDto);
const { status, body } = await request(ctx.getHttpServer())
.post('/auth/login')
.send({ name: 'admin', email: [], password: 'password' });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['email must be an email']));
});
it('should transform the email to all lowercase', async () => {
service.login.mockResolvedValue({ accessToken: 'access-token' } as LoginResponseDto);
const { status } = await request(ctx.getHttpServer())
.post('/auth/login')
.send({ name: 'admin', email: 'aDmIn@iMmIcH.ApP', password: 'password' });
expect(status).toBe(201);
expect(service.login).toHaveBeenCalledWith(
expect.objectContaining({ email: 'admin@immich.app' }),
expect.anything(),
);
});
it('should accept an email with a local domain', async () => {
service.login.mockResolvedValue({ accessToken: 'access-token' } as LoginResponseDto);
const { status } = await request(ctx.getHttpServer())
.post('/auth/login')
.send({ name: 'admin', email: 'admin@local', password: 'password' });
expect(status).toEqual(201);
expect(service.login).toHaveBeenCalledWith(expect.objectContaining({ email: 'admin@local' }), expect.anything());
});
});
describe('POST /auth/change-password', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer())
.post('/auth/change-password')
.send({ password: 'password', newPassword: 'Password1234' });
expect(ctx.authenticate).toHaveBeenCalled();
});
}); });
}); });

View file

@ -23,8 +23,8 @@ export class AuthController {
@Post('login') @Post('login')
async login( async login(
@Body() loginCredential: LoginCredentialDto,
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
@Body() loginCredential: LoginCredentialDto,
@GetLoginDetails() loginDetails: LoginDetails, @GetLoginDetails() loginDetails: LoginDetails,
): Promise<LoginResponseDto> { ): Promise<LoginResponseDto> {
const body = await this.service.login(loginCredential, loginDetails); const body = await this.service.login(loginCredential, loginDetails);

View file

@ -0,0 +1,46 @@
import { DownloadController } from 'src/controllers/download.controller';
import { DownloadService } from 'src/services/download.service';
import request from 'supertest';
import { factory } from 'test/small.factory';
import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils';
import { Readable } from 'typeorm/platform/PlatformTools.js';
describe(DownloadController.name, () => {
let ctx: ControllerContext;
const service = mockBaseService(DownloadService);
beforeAll(async () => {
ctx = await controllerSetup(DownloadController, [{ provide: DownloadService, useValue: service }]);
return () => ctx.close();
});
beforeEach(() => {
service.resetAllMocks();
ctx.reset();
});
describe('POST /download/info', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer())
.post('/download/info')
.send({ assetIds: [factory.uuid()] });
expect(ctx.authenticate).toHaveBeenCalled();
});
});
describe('POST /download/archive', () => {
it('should be an authenticated route', async () => {
const stream = new Readable({
read() {
this.push('test');
this.push(null);
},
});
service.downloadArchive.mockResolvedValue({ stream });
await request(ctx.getHttpServer())
.post('/download/archive')
.send({ assetIds: [factory.uuid()] });
expect(ctx.authenticate).toHaveBeenCalled();
});
});
});

View file

@ -7,15 +7,15 @@ import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'
describe(NotificationController.name, () => { describe(NotificationController.name, () => {
let ctx: ControllerContext; let ctx: ControllerContext;
const service = mockBaseService(NotificationService);
beforeAll(async () => { beforeAll(async () => {
ctx = await controllerSetup(NotificationController, [ ctx = await controllerSetup(NotificationController, [{ provide: NotificationService, useValue: service }]);
{ provide: NotificationService, useValue: mockBaseService(NotificationService) },
]);
return () => ctx.close(); return () => ctx.close();
}); });
beforeEach(() => { beforeEach(() => {
service.resetAllMocks();
ctx.reset(); ctx.reset();
}); });

View file

@ -6,15 +6,15 @@ import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'
describe(SearchController.name, () => { describe(SearchController.name, () => {
let ctx: ControllerContext; let ctx: ControllerContext;
const service = mockBaseService(SearchService);
beforeAll(async () => { beforeAll(async () => {
ctx = await controllerSetup(SearchController, [ ctx = await controllerSetup(SearchController, [{ provide: SearchService, useValue: service }]);
{ provide: SearchService, useValue: mockBaseService(SearchService) },
]);
return () => ctx.close(); return () => ctx.close();
}); });
beforeEach(() => { beforeEach(() => {
service.resetAllMocks();
ctx.reset(); ctx.reset();
}); });
@ -60,12 +60,14 @@ describe(SearchController.name, () => {
expect(body).toEqual(errorDto.badRequest(['size must not be less than 1', 'size must be an integer number'])); expect(body).toEqual(errorDto.badRequest(['size must not be less than 1', 'size must be an integer number']));
}); });
it('should reject an isArchived as not a boolean', async () => { it('should reject an visibility as not an enum', async () => {
const { status, body } = await request(ctx.getHttpServer()) const { status, body } = await request(ctx.getHttpServer())
.post('/search/metadata') .post('/search/metadata')
.send({ isArchived: 'immich' }); .send({ visibility: 'immich' });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['isArchived must be a boolean value'])); expect(body).toEqual(
errorDto.badRequest(['visibility must be one of the following values: archive, timeline, hidden']),
);
}); });
it('should reject an isFavorite as not a boolean', async () => { it('should reject an isFavorite as not a boolean', async () => {
@ -98,15 +100,6 @@ describe(SearchController.name, () => {
expect(body).toEqual(errorDto.badRequest(['isMotion must be a boolean value'])); expect(body).toEqual(errorDto.badRequest(['isMotion must be a boolean value']));
}); });
it('should reject an isVisible as not a boolean', async () => {
const { status, body } = await request(ctx.getHttpServer())
.post('/search/metadata')
.send({ isVisible: 'immich' });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['isVisible must be a boolean value']));
});
});
describe('POST /search/random', () => { describe('POST /search/random', () => {
it('should be an authenticated route', async () => { it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).post('/search/random'); await request(ctx.getHttpServer()).post('/search/random');
@ -122,7 +115,9 @@ describe(SearchController.name, () => {
}); });
it('should reject if withPeople is not a boolean', async () => { it('should reject if withPeople is not a boolean', async () => {
const { status, body } = await request(ctx.getHttpServer()).post('/search/random').send({ withPeople: 'immich' }); const { status, body } = await request(ctx.getHttpServer())
.post('/search/random')
.send({ withPeople: 'immich' });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['withPeople must be a boolean value'])); expect(body).toEqual(errorDto.badRequest(['withPeople must be a boolean value']));
}); });
@ -198,4 +193,5 @@ describe(SearchController.name, () => {
); );
}); });
}); });
});
}); });

View file

@ -6,16 +6,20 @@ import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'
describe(ServerController.name, () => { describe(ServerController.name, () => {
let ctx: ControllerContext; let ctx: ControllerContext;
const serverService = mockBaseService(ServerService);
const versionService = mockBaseService(VersionService);
beforeAll(async () => { beforeAll(async () => {
ctx = await controllerSetup(ServerController, [ ctx = await controllerSetup(ServerController, [
{ provide: ServerService, useValue: mockBaseService(ServerService) }, { provide: ServerService, useValue: serverService },
{ provide: VersionService, useValue: mockBaseService(VersionService) }, { provide: VersionService, useValue: versionService },
]); ]);
return () => ctx.close(); return () => ctx.close();
}); });
beforeEach(() => { beforeEach(() => {
serverService.resetAllMocks();
versionService.resetAllMocks();
ctx.reset(); ctx.reset();
}); });

View file

@ -8,16 +8,18 @@ import { automock, ControllerContext, controllerSetup, mockBaseService } from 't
describe(UserController.name, () => { describe(UserController.name, () => {
let ctx: ControllerContext; let ctx: ControllerContext;
const service = mockBaseService(UserService);
beforeAll(async () => { beforeAll(async () => {
ctx = await controllerSetup(UserController, [ ctx = await controllerSetup(UserController, [
{ provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) }, { provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) },
{ provide: UserService, useValue: mockBaseService(UserService) }, { provide: UserService, useValue: service },
]); ]);
return () => ctx.close(); return () => ctx.close();
}); });
beforeEach(() => { beforeEach(() => {
service.resetAllMocks();
ctx.reset(); ctx.reset();
}); });

View file

@ -5,6 +5,7 @@ import {
AlbumUserRole, AlbumUserRole,
AssetFileType, AssetFileType,
AssetType, AssetType,
AssetVisibility,
MemoryType, MemoryType,
Permission, Permission,
SharedLinkType, SharedLinkType,
@ -108,7 +109,7 @@ export type Asset = {
fileCreatedAt: Date; fileCreatedAt: Date;
fileModifiedAt: Date; fileModifiedAt: Date;
isExternal: boolean; isExternal: boolean;
isVisible: boolean; visibility: AssetVisibility;
libraryId: string | null; libraryId: string | null;
livePhotoVideoId: string | null; livePhotoVideoId: string | null;
localDateTime: Date; localDateTime: Date;
@ -285,7 +286,7 @@ export const columns = {
'assets.fileCreatedAt', 'assets.fileCreatedAt',
'assets.fileModifiedAt', 'assets.fileModifiedAt',
'assets.isExternal', 'assets.isExternal',
'assets.isVisible', 'assets.visibility',
'assets.libraryId', 'assets.libraryId',
'assets.livePhotoVideoId', 'assets.livePhotoVideoId',
'assets.localDateTime', 'assets.localDateTime',
@ -345,7 +346,7 @@ export const columns = {
'type', 'type',
'deletedAt', 'deletedAt',
'isFavorite', 'isFavorite',
'isVisible', 'visibility',
'updateId', 'updateId',
], ],
stack: ['stack.id', 'stack.primaryAssetId', 'ownerId'], stack: ['stack.id', 'stack.primaryAssetId', 'ownerId'],

4
server/src/db.d.ts vendored
View file

@ -10,6 +10,7 @@ import {
AssetOrder, AssetOrder,
AssetStatus, AssetStatus,
AssetType, AssetType,
AssetVisibility,
MemoryType, MemoryType,
NotificationLevel, NotificationLevel,
NotificationType, NotificationType,
@ -148,11 +149,10 @@ export interface Assets {
fileCreatedAt: Timestamp; fileCreatedAt: Timestamp;
fileModifiedAt: Timestamp; fileModifiedAt: Timestamp;
id: Generated<string>; id: Generated<string>;
isArchived: Generated<boolean>;
isExternal: Generated<boolean>; isExternal: Generated<boolean>;
isFavorite: Generated<boolean>; isFavorite: Generated<boolean>;
isOffline: Generated<boolean>; isOffline: Generated<boolean>;
isVisible: Generated<boolean>; visibility: Generated<AssetVisibility>;
libraryId: string | null; libraryId: string | null;
livePhotoVideoId: string | null; livePhotoVideoId: string | null;
localDateTime: Timestamp; localDateTime: Timestamp;

View file

@ -1,7 +1,8 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { ArrayNotEmpty, IsArray, IsEnum, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; import { ArrayNotEmpty, IsArray, IsEnum, IsNotEmpty, IsString, ValidateNested } from 'class-validator';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; import { AssetVisibility } from 'src/enum';
import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
export enum AssetMediaSize { export enum AssetMediaSize {
/** /**
@ -55,11 +56,8 @@ export class AssetMediaCreateDto extends AssetMediaBase {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
isFavorite?: boolean; isFavorite?: boolean;
@ValidateBoolean({ optional: true }) @ValidateAssetVisibility({ optional: true })
isArchived?: boolean; visibility?: AssetVisibility;
@ValidateBoolean({ optional: true })
isVisible?: boolean;
@ValidateUUID({ optional: true }) @ValidateUUID({ optional: true })
livePhotoVideoId?: string; livePhotoVideoId?: string;

View file

@ -12,7 +12,7 @@ import {
} from 'src/dtos/person.dto'; } from 'src/dtos/person.dto';
import { TagResponseDto, mapTag } from 'src/dtos/tag.dto'; import { TagResponseDto, mapTag } from 'src/dtos/tag.dto';
import { UserResponseDto, mapUser } from 'src/dtos/user.dto'; import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
import { AssetStatus, AssetType } from 'src/enum'; import { AssetStatus, AssetType, AssetVisibility } from 'src/enum';
import { mimeTypes } from 'src/utils/mime-types'; import { mimeTypes } from 'src/utils/mime-types';
export class SanitizedAssetResponseDto { export class SanitizedAssetResponseDto {
@ -74,11 +74,10 @@ export type MapAsset = {
fileCreatedAt: Date; fileCreatedAt: Date;
fileModifiedAt: Date; fileModifiedAt: Date;
files?: AssetFile[]; files?: AssetFile[];
isArchived: boolean;
isExternal: boolean; isExternal: boolean;
isFavorite: boolean; isFavorite: boolean;
isOffline: boolean; isOffline: boolean;
isVisible: boolean; visibility: AssetVisibility;
libraryId: string | null; libraryId: string | null;
livePhotoVideoId: string | null; livePhotoVideoId: string | null;
localDateTime: Date; localDateTime: Date;
@ -183,7 +182,7 @@ export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): Asset
localDateTime: entity.localDateTime, localDateTime: entity.localDateTime,
updatedAt: entity.updatedAt, updatedAt: entity.updatedAt,
isFavorite: options.auth?.user.id === entity.ownerId ? entity.isFavorite : false, isFavorite: options.auth?.user.id === entity.ownerId ? entity.isFavorite : false,
isArchived: entity.isArchived, isArchived: entity.visibility === AssetVisibility.ARCHIVE,
isTrashed: !!entity.deletedAt, isTrashed: !!entity.deletedAt,
duration: entity.duration ?? '0:00:00.00000', duration: entity.duration ?? '0:00:00.00000',
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined, exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,

View file

@ -14,9 +14,9 @@ import {
ValidateIf, ValidateIf,
} from 'class-validator'; } from 'class-validator';
import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
import { AssetType } from 'src/enum'; import { AssetType, AssetVisibility } from 'src/enum';
import { AssetStats } from 'src/repositories/asset.repository'; import { AssetStats } from 'src/repositories/asset.repository';
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateUUID } from 'src/validation';
export class DeviceIdDto { export class DeviceIdDto {
@IsNotEmpty() @IsNotEmpty()
@ -32,8 +32,8 @@ export class UpdateAssetBase {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
isFavorite?: boolean; isFavorite?: boolean;
@ValidateBoolean({ optional: true }) @ValidateAssetVisibility({ optional: true })
isArchived?: boolean; visibility?: AssetVisibility;
@Optional() @Optional()
@IsDateString() @IsDateString()
@ -105,8 +105,8 @@ export class AssetJobsDto extends AssetIdsDto {
} }
export class AssetStatsDto { export class AssetStatsDto {
@ValidateBoolean({ optional: true }) @ValidateAssetVisibility({ optional: true })
isArchived?: boolean; visibility?: AssetVisibility;
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
isFavorite?: boolean; isFavorite?: boolean;

View file

@ -1,6 +1,6 @@
import { Transform, Type } from 'class-transformer'; import { Transform, Type } from 'class-transformer';
import { IsEnum, IsInt, IsString } from 'class-validator'; import { IsEnum, IsInt, IsString } from 'class-validator';
import { ImmichEnvironment, LogLevel } from 'src/enum'; import { DatabaseSslMode, ImmichEnvironment, LogLevel } from 'src/enum';
import { IsIPRange, Optional, ValidateBoolean } from 'src/validation'; import { IsIPRange, Optional, ValidateBoolean } from 'src/validation';
export class EnvDto { export class EnvDto {
@ -142,6 +142,10 @@ export class EnvDto {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
DB_SKIP_MIGRATIONS?: boolean; DB_SKIP_MIGRATIONS?: boolean;
@IsEnum(DatabaseSslMode)
@Optional()
DB_SSL_MODE?: DatabaseSslMode;
@IsString() @IsString()
@Optional() @Optional()
DB_URL?: string; DB_URL?: string;

View file

@ -5,8 +5,8 @@ import { Place } from 'src/database';
import { PropertyLifecycle } from 'src/decorators'; import { PropertyLifecycle } from 'src/decorators';
import { AlbumResponseDto } from 'src/dtos/album.dto'; import { AlbumResponseDto } from 'src/dtos/album.dto';
import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AssetResponseDto } from 'src/dtos/asset-response.dto';
import { AssetOrder, AssetType } from 'src/enum'; import { AssetOrder, AssetType, AssetVisibility } from 'src/enum';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
class BaseSearchDto { class BaseSearchDto {
@ValidateUUID({ optional: true, nullable: true }) @ValidateUUID({ optional: true, nullable: true })
@ -22,13 +22,6 @@ class BaseSearchDto {
@ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType }) @ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType })
type?: AssetType; type?: AssetType;
@ValidateBoolean({ optional: true })
isArchived?: boolean;
@ValidateBoolean({ optional: true })
@ApiProperty({ default: false })
withArchived?: boolean;
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
isEncoded?: boolean; isEncoded?: boolean;
@ -41,8 +34,8 @@ class BaseSearchDto {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
isOffline?: boolean; isOffline?: boolean;
@ValidateBoolean({ optional: true }) @ValidateAssetVisibility({ optional: true })
isVisible?: boolean; visibility?: AssetVisibility;
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
withDeleted?: boolean; withDeleted?: boolean;

View file

@ -1,7 +1,7 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsInt, IsPositive, IsString } from 'class-validator'; import { IsEnum, IsInt, IsPositive, IsString } from 'class-validator';
import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AssetResponseDto } from 'src/dtos/asset-response.dto';
import { AssetType, SyncEntityType, SyncRequestType } from 'src/enum'; import { AssetType, AssetVisibility, SyncEntityType, SyncRequestType } from 'src/enum';
import { Optional, ValidateDate, ValidateUUID } from 'src/validation'; import { Optional, ValidateDate, ValidateUUID } from 'src/validation';
export class AssetFullSyncDto { export class AssetFullSyncDto {
@ -67,7 +67,7 @@ export class SyncAssetV1 {
type!: AssetType; type!: AssetType;
deletedAt!: Date | null; deletedAt!: Date | null;
isFavorite!: boolean; isFavorite!: boolean;
isVisible!: boolean; visibility!: AssetVisibility;
} }
export class SyncAssetDeleteV1 { export class SyncAssetDeleteV1 {

View file

@ -1,8 +1,8 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { AssetOrder } from 'src/enum'; import { AssetOrder, AssetVisibility } from 'src/enum';
import { TimeBucketSize } from 'src/repositories/asset.repository'; import { TimeBucketSize } from 'src/repositories/asset.repository';
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateUUID } from 'src/validation';
export class TimeBucketDto { export class TimeBucketDto {
@IsNotEmpty() @IsNotEmpty()
@ -22,9 +22,6 @@ export class TimeBucketDto {
@ValidateUUID({ optional: true }) @ValidateUUID({ optional: true })
tagId?: string; tagId?: string;
@ValidateBoolean({ optional: true })
isArchived?: boolean;
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
isFavorite?: boolean; isFavorite?: boolean;
@ -41,6 +38,9 @@ export class TimeBucketDto {
@Optional() @Optional()
@ApiProperty({ enum: AssetOrder, enumName: 'AssetOrder' }) @ApiProperty({ enum: AssetOrder, enumName: 'AssetOrder' })
order?: AssetOrder; order?: AssetOrder;
@ValidateAssetVisibility({ optional: true })
visibility?: AssetVisibility;
} }
export class TimeBucketAssetDto extends TimeBucketDto { export class TimeBucketAssetDto extends TimeBucketDto {

View file

@ -610,3 +610,21 @@ export enum OAuthTokenEndpointAuthMethod {
CLIENT_SECRET_POST = 'client_secret_post', CLIENT_SECRET_POST = 'client_secret_post',
CLIENT_SECRET_BASIC = 'client_secret_basic', CLIENT_SECRET_BASIC = 'client_secret_basic',
} }
export enum DatabaseSslMode {
Disable = 'disable',
Allow = 'allow',
Prefer = 'prefer',
Require = 'require',
VerifyFull = 'verify-full',
}
export enum AssetVisibility {
ARCHIVE = 'archive',
TIMELINE = 'timeline',
/**
* Video part of the LivePhotos and MotionPhotos
*/
HIDDEN = 'hidden',
}

View file

@ -110,8 +110,11 @@ from
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
where where
"partner"."sharedWithId" = $1 "partner"."sharedWithId" = $1
and "assets"."isArchived" = $2 and (
and "assets"."id" in ($3) "assets"."visibility" = 'timeline'
or "assets"."visibility" = 'hidden'
)
and "assets"."id" in ($2)
-- AccessRepository.asset.checkSharedLinkAccess -- AccessRepository.asset.checkSharedLinkAccess
select select

View file

@ -7,7 +7,7 @@ select
"ownerId", "ownerId",
"duplicateId", "duplicateId",
"stackId", "stackId",
"isVisible", "visibility",
"smart_search"."embedding", "smart_search"."embedding",
( (
select select
@ -83,7 +83,7 @@ from
inner join "asset_job_status" on "asset_job_status"."assetId" = "assets"."id" inner join "asset_job_status" on "asset_job_status"."assetId" = "assets"."id"
where where
"assets"."deletedAt" is null "assets"."deletedAt" is null
and "assets"."isVisible" = $1 and "assets"."visibility" != $1
and ( and (
"asset_job_status"."previewAt" is null "asset_job_status"."previewAt" is null
or "asset_job_status"."thumbnailAt" is null or "asset_job_status"."thumbnailAt" is null
@ -118,7 +118,7 @@ where
-- AssetJobRepository.getForGenerateThumbnailJob -- AssetJobRepository.getForGenerateThumbnailJob
select select
"assets"."id", "assets"."id",
"assets"."isVisible", "assets"."visibility",
"assets"."originalFileName", "assets"."originalFileName",
"assets"."originalPath", "assets"."originalPath",
"assets"."ownerId", "assets"."ownerId",
@ -155,7 +155,7 @@ select
"assets"."fileCreatedAt", "assets"."fileCreatedAt",
"assets"."fileModifiedAt", "assets"."fileModifiedAt",
"assets"."isExternal", "assets"."isExternal",
"assets"."isVisible", "assets"."visibility",
"assets"."libraryId", "assets"."libraryId",
"assets"."livePhotoVideoId", "assets"."livePhotoVideoId",
"assets"."localDateTime", "assets"."localDateTime",
@ -201,7 +201,7 @@ from
"assets" "assets"
inner join "asset_job_status" as "job_status" on "assetId" = "assets"."id" inner join "asset_job_status" as "job_status" on "assetId" = "assets"."id"
where where
"assets"."isVisible" = $1 "assets"."visibility" != $1
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
and "job_status"."previewAt" is not null and "job_status"."previewAt" is not null
and not exists ( and not exists (
@ -220,7 +220,7 @@ from
"assets" "assets"
inner join "asset_job_status" as "job_status" on "assetId" = "assets"."id" inner join "asset_job_status" as "job_status" on "assetId" = "assets"."id"
where where
"assets"."isVisible" = $1 "assets"."visibility" != $1
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
and "job_status"."previewAt" is not null and "job_status"."previewAt" is not null
and not exists ( and not exists (
@ -234,7 +234,7 @@ where
-- AssetJobRepository.getForClipEncoding -- AssetJobRepository.getForClipEncoding
select select
"assets"."id", "assets"."id",
"assets"."isVisible", "assets"."visibility",
( (
select select
coalesce(json_agg(agg), '[]') coalesce(json_agg(agg), '[]')
@ -259,7 +259,7 @@ where
-- AssetJobRepository.getForDetectFacesJob -- AssetJobRepository.getForDetectFacesJob
select select
"assets"."id", "assets"."id",
"assets"."isVisible", "assets"."visibility",
to_json("exif") as "exifInfo", to_json("exif") as "exifInfo",
( (
select select
@ -312,7 +312,7 @@ where
-- AssetJobRepository.getForAssetDeletion -- AssetJobRepository.getForAssetDeletion
select select
"assets"."id", "assets"."id",
"assets"."isVisible", "assets"."visibility",
"assets"."libraryId", "assets"."libraryId",
"assets"."ownerId", "assets"."ownerId",
"assets"."livePhotoVideoId", "assets"."livePhotoVideoId",
@ -372,7 +372,7 @@ from
"assets" as "stacked" "assets" as "stacked"
where where
"stacked"."deletedAt" is not null "stacked"."deletedAt" is not null
and "stacked"."isArchived" = $1 and "stacked"."visibility" != $1
and "stacked"."stackId" = "asset_stack"."id" and "stacked"."stackId" = "asset_stack"."id"
group by group by
"asset_stack"."id" "asset_stack"."id"
@ -391,7 +391,7 @@ where
"assets"."encodedVideoPath" is null "assets"."encodedVideoPath" is null
or "assets"."encodedVideoPath" = $2 or "assets"."encodedVideoPath" = $2
) )
and "assets"."isVisible" = $3 and "assets"."visibility" != $3
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
-- AssetJobRepository.getForVideoConversion -- AssetJobRepository.getForVideoConversion
@ -417,7 +417,7 @@ where
"asset_job_status"."metadataExtractedAt" is null "asset_job_status"."metadataExtractedAt" is null
or "asset_job_status"."assetId" is null or "asset_job_status"."assetId" is null
) )
and "assets"."isVisible" = $1 and "assets"."visibility" != $1
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
-- AssetJobRepository.getForStorageTemplateJob -- AssetJobRepository.getForStorageTemplateJob
@ -480,7 +480,7 @@ where
"assets"."sidecarPath" = $1 "assets"."sidecarPath" = $1
or "assets"."sidecarPath" is null or "assets"."sidecarPath" is null
) )
and "assets"."isVisible" = $2 and "assets"."visibility" != $2
-- AssetJobRepository.streamForDetectFacesJob -- AssetJobRepository.streamForDetectFacesJob
select select
@ -489,7 +489,7 @@ from
"assets" "assets"
inner join "asset_job_status" as "job_status" on "assetId" = "assets"."id" inner join "asset_job_status" as "job_status" on "assetId" = "assets"."id"
where where
"assets"."isVisible" = $1 "assets"."visibility" != $1
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
and "job_status"."previewAt" is not null and "job_status"."previewAt" is not null
and "job_status"."facesRecognizedAt" is null and "job_status"."facesRecognizedAt" is null

View file

@ -43,21 +43,20 @@ with
"asset_job_status"."previewAt" is not null "asset_job_status"."previewAt" is not null
and (assets."localDateTime" at time zone 'UTC')::date = today.date and (assets."localDateTime" at time zone 'UTC')::date = today.date
and "assets"."ownerId" = any ($3::uuid[]) and "assets"."ownerId" = any ($3::uuid[])
and "assets"."isVisible" = $4 and "assets"."visibility" = $4
and "assets"."isArchived" = $5
and exists ( and exists (
select select
from from
"asset_files" "asset_files"
where where
"assetId" = "assets"."id" "assetId" = "assets"."id"
and "asset_files"."type" = $6 and "asset_files"."type" = $5
) )
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
order by order by
(assets."localDateTime" at time zone 'UTC')::date desc (assets."localDateTime" at time zone 'UTC')::date desc
limit limit
$7 $6
) as "a" on true ) as "a" on true
inner join "exif" on "a"."id" = "exif"."assetId" inner join "exif" on "a"."id" = "exif"."assetId"
) )
@ -159,7 +158,7 @@ from
where where
"ownerId" = $1::uuid "ownerId" = $1::uuid
and "deviceId" = $2 and "deviceId" = $2
and "isVisible" = $3 and "visibility" != $3
and "deletedAt" is null and "deletedAt" is null
-- AssetRepository.getLivePhotoCount -- AssetRepository.getLivePhotoCount
@ -241,7 +240,10 @@ with
"assets" "assets"
where where
"assets"."deletedAt" is null "assets"."deletedAt" is null
and "assets"."isVisible" = $2 and (
"assets"."visibility" = $2
or "assets"."visibility" = $3
)
) )
select select
"timeBucket", "timeBucket",
@ -271,7 +273,7 @@ from
where where
"stacked"."stackId" = "asset_stack"."id" "stacked"."stackId" = "asset_stack"."id"
and "stacked"."deletedAt" is null and "stacked"."deletedAt" is null
and "stacked"."isArchived" = $1 and "stacked"."visibility" != $1
group by group by
"asset_stack"."id" "asset_stack"."id"
) as "stacked_assets" on "asset_stack"."id" is not null ) as "stacked_assets" on "asset_stack"."id" is not null
@ -281,8 +283,11 @@ where
or "assets"."stackId" is null or "assets"."stackId" is null
) )
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
and "assets"."isVisible" = $2 and (
and date_trunc($3, "localDateTime" at time zone 'UTC') at time zone 'UTC' = $4 "assets"."visibility" = $2
or "assets"."visibility" = $3
)
and date_trunc($4, "localDateTime" at time zone 'UTC') at time zone 'UTC' = $5
order by order by
"assets"."localDateTime" desc "assets"."localDateTime" desc
@ -307,7 +312,7 @@ with
"assets"."ownerId" = $1::uuid "assets"."ownerId" = $1::uuid
and "assets"."duplicateId" is not null and "assets"."duplicateId" is not null
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
and "assets"."isVisible" = $2 and "assets"."visibility" != $2
and "assets"."stackId" is null and "assets"."stackId" is null
group by group by
"assets"."duplicateId" "assets"."duplicateId"
@ -365,12 +370,11 @@ from
inner join "cities" on "exif"."city" = "cities"."city" inner join "cities" on "exif"."city" = "cities"."city"
where where
"ownerId" = $2::uuid "ownerId" = $2::uuid
and "isVisible" = $3 and "visibility" = $3
and "isArchived" = $4 and "type" = $4
and "type" = $5
and "deletedAt" is null and "deletedAt" is null
limit limit
$6 $5
-- AssetRepository.getAllForUserFullSync -- AssetRepository.getAllForUserFullSync
select select
@ -394,7 +398,7 @@ from
) as "stacked_assets" on "asset_stack"."id" is not null ) as "stacked_assets" on "asset_stack"."id" is not null
where where
"assets"."ownerId" = $1::uuid "assets"."ownerId" = $1::uuid
and "assets"."isVisible" = $2 and "assets"."visibility" != $2
and "assets"."updatedAt" <= $3 and "assets"."updatedAt" <= $3
and "assets"."id" > $4 and "assets"."id" > $4
order by order by
@ -424,7 +428,7 @@ from
) as "stacked_assets" on "asset_stack"."id" is not null ) as "stacked_assets" on "asset_stack"."id" is not null
where where
"assets"."ownerId" = any ($1::uuid[]) "assets"."ownerId" = any ($1::uuid[])
and "assets"."isVisible" = $2 and "assets"."visibility" != $2
and "assets"."updatedAt" > $3 and "assets"."updatedAt" > $3
limit limit
$4 $4

View file

@ -35,14 +35,14 @@ select
where where
( (
"assets"."type" = $1 "assets"."type" = $1
and "assets"."isVisible" = $2 and "assets"."visibility" != $2
) )
) as "photos", ) as "photos",
count(*) filter ( count(*) filter (
where where
( (
"assets"."type" = $3 "assets"."type" = $3
and "assets"."isVisible" = $4 and "assets"."visibility" != $4
) )
) as "videos", ) as "videos",
coalesce(sum("exif"."fileSizeInByte"), $5) as "usage" coalesce(sum("exif"."fileSizeInByte"), $5) as "usage"

View file

@ -14,7 +14,7 @@ from
and "exif"."latitude" is not null and "exif"."latitude" is not null
and "exif"."longitude" is not null and "exif"."longitude" is not null
where where
"isVisible" = $1 "assets"."visibility" = $1
and "deletedAt" is null and "deletedAt" is null
and ( and (
"ownerId" in ($2) "ownerId" in ($2)

View file

@ -107,7 +107,7 @@ select
( (
select select
"assets"."ownerId", "assets"."ownerId",
"assets"."isArchived", "assets"."visibility",
"assets"."fileCreatedAt" "assets"."fileCreatedAt"
from from
"assets" "assets"
@ -143,23 +143,20 @@ select
"asset_faces"."boundingBoxY2" as "y2", "asset_faces"."boundingBoxY2" as "y2",
"asset_faces"."imageWidth" as "oldWidth", "asset_faces"."imageWidth" as "oldWidth",
"asset_faces"."imageHeight" as "oldHeight", "asset_faces"."imageHeight" as "oldHeight",
"exif"."exifImageWidth",
"exif"."exifImageHeight",
"assets"."type", "assets"."type",
"assets"."originalPath", "assets"."originalPath",
"asset_files"."path" as "previewPath" "asset_files"."path" as "previewPath",
"exif"."orientation" as "exifOrientation"
from from
"person" "person"
inner join "asset_faces" on "asset_faces"."id" = "person"."faceAssetId" inner join "asset_faces" on "asset_faces"."id" = "person"."faceAssetId"
inner join "assets" on "asset_faces"."assetId" = "assets"."id" inner join "assets" on "asset_faces"."assetId" = "assets"."id"
inner join "exif" on "exif"."assetId" = "assets"."id" left join "exif" on "exif"."assetId" = "assets"."id"
inner join "asset_files" on "asset_files"."assetId" = "assets"."id" left join "asset_files" on "asset_files"."assetId" = "assets"."id"
where where
"person"."id" = $1 "person"."id" = $1
and "asset_faces"."deletedAt" is null and "asset_faces"."deletedAt" is null
and "asset_files"."type" = $2 and "asset_files"."type" = $2
and "exif"."exifImageWidth" > $3
and "exif"."exifImageHeight" > $4
-- PersonRepository.reassignFace -- PersonRepository.reassignFace
update "asset_faces" update "asset_faces"
@ -203,7 +200,7 @@ from
"asset_faces" "asset_faces"
left join "assets" on "assets"."id" = "asset_faces"."assetId" left join "assets" on "assets"."id" = "asset_faces"."assetId"
and "asset_faces"."personId" = $1 and "asset_faces"."personId" = $1
and "assets"."isArchived" = $2 and "assets"."visibility" != $2
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
where where
"asset_faces"."deletedAt" is null "asset_faces"."deletedAt" is null
@ -220,7 +217,7 @@ from
inner join "asset_faces" on "asset_faces"."personId" = "person"."id" inner join "asset_faces" on "asset_faces"."personId" = "person"."id"
inner join "assets" on "assets"."id" = "asset_faces"."assetId" inner join "assets" on "assets"."id" = "asset_faces"."assetId"
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
and "assets"."isArchived" = $2 and "assets"."visibility" != $2
where where
"person"."ownerId" = $3 "person"."ownerId" = $3
and "asset_faces"."deletedAt" is null and "asset_faces"."deletedAt" is null

Some files were not shown because too many files have changed in this diff Show more