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.
- name: Initialize CodeQL
uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3
uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3
with:
languages: ${{ matrix.language }}
# 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).
# If this step fails, then you should remove it and run the build manually (see below)
- 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.
# 📚 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
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3
uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3
with:
category: '/language:${{matrix.language}}'

View file

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

View file

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

View file

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

View file

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

102
cli/package-lock.json generated
View file

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

View file

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

View file

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

View file

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

View file

@ -22,7 +22,7 @@ server {
client_max_body_size 50000M;
# 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-Forwarded-For $proxy_add_x_forwarded_for;
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_PASSWORD` | Database password | `postgres` | 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_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
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

View file

@ -2,6 +2,7 @@ import {
mdiBug,
mdiCalendarToday,
mdiCrosshairsOff,
mdiCrop,
mdiDatabase,
mdiLeadPencil,
mdiLockOff,
@ -22,6 +23,18 @@ const withLanguage = (date: Date) => (language: string) => date.toLocaleDateStri
type Item = Omit<TimelineItem, 'done' | 'getDateLabel'> & { date: Date };
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,
iconColor: '#357EC7',

View file

@ -3,50 +3,14 @@
"label": "v1.132.3",
"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",
"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",
"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",
"url": "https://v1.129.0.archive.immich.app"
@ -63,46 +27,14 @@
"label": "v1.126.1",
"url": "https://v1.126.1.archive.immich.app"
},
{
"label": "v1.126.0",
"url": "https://v1.126.0.archive.immich.app"
},
{
"label": "v1.125.7",
"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",
"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",
"url": "https://v1.123.0.archive.immich.app"
@ -111,18 +43,6 @@
"label": "v1.122.3",
"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",
"url": "https://v1.121.0.archive.immich.app"
@ -131,34 +51,14 @@
"label": "v1.120.2",
"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",
"url": "https://v1.119.1.archive.immich.app"
},
{
"label": "v1.119.0",
"url": "https://v1.119.0.archive.immich.app"
},
{
"label": "v1.118.2",
"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",
"url": "https://v1.117.0.archive.immich.app"
@ -167,14 +67,6 @@
"label": "v1.116.2",
"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",
"url": "https://v1.115.0.archive.immich.app"
@ -187,18 +79,10 @@
"label": "v1.113.1",
"url": "https://v1.113.1.archive.immich.app"
},
{
"label": "v1.113.0",
"url": "https://v1.113.0.archive.immich.app"
},
{
"label": "v1.112.1",
"url": "https://v1.112.1.archive.immich.app"
},
{
"label": "v1.112.0",
"url": "https://v1.112.0.archive.immich.app"
},
{
"label": "v1.111.0",
"url": "https://v1.111.0.archive.immich.app"
@ -211,14 +95,6 @@
"label": "v1.109.2",
"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",
"url": "https://v1.108.0.archive.immich.app"
@ -227,38 +103,14 @@
"label": "v1.107.2",
"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",
"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",
"url": "https://v1.105.1.archive.immich.app"
},
{
"label": "v1.105.0",
"url": "https://v1.105.0.archive.immich.app"
},
{
"label": "v1.104.0",
"url": "https://v1.104.0.archive.immich.app"
@ -267,26 +119,10 @@
"label": "v1.103.1",
"url": "https://v1.103.1.archive.immich.app"
},
{
"label": "v1.103.0",
"url": "https://v1.103.0.archive.immich.app"
},
{
"label": "v1.102.3",
"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",
"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": {
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz",
"integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==",
"version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz",
"integrity": "sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.31.0",
"@typescript-eslint/type-utils": "8.31.0",
"@typescript-eslint/utils": "8.31.0",
"@typescript-eslint/visitor-keys": "8.31.0",
"@typescript-eslint/scope-manager": "8.31.1",
"@typescript-eslint/type-utils": "8.31.1",
"@typescript-eslint/utils": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.1",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
@ -1713,16 +1713,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz",
"integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==",
"version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.1.tgz",
"integrity": "sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.31.0",
"@typescript-eslint/types": "8.31.0",
"@typescript-eslint/typescript-estree": "8.31.0",
"@typescript-eslint/visitor-keys": "8.31.0",
"@typescript-eslint/scope-manager": "8.31.1",
"@typescript-eslint/types": "8.31.1",
"@typescript-eslint/typescript-estree": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.1",
"debug": "^4.3.4"
},
"engines": {
@ -1738,14 +1738,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz",
"integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==",
"version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz",
"integrity": "sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.31.0",
"@typescript-eslint/visitor-keys": "8.31.0"
"@typescript-eslint/types": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1756,14 +1756,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz",
"integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==",
"version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz",
"integrity": "sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "8.31.0",
"@typescript-eslint/utils": "8.31.0",
"@typescript-eslint/typescript-estree": "8.31.1",
"@typescript-eslint/utils": "8.31.1",
"debug": "^4.3.4",
"ts-api-utils": "^2.0.1"
},
@ -1780,9 +1780,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz",
"integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==",
"version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.1.tgz",
"integrity": "sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==",
"dev": true,
"license": "MIT",
"engines": {
@ -1794,14 +1794,14 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz",
"integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==",
"version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz",
"integrity": "sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.31.0",
"@typescript-eslint/visitor-keys": "8.31.0",
"@typescript-eslint/types": "8.31.1",
"@typescript-eslint/visitor-keys": "8.31.1",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@ -1847,16 +1847,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz",
"integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==",
"version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.1.tgz",
"integrity": "sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.31.0",
"@typescript-eslint/types": "8.31.0",
"@typescript-eslint/typescript-estree": "8.31.0"
"@typescript-eslint/scope-manager": "8.31.1",
"@typescript-eslint/types": "8.31.1",
"@typescript-eslint/typescript-estree": "8.31.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1871,13 +1871,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz",
"integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==",
"version": "8.31.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz",
"integrity": "sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.31.0",
"@typescript-eslint/types": "8.31.1",
"eslint-visitor-keys": "^4.2.0"
},
"engines": {
@ -5222,14 +5222,14 @@
}
},
"node_modules/pg": {
"version": "8.15.5",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.15.5.tgz",
"integrity": "sha512-EpAhHFQc+aH9VfeffWIVC+XXk6lmAhS9W1FxtxcPXs94yxhrI1I6w/zkWfIOII/OkBv3Be04X3xMOj0kQ78l6w==",
"version": "8.15.6",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.15.6.tgz",
"integrity": "sha512-yvao7YI3GdmmrslNVsZgx9PfntfWrnXwtR+K/DjI0I/sTKif4Z623um+sjVZ1hk5670B+ODjvHDAckKdjmPTsg==",
"dev": true,
"license": "MIT",
"dependencies": {
"pg-connection-string": "^2.8.5",
"pg-pool": "^3.9.5",
"pg-pool": "^3.9.6",
"pg-protocol": "^1.9.5",
"pg-types": "^2.1.0",
"pgpass": "1.x"
@ -6747,15 +6747,15 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.0.tgz",
"integrity": "sha512-u+93F0sB0An8WEAPtwxVhFby573E8ckdjwUUQUj9QA4v8JAvgtoDdIyYR3XFwFHq2W1KJ1AurwJCO+w+Y1ixyQ==",
"version": "8.31.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.1.tgz",
"integrity": "sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.31.0",
"@typescript-eslint/parser": "8.31.0",
"@typescript-eslint/utils": "8.31.0"
"@typescript-eslint/eslint-plugin": "8.31.1",
"@typescript-eslint/parser": "8.31.1",
"@typescript-eslint/utils": "8.31.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"

View file

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

View file

@ -19,17 +19,6 @@ describe(`/auth/admin-sign-up`, () => {
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 () => {
await signUpAdmin({ signUpDto: signupDto.admin });
@ -57,22 +46,6 @@ describe('/auth/*', () => {
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 () => {
const { status, body, headers } = await request(app).post('/auth/login').send({ email, password });
expect(status).toBe(201);
@ -127,14 +100,6 @@ describe('/auth/*', () => {
});
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 () => {
const { status, body } = await request(app)
.post(`/auth/change-password`)

View file

@ -1,6 +1,5 @@
import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk';
import { readFile, writeFile } from 'node:fs/promises';
import { errorDto } from 'src/responses';
import { app, tempDir, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
@ -17,15 +16,6 @@ describe('/download', () => {
});
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 () => {
const { status, body } = await request(app)
.post('/download/info')
@ -42,15 +32,6 @@ describe('/download', () => {
});
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 () => {
const { status, body } = await request(app)
.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 { basename, join } from 'node:path';
import { Socket } from 'socket.io-client';
@ -44,7 +44,7 @@ describe('/map', () => {
it('should get map markers for all non-archived assets', async () => {
const { status, body } = await request(app)
.get('/map/markers')
.query({ isArchived: false })
.query({ visibility: AssetVisibility.Timeline })
.set('Authorization', `Bearer ${admin.accessToken}`);
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 { readFile } from 'node:fs/promises';
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-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
{ filename: '/formats/png/density_plot.png' },
@ -171,12 +178,12 @@ describe('/search', () => {
deferred: () => ({ dto: { size: 1, isFavorite: false }, assets: [assetLast] }),
},
{
should: 'should search by isArchived (true)',
deferred: () => ({ dto: { isArchived: true }, assets: [assetSprings] }),
should: 'should search by visibility (AssetVisibility.Archive)',
deferred: () => ({ dto: { visibility: AssetVisibility.Archive }, assets: [assetSprings] }),
},
{
should: 'should search by isArchived (false)',
deferred: () => ({ dto: { size: 1, isArchived: false }, assets: [assetLast] }),
should: 'should search by visibility (AssetVisibility.Timeline)',
deferred: () => ({ dto: { size: 1, visibility: AssetVisibility.Timeline }, assets: [assetLast] }),
},
{
should: 'should search by type (image)',
@ -185,7 +192,7 @@ describe('/search', () => {
{
should: 'should search by type (video)',
deferred: () => ({
dto: { type: 'VIDEO' },
dto: { type: 'VIDEO', visibility: AssetVisibility.Hidden },
assets: [
// the three live motion photos
{ id: expect.any(String) },
@ -229,13 +236,6 @@ describe('/search', () => {
should: 'should search by takenAfter (no results)',
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',
deferred: () => ({
@ -265,7 +265,7 @@ describe('/search', () => {
deferred: () => ({
dto: {
city: '',
isVisible: true,
visibility: AssetVisibility.Timeline,
includeNull: true,
},
assets: [assetLast],
@ -276,7 +276,7 @@ describe('/search', () => {
deferred: () => ({
dto: {
city: null,
isVisible: true,
visibility: AssetVisibility.Timeline,
includeNull: true,
},
assets: [assetLast],
@ -297,7 +297,7 @@ describe('/search', () => {
deferred: () => ({
dto: {
state: '',
isVisible: true,
visibility: AssetVisibility.Timeline,
withExif: true,
includeNull: true,
},
@ -309,7 +309,7 @@ describe('/search', () => {
deferred: () => ({
dto: {
state: null,
isVisible: true,
visibility: AssetVisibility.Timeline,
includeNull: true,
},
assets: [assetLast, assetNotocactus],
@ -330,7 +330,7 @@ describe('/search', () => {
deferred: () => ({
dto: {
country: '',
isVisible: true,
visibility: AssetVisibility.Timeline,
includeNull: true,
},
assets: [assetLast],
@ -341,7 +341,7 @@ describe('/search', () => {
deferred: () => ({
dto: {
country: null,
isVisible: true,
visibility: AssetVisibility.Timeline,
includeNull: true,
},
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 { createUserDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
@ -104,7 +104,7 @@ describe('/timeline', () => {
const req1 = await request(app)
.get('/timeline/buckets')
.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.body).toEqual(errorDto.badRequest());
@ -112,7 +112,7 @@ describe('/timeline', () => {
const req2 = await request(app)
.get('/timeline/buckets')
.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.body).toEqual(errorDto.badRequest());

View file

@ -3,6 +3,7 @@ import {
AssetMediaCreateDto,
AssetMediaResponseDto,
AssetResponseDto,
AssetVisibility,
CheckExistingAssetsDto,
CreateAlbumDto,
CreateLibraryDto,
@ -429,7 +430,10 @@ export const utils = {
},
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({ 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.waitForSelector('[data-group] svg');
await page.getByRole('checkbox').click();
await page.getByRole('button', { name: 'Download' }).click();
await page.waitForEvent('download');
await Promise.all([page.waitForEvent('download'), page.getByRole('button', { name: 'Download' }).click()]);
});
test('download all from shared link', async ({ page }) => {
await page.goto(`/share/${sharedLink.key}`);
await page.getByRole('heading', { name: 'Test Album' }).waitFor();
await page.getByRole('button', { name: 'Download' }).click();
await page.waitForEvent('download');
await Promise.all([page.waitForEvent('download'), page.getByRole('button', { name: 'Download' }).click()]);
});
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_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_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_title": "Prefer remote images",
"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_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_items": "{} items",
"album_thumbnail_card_items": "{count} items",
"album_thumbnail_card_shared": " · Shared",
"album_thumbnail_shared_by": "Shared by {}",
"album_thumbnail_shared_by": "Shared by {user}",
"album_updated": "Album updated",
"album_updated_setting_description": "Receive an email notification when a shared album has new assets",
"album_user_left": "Left {album}",
@ -440,7 +440,7 @@
"archive": "Archive",
"archive_or_unarchive_photo": "Archive or unarchive photo",
"archive_page_no_archived_assets": "No archived assets found",
"archive_page_title": "Archive ({})",
"archive_page_title": "Archive ({count})",
"archive_size": "Archive size",
"archive_size_description": "Configure the archive size for downloads (in GiB)",
"archived": "Archived",
@ -477,18 +477,18 @@
"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_count": "{count, plural, one {# asset} other {# assets}}",
"assets_deleted_permanently": "{} asset(s) deleted permanently",
"assets_deleted_permanently_from_server": "{} asset(s) deleted permanently from the Immich server",
"assets_deleted_permanently": "{count} asset(s) deleted permanently",
"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_permanently_deleted_count": "Permanently deleted {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_restored_count": "Restored {count, plural, one {# asset} other {# assets}}",
"assets_restored_successfully": "{} asset(s) restored successfully",
"assets_trashed": "{} asset(s) trashed",
"assets_restored_successfully": "{count} asset(s) restored successfully",
"assets_trashed": "{count} asset(s) trashed",
"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",
"authorized_devices": "Authorized Devices",
"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",
"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",
"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_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",
@ -506,11 +506,11 @@
"backup_all": "All",
"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_current_upload_notification": "Uploading {}",
"backup_background_service_current_upload_notification": "Uploading {filename}",
"backup_background_service_default_notification": "Checking for new assets…",
"backup_background_service_error_title": "Backup error",
"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_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",
@ -521,7 +521,7 @@
"backup_controller_page_background_battery_info_title": "Battery optimizations",
"backup_controller_page_background_charging": "Only while charging",
"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_is_off": "Automatic background backup is off",
"backup_controller_page_background_is_on": "Automatic background backup is on",
@ -531,12 +531,12 @@
"backup_controller_page_backup": "Backup",
"backup_controller_page_backup_selected": "Selected: ",
"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_excluded": "Excluded: ",
"backup_controller_page_failed": "Failed ({})",
"backup_controller_page_filename": "File name: {} [{}]",
"backup_controller_page_id": "ID: {}",
"backup_controller_page_failed": "Failed ({count})",
"backup_controller_page_filename": "File name: {filename} [{size}]",
"backup_controller_page_id": "ID: {id}",
"backup_controller_page_info": "Backup Information",
"backup_controller_page_none_selected": "None selected",
"backup_controller_page_remainder": "Remainder",
@ -545,7 +545,7 @@
"backup_controller_page_start_backup": "Start Backup",
"backup_controller_page_status_off": "Automatic foreground backup is off",
"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_total_sub": "All unique photos and videos from selected albums",
"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_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",
"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_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_subtitle": "Photos and videos that are black listed by the app",
"cache_settings_duplicated_assets_title": "Duplicated Assets ({})",
"cache_settings_image_cache_size": "Image cache size ({} assets)",
"cache_settings_duplicated_assets_title": "Duplicated Assets ({count})",
"cache_settings_image_cache_size": "Image cache size ({count} assets)",
"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_shared": "Shared album thumbnails",
"cache_settings_statistics_thumbnail": "Thumbnails",
"cache_settings_statistics_title": "Cache usage",
"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_title": "Local Storage",
"cache_settings_title": "Caching Settings",
@ -654,7 +654,7 @@
"contain": "Contain",
"context": "Context",
"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_delete_from_immich": "Delete from Immich",
"control_bottom_app_bar_delete_from_local": "Delete from device",
@ -763,7 +763,7 @@
"download_enqueue": "Download enqueued",
"download_error": "Download Error",
"download_failed": "Download failed",
"download_filename": "file: {}",
"download_filename": "file: {filename}",
"download_finished": "Download finished",
"download_include_embedded_motion_videos": "Embedded videos",
"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_delete_face": "Error deleting face from asset",
"error_loading_image": "Error loading image",
"error_saving_image": "Error: {}",
"error_saving_image": "Error: {error}",
"error_title": "Error - Something went wrong",
"errors": {
"cannot_navigate_next_asset": "Cannot navigate to the next asset",
@ -955,10 +955,10 @@
"exif_bottom_sheet_location": "LOCATION",
"exif_bottom_sheet_people": "PEOPLE",
"exif_bottom_sheet_person_add_person": "Add name",
"exif_bottom_sheet_person_age": "Age {}",
"exif_bottom_sheet_person_age_months": "Age {} months",
"exif_bottom_sheet_person_age_year_months": "Age 1 year, {} months",
"exif_bottom_sheet_person_age_years": "Age {}",
"exif_bottom_sheet_person_age": "Age {age}",
"exif_bottom_sheet_person_age_months": "Age {months} months",
"exif_bottom_sheet_person_age_year_months": "Age 1 year, {months} months",
"exif_bottom_sheet_person_age_years": "Age {years}",
"exit_slideshow": "Exit Slideshow",
"expand_all": "Expand all",
"experimental_settings_new_asset_list_subtitle": "Work in progress",
@ -1173,8 +1173,8 @@
"manage_your_devices": "Manage your logged-in devices",
"manage_your_oauth_connection": "Manage your OAuth connection",
"map": "Map",
"map_assets_in_bound": "{} photo",
"map_assets_in_bounds": "{} photos",
"map_assets_in_bound": "{count} photo",
"map_assets_in_bounds": "{count} photos",
"map_cannot_get_user_location": "Cannot get user's location",
"map_location_dialog_yes": "Yes",
"map_location_picker_page_use_location": "Use this location",
@ -1188,9 +1188,9 @@
"map_settings": "Map settings",
"map_settings_dark_mode": "Dark mode",
"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_years": "Past {} years",
"map_settings_date_range_option_years": "Past {years} years",
"map_settings_dialog_title": "Map Settings",
"map_settings_include_show_archived": "Include Archived",
"map_settings_include_show_partners": "Include Partners",
@ -1209,7 +1209,7 @@
"memories_start_over": "Start Over",
"memories_swipe_to_close": "Swipe up to close",
"memories_year_ago": "A year ago",
"memories_years_ago": "{} years ago",
"memories_years_ago": "{years} years ago",
"memory": "Memory",
"memory_lane_title": "Memory Lane {title}",
"menu": "Menu",
@ -1316,7 +1316,7 @@
"partner_page_partner_add_failed": "Failed to add partner",
"partner_page_select_partner": "Select partner",
"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",
"partners": "Partners",
"password": "Password",
@ -1604,12 +1604,12 @@
"setting_languages_apply": "Apply",
"setting_languages_subtitle": "Change the app's language",
"setting_languages_title": "Languages",
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
"setting_notifications_notify_hours": "{} hours",
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {duration}",
"setting_notifications_notify_hours": "{count} hours",
"setting_notifications_notify_immediately": "immediately",
"setting_notifications_notify_minutes": "{} minutes",
"setting_notifications_notify_minutes": "{count} minutes",
"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_title": "Show background backup detail progress",
"setting_notifications_subtitle": "Adjust your notification preferences",
@ -1623,7 +1623,7 @@
"settings_saved": "Settings saved",
"share": "Share",
"share_add_photos": "Add photos",
"share_assets_selected": "{} selected",
"share_assets_selected": "{count} selected",
"share_dialog_preparing": "Preparing...",
"shared": "Shared",
"shared_album_activities_input_disable": "Comment is disabled",
@ -1637,32 +1637,32 @@
"shared_by_user": "Shared by {user}",
"shared_by_you": "Shared by you",
"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_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_edit_description_hint": "Enter the share description",
"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_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_minutes": "{} minutes",
"shared_link_edit_expire_after_option_months": "{} months",
"shared_link_edit_expire_after_option_year": "{} year",
"shared_link_edit_expire_after_option_minutes": "{count} minutes",
"shared_link_edit_expire_after_option_months": "{count} months",
"shared_link_edit_expire_after_option_year": "{count} year",
"shared_link_edit_password_hint": "Enter the share password",
"shared_link_edit_submit_button": "Update link",
"shared_link_error_server_url_fetch": "Cannot fetch the server url",
"shared_link_expires_day": "Expires in {} day",
"shared_link_expires_days": "Expires in {} days",
"shared_link_expires_hour": "Expires in {} hour",
"shared_link_expires_hours": "Expires in {} hours",
"shared_link_expires_minute": "Expires in {} minute",
"shared_link_expires_minutes": "Expires in {} minutes",
"shared_link_expires_day": "Expires in {count} day",
"shared_link_expires_days": "Expires in {count} days",
"shared_link_expires_hour": "Expires in {count} hour",
"shared_link_expires_hours": "Expires in {count} hours",
"shared_link_expires_minute": "Expires in {count} minute",
"shared_link_expires_minutes": "Expires in {count} minutes",
"shared_link_expires_never": "Expires ∞",
"shared_link_expires_second": "Expires in {} second",
"shared_link_expires_seconds": "Expires in {} seconds",
"shared_link_expires_second": "Expires in {count} second",
"shared_link_expires_seconds": "Expires in {count} seconds",
"shared_link_individual_shared": "Individual shared",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_manage_links": "Manage Shared links",
@ -1763,7 +1763,7 @@
"theme_selection": "Theme selection",
"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_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_title": "Colorful interface",
"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_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_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_restore_all": "Restore All",
"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}}.",
"type": "Type",
"unarchive": "Unarchive",
@ -1840,7 +1840,7 @@
"upload_status_errors": "Errors",
"upload_status_uploaded": "Uploaded",
"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",
"url": "URL",
"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_hooks/flutter_hooks.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/build_context_extensions.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/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
@RoutePage()
class AlbumAssetSelectionPage extends HookConsumerWidget {
@ -59,7 +59,7 @@ class AlbumAssetSelectionPage extends HookConsumerWidget {
: const Text(
'share_assets_selected',
style: TextStyle(fontSize: 18),
).tr(args: [selected.value.length.toString()]),
).tr(namedArgs: {'count': selected.value.length.toString()}),
centerTitle: false,
actions: [
if (selected.value.isNotEmpty || canDeselect)

View file

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

View file

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

View file

@ -1,18 +1,18 @@
import 'dart:typed_data';
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:fluttertoast/fluttertoast.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/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: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;
/// A stateless widget that provides functionality for editing an image.
@ -81,7 +81,7 @@ class EditImagePage extends ConsumerWidget {
ImmichToast.show(
durationInSecond: 6,
context: context,
msg: "error_saving_image".tr(args: [e.toString()]),
msg: "error_saving_image".tr(namedArgs: {'error': e.toString()}),
gravity: ToastGravity.CENTER,
);
}

View file

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

View file

@ -73,7 +73,8 @@ class PartnerPage extends HookConsumerWidget {
builder: (BuildContext context) {
return ConfirmDialog(
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),
);
},

View file

@ -7,11 +7,11 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/shared_link.provider.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/widgets/common/immich_toast.dart';
@RoutePage()
class SharedLinkEditPage extends HookConsumerWidget {
@ -241,8 +241,8 @@ class SharedLinkEditPage extends HookConsumerWidget {
),
DropdownMenuEntry(
value: 30,
label:
"shared_link_edit_expire_after_option_minutes".tr(args: ["30"]),
label: "shared_link_edit_expire_after_option_minutes"
.tr(namedArgs: {'count': "30"}),
),
DropdownMenuEntry(
value: 60,
@ -250,7 +250,8 @@ class SharedLinkEditPage extends HookConsumerWidget {
),
DropdownMenuEntry(
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(
value: 60 * 24,
@ -258,20 +259,23 @@ class SharedLinkEditPage extends HookConsumerWidget {
),
DropdownMenuEntry(
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(
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(
value: 60 * 24 * 30 * 3,
label:
"shared_link_edit_expire_after_option_months".tr(args: ["3"]),
label: "shared_link_edit_expire_after_option_months"
.tr(namedArgs: {'count': "3"}),
),
DropdownMenuEntry(
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:fluttertoast/fluttertoast.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/build_context_extensions.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/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/immich_toast.dart';
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
@RoutePage()
class TrashPage extends HookConsumerWidget {
@ -77,7 +77,7 @@ class TrashPage extends HookConsumerWidget {
ImmichToast.show(
context: context,
msg: 'assets_deleted_permanently'
.tr(args: ["${selection.value.length}"]),
.tr(namedArgs: {'count': "${selection.value.length}"}),
gravity: ToastGravity.BOTTOM,
);
}
@ -118,7 +118,7 @@ class TrashPage extends HookConsumerWidget {
ImmichToast.show(
context: context,
msg: 'assets_restored_successfully'
.tr(args: ["${selection.value.length}"]),
.tr(namedArgs: {'count': "${selection.value.length}"}),
gravity: ToastGravity.BOTTOM,
);
}
@ -135,7 +135,7 @@ class TrashPage extends HookConsumerWidget {
? "${selection.value.length}"
: "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) {
@ -260,7 +260,7 @@ class TrashPage extends HookConsumerWidget {
),
child: const Text(
"trash_page_info",
).tr(args: ["$trashDays"]),
).tr(namedArgs: {"days": "$trashDays"}),
),
),
),

View file

@ -56,9 +56,7 @@ class ShareIntentPage extends HookConsumerWidget {
title: Column(
children: [
const Text('upload_to_immich').tr(
args: [
candidates.length.toString(),
],
namedArgs: {'count': candidates.length.toString()},
),
Text(
currentEndpoint,
@ -176,8 +174,12 @@ class UploadingText extends StatelessWidget {
return element.status == UploadStatus.complete;
}).length;
return const Text("shared_intent_upload_button_progress_text")
.tr(args: [uploadedCount.toString(), candidates.length.toString()]);
return const Text("shared_intent_upload_button_progress_text").tr(
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:fluttertoast/fluttertoast.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/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/current_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/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/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/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/entities/asset.entity.dart';
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
import 'package:immich_mobile/services/background.service.dart';
import 'package:immich_mobile/services/backup.service.dart';
import 'package:immich_mobile/services/backup_album.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/widgets/common/immich_toast.dart';
import 'package:logging/logging.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:photo_manager/photo_manager.dart' show PMProgressHandler;
@ -170,7 +170,7 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
if (state.showDetailedNotification) {
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);
}
}
@ -186,7 +186,7 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
if (state.showDetailedNotification) {
_throttledDetailNotify.title =
"backup_background_service_current_upload_notification"
.tr(args: [currentUploadAsset.fileName]);
.tr(namedArgs: {'filename': currentUploadAsset.fileName});
_throttledDetailNotify.progress = 0;
_throttledDetailNotify.total = 0;
}

View file

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

View file

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

View file

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

View file

@ -68,7 +68,9 @@ class SearchService {
model: filter.camera.model,
takenAfter: filter.date.takenAfter,
takenBefore: filter.date.takenBefore,
isArchived: filter.display.isArchive ? true : null,
visibility: filter.display.isArchive
? AssetVisibility.archive
: AssetVisibility.timeline,
isFavorite: filter.display.isFavorite ? true : null,
isNotInAlbum: filter.display.isNotInAlbum ? true : null,
personIds: filter.people.map((e) => e.id).toList(),
@ -95,7 +97,9 @@ class SearchService {
model: filter.camera.model,
takenAfter: filter.date.takenAfter,
takenBefore: filter.date.takenBefore,
isArchived: filter.display.isArchive ? true : null,
visibility: filter.display.isArchive
? AssetVisibility.archive
: AssetVisibility.timeline,
isFavorite: filter.display.isFavorite ? true : null,
isNotInAlbum: filter.display.isNotInAlbum ? true : null,
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) {
owner = 'owned'.tr();
} 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: [
TextSpan(
text: album.assetCount == 1
? 'album_thumbnail_card_item'
.tr(args: ['${album.assetCount}'])
? 'album_thumbnail_card_item'.tr()
: 'album_thumbnail_card_items'
.tr(args: ['${album.assetCount}']),
.tr(namedArgs: {'count': '${album.assetCount}'}),
),
if (owner != null) const TextSpan(text: ''),
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:easy_localization/easy_localization.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/routing/router.dart';
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:openapi/api.dart';
@ -96,7 +96,7 @@ class AlbumThumbnailListTile extends StatelessWidget {
style: const TextStyle(
fontSize: 12,
),
).tr(args: ['${album.assetCount}']),
).tr(namedArgs: {'count': '${album.assetCount}'}),
if (album.shared)
const Text(
'album_thumbnail_card_shared',

View file

@ -7,24 +7,24 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fluttertoast/fluttertoast.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/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/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/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/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 {
const MultiselectGrid({
@ -190,8 +190,9 @@ class MultiselectGrid extends HookConsumerWidget {
context: context,
msg: force
? 'assets_deleted_permanently'
.tr(args: ["${selection.value.length}"])
: 'assets_trashed'.tr(args: ["${selection.value.length}"]),
.tr(namedArgs: {'count': "${selection.value.length}"})
: 'assets_trashed'
.tr(namedArgs: {'count': "${selection.value.length}"}),
gravity: ToastGravity.BOTTOM,
);
selectionEnabledHook.value = false;
@ -225,7 +226,7 @@ class MultiselectGrid extends HookConsumerWidget {
ImmichToast.show(
context: context,
msg: 'assets_removed_permanently_from_device'
.tr(args: ["$deletedCount"]),
.tr(namedArgs: {'count': "$deletedCount"}),
gravity: ToastGravity.BOTTOM,
);
@ -254,8 +255,9 @@ class MultiselectGrid extends HookConsumerWidget {
context: context,
msg: shouldDeletePermanently
? 'assets_deleted_permanently_from_server'
.tr(args: ["${toDelete.length}"])
: 'assets_trashed_from_server'.tr(args: ["${toDelete.length}"]),
.tr(namedArgs: {'count': "${toDelete.length}"})
: 'assets_trashed_from_server'
.tr(namedArgs: {'count': "${toDelete.length}"}),
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:flutter/material.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/providers/asset_viewer/asset_people.provider.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/person_name_edit_form.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
class PeopleInfo extends ConsumerWidget {
final Asset asset;
@ -109,13 +109,13 @@ class PeopleInfo extends ConsumerWidget {
if (ageInMonths <= 11) {
return "exif_bottom_sheet_person_age_months"
.tr(args: [ageInMonths.toString()]);
.tr(namedArgs: {'months': ageInMonths.toString()});
} else if (ageInMonths > 12 && ageInMonths <= 23) {
return "exif_bottom_sheet_person_age_year_months"
.tr(args: [(ageInMonths - 12).toString()]);
.tr(namedArgs: {'months': (ageInMonths - 12).toString()});
} else {
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,
),
).tr(
args: isUploadInProgress
? [asset.fileName, asset.fileType.toLowerCase()]
: ["-", "-"],
namedArgs: isUploadInProgress
? {
'filename': asset.fileName,
'size': asset.fileType.toLowerCase(),
}
: {'filename': "-", 'size': "-"},
),
),
),
@ -78,9 +81,11 @@ class BackupAssetInfoTable extends ConsumerWidget {
fontSize: 10.0,
),
).tr(
args: [
isUploadInProgress ? _getAssetCreationDate(asset) : "-",
],
namedArgs: {
'date': isUploadInProgress
? _getAssetCreationDate(asset)
: "-",
},
),
),
),
@ -99,9 +104,7 @@ class BackupAssetInfoTable extends ConsumerWidget {
fontSize: 10.0,
),
).tr(
args: [
isUploadInProgress ? asset.id : "-",
],
namedArgs: {'id': isUploadInProgress ? asset.id : "-"},
),
),
),

View file

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

View file

@ -5,17 +5,17 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.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/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/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_server_info.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';
class ImmichAppBarDialog extends HookConsumerWidget {
@ -200,10 +200,10 @@ class ImmichAppBarDialog extends HookConsumerWidget {
padding: const EdgeInsets.only(top: 12.0),
child:
const Text('backup_controller_page_storage_format').tr(
args: [
usedDiskSpace,
totalDiskSpace,
],
namedArgs: {
'used': usedDiskSpace,
'total': totalDiskSpace,
},
),
),
],

View file

@ -1,20 +1,21 @@
import 'dart:math' as math;
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_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/entities/asset.entity.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/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:scrollable_positioned_list/scrollable_positioned_list.dart';
@ -252,7 +253,8 @@ class _MapSheetDragRegion extends StatelessWidget {
@override
Widget build(BuildContext context) {
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();
return SingleChildScrollView(

View file

@ -44,13 +44,13 @@ class MapTimeDropDown extends StatelessWidget {
DropdownMenuEntry(
value: 7,
label: "map_settings_date_range_option_days".tr(
args: ["7"],
namedArgs: {'days': "7"},
),
),
DropdownMenuEntry(
value: 30,
label: "map_settings_date_range_option_days".tr(
args: ["30"],
namedArgs: {'days': "30"},
),
),
DropdownMenuEntry(
@ -81,7 +81,8 @@ class MapTimeDropDown extends StatelessWidget {
),
)
.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(
text: "advanced_settings_log_level_title".tr(args: [logLevel]),
text: "advanced_settings_log_level_title"
.tr(namedArgs: {'level': logLevel}),
valueNotifier: levelId,
maxValue: 8,
minValue: 1,

View file

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

View file

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

View file

@ -1,9 +1,9 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
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: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/providers/db.provider.dart';
@ -35,7 +35,7 @@ class LocalStorageSettings extends HookConsumerWidget {
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
).tr(args: ["${cacheItemCount.value}"]),
).tr(namedArgs: {'count': "${cacheItemCount.value}"}),
subtitle: Text(
"cache_settings_duplicated_assets_subtitle",
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/providers/notification_permission.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:permission_handler/permission_handler.dart';
class NotificationSetting extends HookConsumerWidget {
@ -90,7 +90,7 @@ class NotificationSetting extends HookConsumerWidget {
enabled: hasPermission,
valueNotifier: sliderValue,
text: 'setting_notifications_notify_failures_grace_period'
.tr(args: [formattedValue]),
.tr(namedArgs: {'duration': formattedValue}),
maxValue: 5.0,
noDivisons: 5,
label: formattedValue,
@ -105,13 +105,14 @@ String _formatSliderValue(double v) {
if (v == 0.0) {
return 'setting_notifications_notify_immediately'.tr();
} else if (v == 1.0) {
return 'setting_notifications_notify_minutes'.tr(args: const ['30']);
return 'setting_notifications_notify_minutes'
.tr(namedArgs: {'count': '30'});
} 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) {
return 'setting_notifications_notify_hours'.tr(args: const ['8']);
return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '8'});
} else if (v == 4.0) {
return 'setting_notifications_notify_hours'.tr(args: const ['24']);
return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '24'});
} else {
return 'setting_notifications_notify_never'.tr();
}

View file

@ -1,4 +1,5 @@
import 'dart:math' as math;
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -6,15 +7,15 @@ import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/shared_link.provider.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/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 {
final SharedLink sharedLink;
@ -44,17 +45,17 @@ class SharedLinkItem extends ConsumerWidget {
if (difference.inHours % 24 > 12) {
dayDifference += 1;
}
expiresText =
"shared_link_expires_days".tr(args: [dayDifference.toString()]);
expiresText = "shared_link_expires_days"
.tr(namedArgs: {'count': dayDifference.toString()});
} else if (difference.inHours > 0) {
expiresText = "shared_link_expires_hours"
.tr(args: [difference.inHours.toString()]);
.tr(namedArgs: {'count': difference.inHours.toString()});
} else if (difference.inMinutes > 0) {
expiresText = "shared_link_expires_minutes"
.tr(args: [difference.inMinutes.toString()]);
.tr(namedArgs: {'count': difference.inMinutes.toString()});
} else if (difference.inSeconds > 0) {
expiresText = "shared_link_expires_seconds"
.tr(args: [difference.inSeconds.toString()]);
.tr(namedArgs: {'count': difference.inSeconds.toString()});
}
}
return Text(

View file

@ -302,6 +302,7 @@ Class | Method | HTTP request | Description
- [AssetStackResponseDto](doc//AssetStackResponseDto.md)
- [AssetStatsResponseDto](doc//AssetStatsResponseDto.md)
- [AssetTypeEnum](doc//AssetTypeEnum.md)
- [AssetVisibility](doc//AssetVisibility.md)
- [AudioCodec](doc//AudioCodec.md)
- [AvatarUpdate](doc//AvatarUpdate.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_stats_response_dto.dart';
part 'model/asset_type_enum.dart';
part 'model/asset_visibility.dart';
part 'model/audio_codec.dart';
part 'model/avatar_update.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].
/// Parameters:
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [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
final apiPath = r'/assets/statistics';
@ -358,15 +358,15 @@ class AssetsApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (isArchived != null) {
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
}
if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
}
if (isTrashed != null) {
queryParams.addAll(_queryParams('', 'isTrashed', isTrashed));
}
if (visibility != null) {
queryParams.addAll(_queryParams('', 'visibility', visibility));
}
const contentTypes = <String>[];
@ -384,13 +384,13 @@ class AssetsApi {
/// Parameters:
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [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) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@ -788,16 +788,14 @@ class AssetsApi {
///
/// * [String] duration:
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [bool] isVisible:
///
/// * [String] livePhotoVideoId:
///
/// * [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
final apiPath = r'/assets';
@ -845,18 +843,10 @@ class AssetsApi {
hasFields = true;
mp.fields[r'fileModifiedAt'] = parameterToString(fileModifiedAt);
}
if (isArchived != null) {
hasFields = true;
mp.fields[r'isArchived'] = parameterToString(isArchived);
}
if (isFavorite != null) {
hasFields = true;
mp.fields[r'isFavorite'] = parameterToString(isFavorite);
}
if (isVisible != null) {
hasFields = true;
mp.fields[r'isVisible'] = parameterToString(isVisible);
}
if (livePhotoVideoId != null) {
hasFields = true;
mp.fields[r'livePhotoVideoId'] = parameterToString(livePhotoVideoId);
@ -866,6 +856,10 @@ class AssetsApi {
mp.fields[r'sidecarData'] = sidecarData.field;
mp.files.add(sidecarData);
}
if (visibility != null) {
hasFields = true;
mp.fields[r'visibility'] = parameterToString(visibility);
}
if (hasFields) {
postBody = mp;
}
@ -900,17 +894,15 @@ class AssetsApi {
///
/// * [String] duration:
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [bool] isVisible:
///
/// * [String] livePhotoVideoId:
///
/// * [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) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View file

@ -25,8 +25,6 @@ class TimelineApi {
///
/// * [String] albumId:
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [bool] isTrashed:
@ -41,10 +39,12 @@ class TimelineApi {
///
/// * [String] userId:
///
/// * [AssetVisibility] visibility:
///
/// * [bool] withPartners:
///
/// * [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
final apiPath = r'/timeline/bucket';
@ -58,9 +58,6 @@ class TimelineApi {
if (albumId != null) {
queryParams.addAll(_queryParams('', 'albumId', albumId));
}
if (isArchived != null) {
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
}
if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
}
@ -84,6 +81,9 @@ class TimelineApi {
if (userId != null) {
queryParams.addAll(_queryParams('', 'userId', userId));
}
if (visibility != null) {
queryParams.addAll(_queryParams('', 'visibility', visibility));
}
if (withPartners != null) {
queryParams.addAll(_queryParams('', 'withPartners', withPartners));
}
@ -113,8 +113,6 @@ class TimelineApi {
///
/// * [String] albumId:
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [bool] isTrashed:
@ -129,11 +127,13 @@ class TimelineApi {
///
/// * [String] userId:
///
/// * [AssetVisibility] visibility:
///
/// * [bool] withPartners:
///
/// * [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 {
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, );
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, 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) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@ -157,8 +157,6 @@ class TimelineApi {
///
/// * [String] albumId:
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [bool] isTrashed:
@ -173,10 +171,12 @@ class TimelineApi {
///
/// * [String] userId:
///
/// * [AssetVisibility] visibility:
///
/// * [bool] withPartners:
///
/// * [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
final apiPath = r'/timeline/buckets';
@ -190,9 +190,6 @@ class TimelineApi {
if (albumId != null) {
queryParams.addAll(_queryParams('', 'albumId', albumId));
}
if (isArchived != null) {
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
}
if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
}
@ -215,6 +212,9 @@ class TimelineApi {
if (userId != null) {
queryParams.addAll(_queryParams('', 'userId', userId));
}
if (visibility != null) {
queryParams.addAll(_queryParams('', 'visibility', visibility));
}
if (withPartners != null) {
queryParams.addAll(_queryParams('', 'withPartners', withPartners));
}
@ -242,8 +242,6 @@ class TimelineApi {
///
/// * [String] albumId:
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [bool] isTrashed:
@ -258,11 +256,13 @@ class TimelineApi {
///
/// * [String] userId:
///
/// * [AssetVisibility] visibility:
///
/// * [bool] withPartners:
///
/// * [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 {
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, );
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, 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) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View file

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

View file

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

View file

@ -16,11 +16,11 @@ class AssetBulkUpdateDto {
this.dateTimeOriginal,
this.duplicateId,
this.ids = const [],
this.isArchived,
this.isFavorite,
this.latitude,
this.longitude,
this.rating,
this.visibility,
});
///
@ -35,14 +35,6 @@ class AssetBulkUpdateDto {
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
/// does not include a default value (using the "default:" property), however, the generated
@ -77,16 +69,24 @@ class AssetBulkUpdateDto {
///
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
bool operator ==(Object other) => identical(this, other) || other is AssetBulkUpdateDto &&
other.dateTimeOriginal == dateTimeOriginal &&
other.duplicateId == duplicateId &&
_deepEquality.equals(other.ids, ids) &&
other.isArchived == isArchived &&
other.isFavorite == isFavorite &&
other.latitude == latitude &&
other.longitude == longitude &&
other.rating == rating;
other.rating == rating &&
other.visibility == visibility;
@override
int get hashCode =>
@ -94,14 +94,14 @@ class AssetBulkUpdateDto {
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
(duplicateId == null ? 0 : duplicateId!.hashCode) +
(ids.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) +
(latitude == null ? 0 : latitude!.hashCode) +
(longitude == null ? 0 : longitude!.hashCode) +
(rating == null ? 0 : rating!.hashCode);
(rating == null ? 0 : rating!.hashCode) +
(visibility == null ? 0 : visibility!.hashCode);
@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() {
final json = <String, dynamic>{};
@ -116,11 +116,6 @@ class AssetBulkUpdateDto {
// json[r'duplicateId'] = null;
}
json[r'ids'] = this.ids;
if (this.isArchived != null) {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isFavorite != null) {
json[r'isFavorite'] = this.isFavorite;
} else {
@ -141,6 +136,11 @@ class AssetBulkUpdateDto {
} else {
// json[r'rating'] = null;
}
if (this.visibility != null) {
json[r'visibility'] = this.visibility;
} else {
// json[r'visibility'] = null;
}
return json;
}
@ -158,11 +158,11 @@ class AssetBulkUpdateDto {
ids: json[r'ids'] is Iterable
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
: const [],
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
latitude: num.parse('${json[r'latitude']}'),
longitude: num.parse('${json[r'longitude']}'),
rating: num.parse('${json[r'rating']}'),
visibility: AssetVisibility.fromJson(json[r'visibility']),
);
}
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.encodedVideoPath,
this.id,
this.isArchived,
this.isEncoded,
this.isFavorite,
this.isMotion,
this.isNotInAlbum,
this.isOffline,
this.isVisible,
this.lensModel,
this.libraryId,
this.make,
@ -52,7 +50,7 @@ class MetadataSearchDto {
this.type,
this.updatedAfter,
this.updatedBefore,
this.withArchived = false,
this.visibility,
this.withDeleted,
this.withExif,
this.withPeople,
@ -127,14 +125,6 @@ class MetadataSearchDto {
///
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
/// does not include a default value (using the "default:" property), however, the generated
@ -175,14 +165,6 @@ class MetadataSearchDto {
///
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? libraryId;
@ -322,7 +304,13 @@ class MetadataSearchDto {
///
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
@ -368,13 +356,11 @@ class MetadataSearchDto {
other.deviceId == deviceId &&
other.encodedVideoPath == encodedVideoPath &&
other.id == id &&
other.isArchived == isArchived &&
other.isEncoded == isEncoded &&
other.isFavorite == isFavorite &&
other.isMotion == isMotion &&
other.isNotInAlbum == isNotInAlbum &&
other.isOffline == isOffline &&
other.isVisible == isVisible &&
other.lensModel == lensModel &&
other.libraryId == libraryId &&
other.make == make &&
@ -397,7 +383,7 @@ class MetadataSearchDto {
other.type == type &&
other.updatedAfter == updatedAfter &&
other.updatedBefore == updatedBefore &&
other.withArchived == withArchived &&
other.visibility == visibility &&
other.withDeleted == withDeleted &&
other.withExif == withExif &&
other.withPeople == withPeople &&
@ -416,13 +402,11 @@ class MetadataSearchDto {
(deviceId == null ? 0 : deviceId!.hashCode) +
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
(id == null ? 0 : id!.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isEncoded == null ? 0 : isEncoded!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) +
(isMotion == null ? 0 : isMotion!.hashCode) +
(isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) +
(isOffline == null ? 0 : isOffline!.hashCode) +
(isVisible == null ? 0 : isVisible!.hashCode) +
(lensModel == null ? 0 : lensModel!.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) +
(make == null ? 0 : make!.hashCode) +
@ -445,14 +429,14 @@ class MetadataSearchDto {
(type == null ? 0 : type!.hashCode) +
(updatedAfter == null ? 0 : updatedAfter!.hashCode) +
(updatedBefore == null ? 0 : updatedBefore!.hashCode) +
(withArchived.hashCode) +
(visibility == null ? 0 : visibility!.hashCode) +
(withDeleted == null ? 0 : withDeleted!.hashCode) +
(withExif == null ? 0 : withExif!.hashCode) +
(withPeople == null ? 0 : withPeople!.hashCode) +
(withStacked == null ? 0 : withStacked!.hashCode);
@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() {
final json = <String, dynamic>{};
@ -506,11 +490,6 @@ class MetadataSearchDto {
} else {
// json[r'id'] = null;
}
if (this.isArchived != null) {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isEncoded != null) {
json[r'isEncoded'] = this.isEncoded;
} else {
@ -536,11 +515,6 @@ class MetadataSearchDto {
} else {
// json[r'isOffline'] = null;
}
if (this.isVisible != null) {
json[r'isVisible'] = this.isVisible;
} else {
// json[r'isVisible'] = null;
}
if (this.lensModel != null) {
json[r'lensModel'] = this.lensModel;
} else {
@ -639,7 +613,11 @@ class MetadataSearchDto {
} else {
// 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) {
json[r'withDeleted'] = this.withDeleted;
} else {
@ -682,13 +660,11 @@ class MetadataSearchDto {
deviceId: mapValueOfType<String>(json, r'deviceId'),
encodedVideoPath: mapValueOfType<String>(json, r'encodedVideoPath'),
id: mapValueOfType<String>(json, r'id'),
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isEncoded: mapValueOfType<bool>(json, r'isEncoded'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
isMotion: mapValueOfType<bool>(json, r'isMotion'),
isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'),
isOffline: mapValueOfType<bool>(json, r'isOffline'),
isVisible: mapValueOfType<bool>(json, r'isVisible'),
lensModel: mapValueOfType<String>(json, r'lensModel'),
libraryId: mapValueOfType<String>(json, r'libraryId'),
make: mapValueOfType<String>(json, r'make'),
@ -715,7 +691,7 @@ class MetadataSearchDto {
type: AssetTypeEnum.fromJson(json[r'type']),
updatedAfter: mapDateTime(json, r'updatedAfter', 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'),
withExif: mapValueOfType<bool>(json, r'withExif'),
withPeople: mapValueOfType<bool>(json, r'withPeople'),

View file

@ -18,13 +18,11 @@ class RandomSearchDto {
this.createdAfter,
this.createdBefore,
this.deviceId,
this.isArchived,
this.isEncoded,
this.isFavorite,
this.isMotion,
this.isNotInAlbum,
this.isOffline,
this.isVisible,
this.lensModel,
this.libraryId,
this.make,
@ -41,7 +39,7 @@ class RandomSearchDto {
this.type,
this.updatedAfter,
this.updatedBefore,
this.withArchived = false,
this.visibility,
this.withDeleted,
this.withExif,
this.withPeople,
@ -76,14 +74,6 @@ class RandomSearchDto {
///
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
/// does not include a default value (using the "default:" property), however, the generated
@ -124,14 +114,6 @@ class RandomSearchDto {
///
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? libraryId;
@ -228,7 +210,13 @@ class RandomSearchDto {
///
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
@ -269,13 +257,11 @@ class RandomSearchDto {
other.createdAfter == createdAfter &&
other.createdBefore == createdBefore &&
other.deviceId == deviceId &&
other.isArchived == isArchived &&
other.isEncoded == isEncoded &&
other.isFavorite == isFavorite &&
other.isMotion == isMotion &&
other.isNotInAlbum == isNotInAlbum &&
other.isOffline == isOffline &&
other.isVisible == isVisible &&
other.lensModel == lensModel &&
other.libraryId == libraryId &&
other.make == make &&
@ -292,7 +278,7 @@ class RandomSearchDto {
other.type == type &&
other.updatedAfter == updatedAfter &&
other.updatedBefore == updatedBefore &&
other.withArchived == withArchived &&
other.visibility == visibility &&
other.withDeleted == withDeleted &&
other.withExif == withExif &&
other.withPeople == withPeople &&
@ -306,13 +292,11 @@ class RandomSearchDto {
(createdAfter == null ? 0 : createdAfter!.hashCode) +
(createdBefore == null ? 0 : createdBefore!.hashCode) +
(deviceId == null ? 0 : deviceId!.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isEncoded == null ? 0 : isEncoded!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) +
(isMotion == null ? 0 : isMotion!.hashCode) +
(isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) +
(isOffline == null ? 0 : isOffline!.hashCode) +
(isVisible == null ? 0 : isVisible!.hashCode) +
(lensModel == null ? 0 : lensModel!.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) +
(make == null ? 0 : make!.hashCode) +
@ -329,14 +313,14 @@ class RandomSearchDto {
(type == null ? 0 : type!.hashCode) +
(updatedAfter == null ? 0 : updatedAfter!.hashCode) +
(updatedBefore == null ? 0 : updatedBefore!.hashCode) +
(withArchived.hashCode) +
(visibility == null ? 0 : visibility!.hashCode) +
(withDeleted == null ? 0 : withDeleted!.hashCode) +
(withExif == null ? 0 : withExif!.hashCode) +
(withPeople == null ? 0 : withPeople!.hashCode) +
(withStacked == null ? 0 : withStacked!.hashCode);
@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() {
final json = <String, dynamic>{};
@ -365,11 +349,6 @@ class RandomSearchDto {
} else {
// json[r'deviceId'] = null;
}
if (this.isArchived != null) {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isEncoded != null) {
json[r'isEncoded'] = this.isEncoded;
} else {
@ -395,11 +374,6 @@ class RandomSearchDto {
} else {
// json[r'isOffline'] = null;
}
if (this.isVisible != null) {
json[r'isVisible'] = this.isVisible;
} else {
// json[r'isVisible'] = null;
}
if (this.lensModel != null) {
json[r'lensModel'] = this.lensModel;
} else {
@ -472,7 +446,11 @@ class RandomSearchDto {
} else {
// 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) {
json[r'withDeleted'] = this.withDeleted;
} else {
@ -510,13 +488,11 @@ class RandomSearchDto {
createdAfter: mapDateTime(json, r'createdAfter', r''),
createdBefore: mapDateTime(json, r'createdBefore', r''),
deviceId: mapValueOfType<String>(json, r'deviceId'),
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isEncoded: mapValueOfType<bool>(json, r'isEncoded'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
isMotion: mapValueOfType<bool>(json, r'isMotion'),
isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'),
isOffline: mapValueOfType<bool>(json, r'isOffline'),
isVisible: mapValueOfType<bool>(json, r'isVisible'),
lensModel: mapValueOfType<String>(json, r'lensModel'),
libraryId: mapValueOfType<String>(json, r'libraryId'),
make: mapValueOfType<String>(json, r'make'),
@ -537,7 +513,7 @@ class RandomSearchDto {
type: AssetTypeEnum.fromJson(json[r'type']),
updatedAfter: mapDateTime(json, r'updatedAfter', 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'),
withExif: mapValueOfType<bool>(json, r'withExif'),
withPeople: mapValueOfType<bool>(json, r'withPeople'),

View file

@ -18,13 +18,11 @@ class SmartSearchDto {
this.createdAfter,
this.createdBefore,
this.deviceId,
this.isArchived,
this.isEncoded,
this.isFavorite,
this.isMotion,
this.isNotInAlbum,
this.isOffline,
this.isVisible,
this.language,
this.lensModel,
this.libraryId,
@ -44,7 +42,7 @@ class SmartSearchDto {
this.type,
this.updatedAfter,
this.updatedBefore,
this.withArchived = false,
this.visibility,
this.withDeleted,
this.withExif,
});
@ -77,14 +75,6 @@ class SmartSearchDto {
///
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
/// does not include a default value (using the "default:" property), however, the generated
@ -125,14 +115,6 @@ class SmartSearchDto {
///
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
/// does not include a default value (using the "default:" property), however, the generated
@ -248,7 +230,13 @@ class SmartSearchDto {
///
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
@ -273,13 +261,11 @@ class SmartSearchDto {
other.createdAfter == createdAfter &&
other.createdBefore == createdBefore &&
other.deviceId == deviceId &&
other.isArchived == isArchived &&
other.isEncoded == isEncoded &&
other.isFavorite == isFavorite &&
other.isMotion == isMotion &&
other.isNotInAlbum == isNotInAlbum &&
other.isOffline == isOffline &&
other.isVisible == isVisible &&
other.language == language &&
other.lensModel == lensModel &&
other.libraryId == libraryId &&
@ -299,7 +285,7 @@ class SmartSearchDto {
other.type == type &&
other.updatedAfter == updatedAfter &&
other.updatedBefore == updatedBefore &&
other.withArchived == withArchived &&
other.visibility == visibility &&
other.withDeleted == withDeleted &&
other.withExif == withExif;
@ -311,13 +297,11 @@ class SmartSearchDto {
(createdAfter == null ? 0 : createdAfter!.hashCode) +
(createdBefore == null ? 0 : createdBefore!.hashCode) +
(deviceId == null ? 0 : deviceId!.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isEncoded == null ? 0 : isEncoded!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) +
(isMotion == null ? 0 : isMotion!.hashCode) +
(isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) +
(isOffline == null ? 0 : isOffline!.hashCode) +
(isVisible == null ? 0 : isVisible!.hashCode) +
(language == null ? 0 : language!.hashCode) +
(lensModel == null ? 0 : lensModel!.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) +
@ -337,12 +321,12 @@ class SmartSearchDto {
(type == null ? 0 : type!.hashCode) +
(updatedAfter == null ? 0 : updatedAfter!.hashCode) +
(updatedBefore == null ? 0 : updatedBefore!.hashCode) +
(withArchived.hashCode) +
(visibility == null ? 0 : visibility!.hashCode) +
(withDeleted == null ? 0 : withDeleted!.hashCode) +
(withExif == null ? 0 : withExif!.hashCode);
@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() {
final json = <String, dynamic>{};
@ -371,11 +355,6 @@ class SmartSearchDto {
} else {
// json[r'deviceId'] = null;
}
if (this.isArchived != null) {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isEncoded != null) {
json[r'isEncoded'] = this.isEncoded;
} else {
@ -401,11 +380,6 @@ class SmartSearchDto {
} else {
// json[r'isOffline'] = null;
}
if (this.isVisible != null) {
json[r'isVisible'] = this.isVisible;
} else {
// json[r'isVisible'] = null;
}
if (this.language != null) {
json[r'language'] = this.language;
} else {
@ -489,7 +463,11 @@ class SmartSearchDto {
} else {
// 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) {
json[r'withDeleted'] = this.withDeleted;
} else {
@ -517,13 +495,11 @@ class SmartSearchDto {
createdAfter: mapDateTime(json, r'createdAfter', r''),
createdBefore: mapDateTime(json, r'createdBefore', r''),
deviceId: mapValueOfType<String>(json, r'deviceId'),
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isEncoded: mapValueOfType<bool>(json, r'isEncoded'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
isMotion: mapValueOfType<bool>(json, r'isMotion'),
isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'),
isOffline: mapValueOfType<bool>(json, r'isOffline'),
isVisible: mapValueOfType<bool>(json, r'isVisible'),
language: mapValueOfType<String>(json, r'language'),
lensModel: mapValueOfType<String>(json, r'lensModel'),
libraryId: mapValueOfType<String>(json, r'libraryId'),
@ -547,7 +523,7 @@ class SmartSearchDto {
type: AssetTypeEnum.fromJson(json[r'type']),
updatedAfter: mapDateTime(json, r'updatedAfter', 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'),
withExif: mapValueOfType<bool>(json, r'withExif'),
);

View file

@ -19,11 +19,11 @@ class SyncAssetV1 {
required this.fileModifiedAt,
required this.id,
required this.isFavorite,
required this.isVisible,
required this.localDateTime,
required this.ownerId,
required this.thumbhash,
required this.type,
required this.visibility,
});
String checksum;
@ -38,8 +38,6 @@ class SyncAssetV1 {
bool isFavorite;
bool isVisible;
DateTime? localDateTime;
String ownerId;
@ -48,6 +46,8 @@ class SyncAssetV1 {
SyncAssetV1TypeEnum type;
SyncAssetV1VisibilityEnum visibility;
@override
bool operator ==(Object other) => identical(this, other) || other is SyncAssetV1 &&
other.checksum == checksum &&
@ -56,11 +56,11 @@ class SyncAssetV1 {
other.fileModifiedAt == fileModifiedAt &&
other.id == id &&
other.isFavorite == isFavorite &&
other.isVisible == isVisible &&
other.localDateTime == localDateTime &&
other.ownerId == ownerId &&
other.thumbhash == thumbhash &&
other.type == type;
other.type == type &&
other.visibility == visibility;
@override
int get hashCode =>
@ -71,14 +71,14 @@ class SyncAssetV1 {
(fileModifiedAt == null ? 0 : fileModifiedAt!.hashCode) +
(id.hashCode) +
(isFavorite.hashCode) +
(isVisible.hashCode) +
(localDateTime == null ? 0 : localDateTime!.hashCode) +
(ownerId.hashCode) +
(thumbhash == null ? 0 : thumbhash!.hashCode) +
(type.hashCode);
(type.hashCode) +
(visibility.hashCode);
@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() {
final json = <String, dynamic>{};
@ -100,7 +100,6 @@ class SyncAssetV1 {
}
json[r'id'] = this.id;
json[r'isFavorite'] = this.isFavorite;
json[r'isVisible'] = this.isVisible;
if (this.localDateTime != null) {
json[r'localDateTime'] = this.localDateTime!.toUtc().toIso8601String();
} else {
@ -113,6 +112,7 @@ class SyncAssetV1 {
// json[r'thumbhash'] = null;
}
json[r'type'] = this.type;
json[r'visibility'] = this.visibility;
return json;
}
@ -131,11 +131,11 @@ class SyncAssetV1 {
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r''),
id: mapValueOfType<String>(json, r'id')!,
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
isVisible: mapValueOfType<bool>(json, r'isVisible')!,
localDateTime: mapDateTime(json, r'localDateTime', r''),
ownerId: mapValueOfType<String>(json, r'ownerId')!,
thumbhash: mapValueOfType<String>(json, r'thumbhash'),
type: SyncAssetV1TypeEnum.fromJson(json[r'type'])!,
visibility: SyncAssetV1VisibilityEnum.fromJson(json[r'visibility'])!,
);
}
return null;
@ -189,11 +189,11 @@ class SyncAssetV1 {
'fileModifiedAt',
'id',
'isFavorite',
'isVisible',
'localDateTime',
'ownerId',
'thumbhash',
'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({
this.dateTimeOriginal,
this.description,
this.isArchived,
this.isFavorite,
this.latitude,
this.livePhotoVideoId,
this.longitude,
this.rating,
this.visibility,
});
///
@ -39,14 +39,6 @@ class UpdateAssetDto {
///
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
/// does not include a default value (using the "default:" property), however, the generated
@ -83,31 +75,39 @@ class UpdateAssetDto {
///
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
bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto &&
other.dateTimeOriginal == dateTimeOriginal &&
other.description == description &&
other.isArchived == isArchived &&
other.isFavorite == isFavorite &&
other.latitude == latitude &&
other.livePhotoVideoId == livePhotoVideoId &&
other.longitude == longitude &&
other.rating == rating;
other.rating == rating &&
other.visibility == visibility;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
(description == null ? 0 : description!.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) +
(latitude == null ? 0 : latitude!.hashCode) +
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
(longitude == null ? 0 : longitude!.hashCode) +
(rating == null ? 0 : rating!.hashCode);
(rating == null ? 0 : rating!.hashCode) +
(visibility == null ? 0 : visibility!.hashCode);
@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() {
final json = <String, dynamic>{};
@ -121,11 +121,6 @@ class UpdateAssetDto {
} else {
// json[r'description'] = null;
}
if (this.isArchived != null) {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isFavorite != null) {
json[r'isFavorite'] = this.isFavorite;
} else {
@ -151,6 +146,11 @@ class UpdateAssetDto {
} else {
// json[r'rating'] = null;
}
if (this.visibility != null) {
json[r'visibility'] = this.visibility;
} else {
// json[r'visibility'] = null;
}
return json;
}
@ -165,12 +165,12 @@ class UpdateAssetDto {
return UpdateAssetDto(
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
description: mapValueOfType<String>(json, r'description'),
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
latitude: num.parse('${json[r'latitude']}'),
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
longitude: num.parse('${json[r'longitude']}'),
rating: num.parse('${json[r'rating']}'),
visibility: AssetVisibility.fromJson(json[r'visibility']),
);
}
return null;

View file

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

View file

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

370
server/package-lock.json generated
View file

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

View file

@ -48,7 +48,7 @@
"@opentelemetry/context-async-hooks": "^2.0.0",
"@opentelemetry/exporter-prometheus": "^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",
"archiver": "^7.0.0",
"async-lock": "^1.4.0",

View file

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

View file

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

View file

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

View file

@ -100,38 +100,29 @@ describe(AssetMediaController.name, () => {
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())
.post('/assets')
.attach('assetData', assetData, filename)
.field({ ...makeUploadDto(), isVisible: 'not-a-boolean' });
.field({ ...makeUploadDto(), visibility: 'not-a-boolean' });
expect(status).toBe(400);
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`
describe.skip('GET /assets/:id/original', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/original`);
expect(ctx.authenticate).toHaveBeenCalled();
});
});
});
// TODO figure out how to deal with `sendFile`
describe.skip('GET /assets/:id/original', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/original`);
expect(ctx.authenticate).toHaveBeenCalled();
});
});
// TODO figure out how to deal with `sendFile`
describe.skip('GET /assets/:id/thumbnail', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/thumbnail`);
expect(ctx.authenticate).toHaveBeenCalled();
// TODO figure out how to deal with `sendFile`
describe.skip('GET /assets/:id/thumbnail', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/thumbnail`);
expect(ctx.authenticate).toHaveBeenCalled();
});
});
});
});

View file

@ -1,4 +1,5 @@
import { AuthController } from 'src/controllers/auth.controller';
import { LoginResponseDto } from 'src/dtos/auth.dto';
import { AuthService } from 'src/services/auth.service';
import request from 'supertest';
import { errorDto } from 'test/medium/responses';
@ -14,6 +15,7 @@ describe(AuthController.name, () => {
});
beforeEach(() => {
service.resetAllMocks();
ctx.reset();
});
@ -56,5 +58,88 @@ describe(AuthController.name, () => {
expect(status).toEqual(201);
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')
async login(
@Body() loginCredential: LoginCredentialDto,
@Res({ passthrough: true }) res: Response,
@Body() loginCredential: LoginCredentialDto,
@GetLoginDetails() loginDetails: LoginDetails,
): Promise<LoginResponseDto> {
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, () => {
let ctx: ControllerContext;
const service = mockBaseService(NotificationService);
beforeAll(async () => {
ctx = await controllerSetup(NotificationController, [
{ provide: NotificationService, useValue: mockBaseService(NotificationService) },
]);
ctx = await controllerSetup(NotificationController, [{ provide: NotificationService, useValue: service }]);
return () => ctx.close();
});
beforeEach(() => {
service.resetAllMocks();
ctx.reset();
});

View file

@ -6,15 +6,15 @@ import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'
describe(SearchController.name, () => {
let ctx: ControllerContext;
const service = mockBaseService(SearchService);
beforeAll(async () => {
ctx = await controllerSetup(SearchController, [
{ provide: SearchService, useValue: mockBaseService(SearchService) },
]);
ctx = await controllerSetup(SearchController, [{ provide: SearchService, useValue: service }]);
return () => ctx.close();
});
beforeEach(() => {
service.resetAllMocks();
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']));
});
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())
.post('/search/metadata')
.send({ isArchived: 'immich' });
.send({ visibility: 'immich' });
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 () => {
@ -98,104 +100,98 @@ describe(SearchController.name, () => {
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', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).post('/search/random');
expect(ctx.authenticate).toHaveBeenCalled();
});
describe('POST /search/random', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).post('/search/random');
expect(ctx.authenticate).toHaveBeenCalled();
it('should reject if withStacked is not a boolean', async () => {
const { status, body } = await request(ctx.getHttpServer())
.post('/search/random')
.send({ withStacked: 'immich' });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['withStacked must be a boolean value']));
});
it('should reject if withPeople is not a boolean', async () => {
const { status, body } = await request(ctx.getHttpServer())
.post('/search/random')
.send({ withPeople: 'immich' });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['withPeople must be a boolean value']));
});
});
it('should reject if withStacked is not a boolean', async () => {
const { status, body } = await request(ctx.getHttpServer())
.post('/search/random')
.send({ withStacked: 'immich' });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['withStacked must be a boolean value']));
describe('POST /search/smart', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).post('/search/smart');
expect(ctx.authenticate).toHaveBeenCalled();
});
it('should require a query', async () => {
const { status, body } = await request(ctx.getHttpServer()).post('/search/smart').send({});
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['query should not be empty', 'query must be a string']));
});
});
it('should reject if withPeople is not a boolean', async () => {
const { status, body } = await request(ctx.getHttpServer()).post('/search/random').send({ withPeople: 'immich' });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['withPeople must be a boolean value']));
});
});
describe('POST /search/smart', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).post('/search/smart');
expect(ctx.authenticate).toHaveBeenCalled();
describe('GET /search/explore', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get('/search/explore');
expect(ctx.authenticate).toHaveBeenCalled();
});
});
it('should require a query', async () => {
const { status, body } = await request(ctx.getHttpServer()).post('/search/smart').send({});
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['query should not be empty', 'query must be a string']));
});
});
describe('POST /search/person', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get('/search/person');
expect(ctx.authenticate).toHaveBeenCalled();
});
describe('GET /search/explore', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get('/search/explore');
expect(ctx.authenticate).toHaveBeenCalled();
});
});
describe('POST /search/person', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get('/search/person');
expect(ctx.authenticate).toHaveBeenCalled();
it('should require a name', async () => {
const { status, body } = await request(ctx.getHttpServer()).get('/search/person').send({});
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['name should not be empty', 'name must be a string']));
});
});
it('should require a name', async () => {
const { status, body } = await request(ctx.getHttpServer()).get('/search/person').send({});
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['name should not be empty', 'name must be a string']));
});
});
describe('GET /search/places', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get('/search/places');
expect(ctx.authenticate).toHaveBeenCalled();
});
describe('GET /search/places', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get('/search/places');
expect(ctx.authenticate).toHaveBeenCalled();
it('should require a name', async () => {
const { status, body } = await request(ctx.getHttpServer()).get('/search/places').send({});
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['name should not be empty', 'name must be a string']));
});
});
it('should require a name', async () => {
const { status, body } = await request(ctx.getHttpServer()).get('/search/places').send({});
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['name should not be empty', 'name must be a string']));
});
});
describe('GET /search/cities', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get('/search/cities');
expect(ctx.authenticate).toHaveBeenCalled();
});
});
describe('GET /search/suggestions', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get('/search/suggestions');
expect(ctx.authenticate).toHaveBeenCalled();
describe('GET /search/cities', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get('/search/cities');
expect(ctx.authenticate).toHaveBeenCalled();
});
});
it('should require a type', async () => {
const { status, body } = await request(ctx.getHttpServer()).get('/search/suggestions').send({});
expect(status).toBe(400);
expect(body).toEqual(
errorDto.badRequest([
'type should not be empty',
expect.stringContaining('type must be one of the following values:'),
]),
);
describe('GET /search/suggestions', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get('/search/suggestions');
expect(ctx.authenticate).toHaveBeenCalled();
});
it('should require a type', async () => {
const { status, body } = await request(ctx.getHttpServer()).get('/search/suggestions').send({});
expect(status).toBe(400);
expect(body).toEqual(
errorDto.badRequest([
'type should not be empty',
expect.stringContaining('type must be one of the following values:'),
]),
);
});
});
});
});

View file

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

View file

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

View file

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

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

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

View file

@ -1,7 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
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 {
/**
@ -55,11 +56,8 @@ export class AssetMediaCreateDto extends AssetMediaBase {
@ValidateBoolean({ optional: true })
isFavorite?: boolean;
@ValidateBoolean({ optional: true })
isArchived?: boolean;
@ValidateBoolean({ optional: true })
isVisible?: boolean;
@ValidateAssetVisibility({ optional: true })
visibility?: AssetVisibility;
@ValidateUUID({ optional: true })
livePhotoVideoId?: string;

View file

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

View file

@ -14,9 +14,9 @@ import {
ValidateIf,
} from 'class-validator';
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 { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateUUID } from 'src/validation';
export class DeviceIdDto {
@IsNotEmpty()
@ -32,8 +32,8 @@ export class UpdateAssetBase {
@ValidateBoolean({ optional: true })
isFavorite?: boolean;
@ValidateBoolean({ optional: true })
isArchived?: boolean;
@ValidateAssetVisibility({ optional: true })
visibility?: AssetVisibility;
@Optional()
@IsDateString()
@ -105,8 +105,8 @@ export class AssetJobsDto extends AssetIdsDto {
}
export class AssetStatsDto {
@ValidateBoolean({ optional: true })
isArchived?: boolean;
@ValidateAssetVisibility({ optional: true })
visibility?: AssetVisibility;
@ValidateBoolean({ optional: true })
isFavorite?: boolean;

View file

@ -1,6 +1,6 @@
import { Transform, Type } from 'class-transformer';
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';
export class EnvDto {
@ -142,6 +142,10 @@ export class EnvDto {
@ValidateBoolean({ optional: true })
DB_SKIP_MIGRATIONS?: boolean;
@IsEnum(DatabaseSslMode)
@Optional()
DB_SSL_MODE?: DatabaseSslMode;
@IsString()
@Optional()
DB_URL?: string;

View file

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

View file

@ -1,7 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsInt, IsPositive, IsString } from 'class-validator';
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';
export class AssetFullSyncDto {
@ -67,7 +67,7 @@ export class SyncAssetV1 {
type!: AssetType;
deletedAt!: Date | null;
isFavorite!: boolean;
isVisible!: boolean;
visibility!: AssetVisibility;
}
export class SyncAssetDeleteV1 {

View file

@ -1,8 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
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 { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateUUID } from 'src/validation';
export class TimeBucketDto {
@IsNotEmpty()
@ -22,9 +22,6 @@ export class TimeBucketDto {
@ValidateUUID({ optional: true })
tagId?: string;
@ValidateBoolean({ optional: true })
isArchived?: boolean;
@ValidateBoolean({ optional: true })
isFavorite?: boolean;
@ -41,6 +38,9 @@ export class TimeBucketDto {
@Optional()
@ApiProperty({ enum: AssetOrder, enumName: 'AssetOrder' })
order?: AssetOrder;
@ValidateAssetVisibility({ optional: true })
visibility?: AssetVisibility;
}
export class TimeBucketAssetDto extends TimeBucketDto {

View file

@ -610,3 +610,21 @@ export enum OAuthTokenEndpointAuthMethod {
CLIENT_SECRET_POST = 'client_secret_post',
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
where
"partner"."sharedWithId" = $1
and "assets"."isArchived" = $2
and "assets"."id" in ($3)
and (
"assets"."visibility" = 'timeline'
or "assets"."visibility" = 'hidden'
)
and "assets"."id" in ($2)
-- AccessRepository.asset.checkSharedLinkAccess
select

View file

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

View file

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

View file

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

View file

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

View file

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

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