Compare commits

...

42 Commits

Author SHA1 Message Date
Min Idzelis 9806e94f6f wip 2025-04-30 03:50:13 +00:00
Jason Rasmussen 2e8a286540 refactor: smart search queue (#17977) 2025-04-29 17:44:28 -04:00
Jason Rasmussen 038a82c4f1 refactor: theme manager (#17976) 2025-04-29 17:44:09 -04:00
renovate[bot] 2c2dd01bf0 fix(deps): update machine-learning (#17951) 2025-04-29 20:02:58 +00:00
Ben ac73e163df chore(mobile): translate toast messages (#17964) 2025-04-29 14:26:41 -05:00
Jason Rasmussen d89e88bb3f feat: configure token endpoint auth method (#17968) 2025-04-29 15:17:48 -04:00
Thomas 3ce353393a chore(server): don't insert embeddings if the model has changed (#17885)
* chore(server): don't insert embeddings if the model has changed

We're moving away from the heuristic of waiting for queues to complete. The job
which inserts embeddings can simply check if the model has changed before
inserting, rather than attempting to lock the queue.

* more robust dim size update

* use check constraint

* index command cleanup

* add create statement

* update medium test, create appropriate extension

* new line

* set dimension size when running on all assets

* why does it want braces smh

* take 2

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2025-04-29 14:23:01 -04:00
Min Idzelis 0e4cf9ac57 feat(web): responsive date group header height (#17944)
* feat: responsive date group header height

* update tests

* feat(web): improve perf when changing mobile orientation (#17945)

fix: improve perf when changing mobile orientation
2025-04-29 13:59:06 -04:00
Min Idzelis 07290580a6 feat: improve semantic nav/main tags (#17800)
feat: nav/main elements

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-04-29 13:51:39 -04:00
AverageHelper d9ce74b896 chore: add security.txt (#17952)
* feat: Create .well-known/security.txt

* feat: Add another security.txt for the main website

* fix: deploy hidden files

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2025-04-29 13:48:06 -04:00
Jason Rasmussen 4c0f79b162 fix: use lint:p in checkall script (#17969) 2025-04-29 17:34:36 +00:00
renovate[bot] 9851d24628 chore(deps): update docker.io/valkey/valkey:8-bookworm docker digest to c855f98 (#17948)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 12:08:50 +01:00
renovate[bot] fe6cbd93b1 chore(deps): pin dependencies (#17947)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 12:08:40 +01:00
renovate[bot] df20788088 chore(deps): update grafana/grafana docker tag to v11.6.1 (#17955)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 12:08:08 +01:00
renovate[bot] 3d042cc7f1 fix(deps): update typescript-projects (#17961)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 13:00:37 +02:00
renovate[bot] 85446c5862 chore(deps): update redis:6.2-alpine docker digest to 3211c33 (#17950)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 10:09:25 +00:00
renovate[bot] fb52ac0f5b chore(deps): update node.js to v22.15.0 (#17956)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 12:08:32 +02:00
Eli Gao 48bcbee6ed feat(server): JXL previews from DNG 1.7+ (#17861)
* feat(server): JXL previews from RAW

* refactor(server): use var name assumedExtractedFormat for clarity

* test(server): fix existing media.extract() returning JPEG

* chore(openapi): regen

* style(server): lint

* fix(server): ignore undefined decode orientation

* fix(server): correct orientation assignment in media decode options

* test(server): unit tests of JXL-encoded DNG

* refactor(server): return buffer and format from mediaRepository.extract()

* chore(open-api): regen

* refactor

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2025-04-28 18:18:46 -04:00
Daniel Dietzler f621f8ef2c refactor: more job queries (#17745) 2025-04-29 00:03:20 +02:00
Jason Rasmussen 7f69abbf0d refactor: app init event (#17937) 2025-04-28 14:48:33 -04:00
Jason Rasmussen 895b2bf5cd refactor: download manager (#17935) 2025-04-28 14:21:24 -04:00
Jason Rasmussen f64e6f5dc3 refactor: auth login event (#17934) 2025-04-28 14:13:14 -04:00
Luke Towers 64e738f79d feat(web): move duplicates controls above preview of duplicate images (#17837)
Move duplicates controls above preview of duplicate images
2025-04-28 16:10:40 +00:00
Daniel Dietzler a17390a422 refactor: move managers to new folder (#17929) 2025-04-28 16:56:04 +02:00
Jason Rasmussen 1b5fc9c665 feat: notifications (#17701)
* feat: notifications

* UI works

* chore: pr feedback

* initial fetch and clear notification upon logging out

* fix: merge

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-04-28 10:36:14 -04:00
Yaros 23717ce981 feat(mobile): save grid size on gesture resize (#17891) 2025-04-28 09:23:33 -05:00
Min Idzelis 2fd05e8447 feat: preload and cancel images with a service worker (#16893)
* feat: Service Worker to preload/cancel images and other resources

* Remove caddy configuration, localhost is secure if port-forwarded

* fix e2e tests

* Cache/return the app.html for all web entry points

* Only handle preload/cancel

* fix e2e

* fix e2e

* e2e-2

* that'll do it

* format

* fix test

* lint

* refactor common code to conditionals

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-04-28 14:23:05 +00:00
Min Idzelis c664d99a34 refactor: vscode - format/organize on save (#17928) 2025-04-28 10:11:19 -04:00
Andreas Tollkötter 21c7d70336 feat(mobile): Capitalize month names in asset grid (#17898)
* capitalize month titles

* capitalize day titles as well
2025-04-28 13:56:36 +00:00
Jason Rasmussen ad272333db refactor: user avatar color (#17753) 2025-04-28 08:54:51 -05:00
Zack Pollard 460d594791 feat: api response compression (#17878) 2025-04-28 08:54:11 -05:00
Jason Rasmussen e6c575c33e feat: rtl (#17860) 2025-04-28 08:53:53 -05:00
Andreas Tollkötter 85ac0512a6 fix(web): Make date-time formatting follow locale (#17899)
* fixed missing $locale parameter to .toLocaleString

* Remove unused types and functions in timeline-util

* remove unused export

* re-enable export because it is needed for tests

* format
2025-04-28 08:53:26 -05:00
Alex 205260d31c chore: post release tasks (#17895) 2025-04-27 23:02:03 -05:00
Alex 3858973be5 chore(mobile): translation (#17920) 2025-04-27 23:00:40 -05:00
github-actions 02994883fe chore: version v1.132.3 2025-04-25 19:44:05 +00:00
Alex a1f8150c30 fix: Authelia OAuth code verifier value contains invalid characters (#17886)
* fix(mobile): Authelia OAuth code verifier value contains invalid characters

* Refactor

* Refactoring with Jason

* Refactoring with Jason
2025-04-25 19:39:14 +00:00
Yaros d85ef19bfc fix(mobile): revert get location on app start (#17882) 2025-04-25 10:38:30 -05:00
Jason Rasmussen d0014bdf94 refactor: event manager (#17862)
* refactor: event manager

* refactor: event manager
2025-04-25 08:36:31 -04:00
Martin Mikita e822e3eca9 docs: update MapTiler name (#17863) 2025-04-25 08:57:44 +00:00
Alex 644defa4a1 chore: post release tasks (#17867) 2025-04-25 04:14:40 +00:00
Matthew Momjian 1fe3c7b9b3 fix(docs): priorities (Capitalization) (#17866)
priorities
2025-04-25 04:07:42 +00:00
321 changed files with 6728 additions and 2696 deletions
+1 -1
View File
@@ -96,7 +96,7 @@ jobs:
type=raw,value=latest,enable=${{ github.event_name == 'release' }} type=raw,value=latest,enable=${{ github.event_name == 'release' }}
- name: Build and push image - name: Build and push image
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with: with:
file: cli/Dockerfile file: cli/Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
+3 -3
View File
@@ -50,7 +50,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@45775bd8235c68ba998cffa5171334d58593da47 # v3 uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -63,7 +63,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@45775bd8235c68ba998cffa5171334d58593da47 # v3 uses: github/codeql-action/autobuild@28deaeda66b76a05916b6923827895f2b14ab387 # v3
# ️ Command-line programs to run using the OS shell. # ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -76,6 +76,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh # ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@45775bd8235c68ba998cffa5171334d58593da47 # v3 uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3
with: with:
category: '/language:${{matrix.language}}' category: '/language:${{matrix.language}}'
+4 -4
View File
@@ -205,7 +205,7 @@ jobs:
- name: Build and push image - name: Build and push image
id: build id: build
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with: with:
context: ${{ env.context }} context: ${{ env.context }}
file: ${{ env.file }} file: ${{ env.file }}
@@ -266,7 +266,7 @@ jobs:
- build_and_push_ml - build_and_push_ml
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with: with:
path: ${{ runner.temp }}/digests path: ${{ runner.temp }}/digests
pattern: ml-digests-${{ matrix.device }}-* pattern: ml-digests-${{ matrix.device }}-*
@@ -407,7 +407,7 @@ jobs:
- name: Build and push image - name: Build and push image
id: build id: build
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with: with:
context: ${{ env.context }} context: ${{ env.context }}
file: ${{ env.file }} file: ${{ env.file }}
@@ -454,7 +454,7 @@ jobs:
- build_and_push_server - build_and_push_server
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with: with:
path: ${{ runner.temp }}/digests path: ${{ runner.temp }}/digests
pattern: server-digests-* pattern: server-digests-*
+1
View File
@@ -72,4 +72,5 @@ jobs:
with: with:
name: docs-build-output name: docs-build-output
path: docs/build/ path: docs/build/
include-hidden-files: true
retention-days: 1 retention-days: 1
+1 -1
View File
@@ -95,7 +95,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Download APK - name: Download APK
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with: with:
name: release-apk-signed name: release-apk-signed
+3 -3
View File
@@ -105,12 +105,12 @@ jobs:
actions: read actions: read
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with: with:
persist-credentials: false persist-credentials: false
- name: Install the latest version of uv - name: Install the latest version of uv
uses: astral-sh/setup-uv@v5 uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5
- name: Run zizmor 🌈 - name: Run zizmor 🌈
run: uvx zizmor --format=sarif . > results.sarif run: uvx zizmor --format=sarif . > results.sarif
@@ -118,7 +118,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF file - name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v3 uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3
with: with:
sarif_file: results.sarif sarif_file: results.sarif
category: zizmor category: zizmor
+1 -1
View File
@@ -461,7 +461,7 @@ jobs:
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
# TODO: add caching when supported (https://github.com/actions/setup-python/pull/818) # TODO: add caching when supported (https://github.com/actions/setup-python/pull/818)
# with: # with:
# python-version: 3.11 # python-version: 3.11
+49 -31
View File
@@ -1,45 +1,63 @@
{ {
"editor.formatOnSave": true,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[css]": { "[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2, "editor.formatOnSave": true,
"editor.formatOnSave": true
},
"[svelte]": {
"editor.defaultFormatter": "svelte.svelte-vscode",
"editor.tabSize": 2 "editor.tabSize": 2
}, },
"svelte.enable-ts-plugin": true,
"eslint.validate": [
"javascript",
"svelte"
],
"typescript.preferences.importModuleSpecifier": "non-relative",
"[dart]": { "[dart]": {
"editor.defaultFormatter": "Dart-Code.dart-code",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.selectionHighlight": false, "editor.selectionHighlight": false,
"editor.suggest.snippetsPreventQuickSuggestions": false, "editor.suggest.snippetsPreventQuickSuggestions": false,
"editor.suggestSelection": "first", "editor.suggestSelection": "first",
"editor.tabCompletion": "onlySnippets", "editor.tabCompletion": "onlySnippets",
"editor.wordBasedSuggestions": "off", "editor.wordBasedSuggestions": "off"
"editor.defaultFormatter": "Dart-Code.dart-code"
}, },
"cSpell.words": [ "[javascript]": {
"immich" "editor.codeActionsOnSave": {
], "source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.tabSize": 2
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.tabSize": 2
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.tabSize": 2
},
"[svelte]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "svelte.svelte-vscode",
"editor.formatOnSave": true,
"editor.tabSize": 2
},
"[typescript]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.tabSize": 2
},
"cSpell.words": ["immich"],
"editor.formatOnSave": true,
"eslint.validate": ["javascript", "svelte"],
"explorer.fileNesting.enabled": true, "explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": { "explorer.fileNesting.patterns": {
"*.ts": "${capture}.spec.ts,${capture}.mock.ts", "*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart",
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart" "*.ts": "${capture}.spec.ts,${capture}.mock.ts"
} },
} "svelte.enable-ts-plugin": true,
"typescript.preferences.importModuleSpecifier": "non-relative"
}
+3
View File
@@ -17,6 +17,9 @@ e2e:
prod: prod:
docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
prod-down:
docker compose -f ./docker/docker-compose.prod.yml down --remove-orphans
prod-scale: prod-scale:
docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:22.14.0-alpine3.20@sha256:40be979442621049f40b1d51a26b55e281246b5de4e5f51a18da7beb6e17e3f9 AS core FROM node:22.15.0-alpine3.20@sha256:686b8892b69879ef5bfd6047589666933508f9a5451c67320df3070ba0e9807b AS core
WORKDIR /usr/src/open-api/typescript-sdk WORKDIR /usr/src/open-api/typescript-sdk
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
+124 -136
View File
@@ -1,12 +1,12 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.64", "version": "2.2.65",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.64", "version": "2.2.65",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
@@ -54,7 +54,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.132.2", "version": "1.132.3",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
@@ -647,9 +647,9 @@
} }
}, },
"node_modules/@eslint/core": { "node_modules/@eslint/core": {
"version": "0.12.0", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
"integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -697,9 +697,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.24.0", "version": "9.25.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz",
"integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", "integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -730,19 +730,6 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1380,17 +1367,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.30.1", "version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz",
"integrity": "sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==", "integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.30.1", "@typescript-eslint/scope-manager": "8.31.0",
"@typescript-eslint/type-utils": "8.30.1", "@typescript-eslint/type-utils": "8.31.0",
"@typescript-eslint/utils": "8.30.1", "@typescript-eslint/utils": "8.31.0",
"@typescript-eslint/visitor-keys": "8.30.1", "@typescript-eslint/visitor-keys": "8.31.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@@ -1410,16 +1397,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.30.1", "version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.30.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz",
"integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==", "integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.30.1", "@typescript-eslint/scope-manager": "8.31.0",
"@typescript-eslint/types": "8.30.1", "@typescript-eslint/types": "8.31.0",
"@typescript-eslint/typescript-estree": "8.30.1", "@typescript-eslint/typescript-estree": "8.31.0",
"@typescript-eslint/visitor-keys": "8.30.1", "@typescript-eslint/visitor-keys": "8.31.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -1435,14 +1422,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.30.1", "version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.30.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz",
"integrity": "sha512-+C0B6ChFXZkuaNDl73FJxRYT0G7ufVPOSQkqkpM/U198wUwUFOtgo1k/QzFh1KjpBitaK7R1tgjVz6o9HmsRPg==", "integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.30.1", "@typescript-eslint/types": "8.31.0",
"@typescript-eslint/visitor-keys": "8.30.1" "@typescript-eslint/visitor-keys": "8.31.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1453,14 +1440,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.30.1", "version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.30.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz",
"integrity": "sha512-64uBF76bfQiJyHgZISC7vcNz3adqQKIccVoKubyQcOnNcdJBvYOILV1v22Qhsw3tw3VQu5ll8ND6hycgAR5fEA==", "integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.30.1", "@typescript-eslint/typescript-estree": "8.31.0",
"@typescript-eslint/utils": "8.30.1", "@typescript-eslint/utils": "8.31.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^2.0.1"
}, },
@@ -1477,9 +1464,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.30.1", "version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.30.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz",
"integrity": "sha512-81KawPfkuulyWo5QdyG/LOKbspyyiW+p4vpn4bYO7DM/hZImlVnFwrpCTnmNMOt8CvLRr5ojI9nU1Ekpw4RcEw==", "integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1491,14 +1478,14 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.30.1", "version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.30.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz",
"integrity": "sha512-kQQnxymiUy9tTb1F2uep9W6aBiYODgq5EMSk6Nxh4Z+BDUoYUSa029ISs5zTzKBFnexQEh71KqwjKnRz58lusQ==", "integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.30.1", "@typescript-eslint/types": "8.31.0",
"@typescript-eslint/visitor-keys": "8.30.1", "@typescript-eslint/visitor-keys": "8.31.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@@ -1544,16 +1531,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.30.1", "version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.30.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz",
"integrity": "sha512-T/8q4R9En2tcEsWPQgB5BQ0XJVOtfARcUvOa8yJP3fh9M/mXraLxZrkCfGb6ChrO/V3W+Xbd04RacUEqk1CFEQ==", "integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.30.1", "@typescript-eslint/scope-manager": "8.31.0",
"@typescript-eslint/types": "8.30.1", "@typescript-eslint/types": "8.31.0",
"@typescript-eslint/typescript-estree": "8.30.1" "@typescript-eslint/typescript-estree": "8.31.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1568,13 +1555,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.30.1", "version": "8.31.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.30.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz",
"integrity": "sha512-aEhgas7aJ6vZnNFC7K4/vMGDGyOiqWcYZPpIWrTKuTAlsvDNKy2GFDqh9smL+iq069ZvR0YzEeq0B8NJlLzjFA==", "integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.30.1", "@typescript-eslint/types": "8.31.0",
"eslint-visitor-keys": "^4.2.0" "eslint-visitor-keys": "^4.2.0"
}, },
"engines": { "engines": {
@@ -1586,9 +1573,9 @@
} }
}, },
"node_modules/@vitest/coverage-v8": { "node_modules/@vitest/coverage-v8": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.1.2.tgz",
"integrity": "sha512-MgV6D2dhpD6Hp/uroUoAIvFqA8AuvXEFBC2eepG3WFc1pxTfdk1LEqqkWoWhjz+rytoqrnUUCdf6Lzco3iHkLQ==", "integrity": "sha512-XDdaDOeaTMAMYW7N63AqoK32sYUWbXnTkC6tEbVcu3RlU1bB9of32T+PGf8KZvxqLNqeXhafDFqCkwpf2+dyaQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1601,7 +1588,7 @@
"istanbul-reports": "^3.1.7", "istanbul-reports": "^3.1.7",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"magicast": "^0.3.5", "magicast": "^0.3.5",
"std-env": "^3.8.1", "std-env": "^3.9.0",
"test-exclude": "^7.0.1", "test-exclude": "^7.0.1",
"tinyrainbow": "^2.0.0" "tinyrainbow": "^2.0.0"
}, },
@@ -1609,8 +1596,8 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
}, },
"peerDependencies": { "peerDependencies": {
"@vitest/browser": "3.1.1", "@vitest/browser": "3.1.2",
"vitest": "3.1.1" "vitest": "3.1.2"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@vitest/browser": { "@vitest/browser": {
@@ -1619,14 +1606,14 @@
} }
}, },
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.2.tgz",
"integrity": "sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==", "integrity": "sha512-O8hJgr+zREopCAqWl3uCVaOdqJwZ9qaDwUP7vy3Xigad0phZe9APxKhPcDNqYYi0rX5oMvwJMSCAXY2afqeTSA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "3.1.1", "@vitest/spy": "3.1.2",
"@vitest/utils": "3.1.1", "@vitest/utils": "3.1.2",
"chai": "^5.2.0", "chai": "^5.2.0",
"tinyrainbow": "^2.0.0" "tinyrainbow": "^2.0.0"
}, },
@@ -1635,13 +1622,13 @@
} }
}, },
"node_modules/@vitest/mocker": { "node_modules/@vitest/mocker": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.2.tgz",
"integrity": "sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==", "integrity": "sha512-kOtd6K2lc7SQ0mBqYv/wdGedlqPdM/B38paPY+OwJ1XiNi44w3Fpog82UfOibmHaV9Wod18A09I9SCKLyDMqgw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "3.1.1", "@vitest/spy": "3.1.2",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"magic-string": "^0.30.17" "magic-string": "^0.30.17"
}, },
@@ -1662,9 +1649,9 @@
} }
}, },
"node_modules/@vitest/pretty-format": { "node_modules/@vitest/pretty-format": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.2.tgz",
"integrity": "sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==", "integrity": "sha512-R0xAiHuWeDjTSB3kQ3OQpT8Rx3yhdOAIm/JM4axXxnG7Q/fS8XUwggv/A4xzbQA+drYRjzkMnpYnOGAc4oeq8w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1675,13 +1662,13 @@
} }
}, },
"node_modules/@vitest/runner": { "node_modules/@vitest/runner": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.2.tgz",
"integrity": "sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==", "integrity": "sha512-bhLib9l4xb4sUMPXnThbnhX2Yi8OutBMA8Yahxa7yavQsFDtwY/jrUZwpKp2XH9DhRFJIeytlyGpXCqZ65nR+g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/utils": "3.1.1", "@vitest/utils": "3.1.2",
"pathe": "^2.0.3" "pathe": "^2.0.3"
}, },
"funding": { "funding": {
@@ -1689,13 +1676,13 @@
} }
}, },
"node_modules/@vitest/snapshot": { "node_modules/@vitest/snapshot": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.2.tgz",
"integrity": "sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==", "integrity": "sha512-Q1qkpazSF/p4ApZg1vfZSQ5Yw6OCQxVMVrLjslbLFA1hMDrT2uxtqMaw8Tc/jy5DLka1sNs1Y7rBcftMiaSH/Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "3.1.1", "@vitest/pretty-format": "3.1.2",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"pathe": "^2.0.3" "pathe": "^2.0.3"
}, },
@@ -1704,9 +1691,9 @@
} }
}, },
"node_modules/@vitest/spy": { "node_modules/@vitest/spy": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.2.tgz",
"integrity": "sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==", "integrity": "sha512-OEc5fSXMws6sHVe4kOFyDSj/+4MSwst0ib4un0DlcYgQvRuYQ0+M2HyqGaauUMnjq87tmUaMNDxKQx7wNfVqPA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1717,13 +1704,13 @@
} }
}, },
"node_modules/@vitest/utils": { "node_modules/@vitest/utils": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.2.tgz",
"integrity": "sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==", "integrity": "sha512-5GGd0ytZ7BH3H6JTj9Kw7Prn1Nbg0wZVrIvou+UWxm54d+WoXXgAgjFJ8wn3LdagWLFSEfpPeyYrByZaGEZHLg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "3.1.1", "@vitest/pretty-format": "3.1.2",
"loupe": "^3.1.3", "loupe": "^3.1.3",
"tinyrainbow": "^2.0.0" "tinyrainbow": "^2.0.0"
}, },
@@ -2183,9 +2170,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/es-module-lexer": { "node_modules/es-module-lexer": {
"version": "1.6.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
"integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -2254,20 +2241,20 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.24.0", "version": "9.25.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz",
"integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==", "integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.20.0", "@eslint/config-array": "^0.20.0",
"@eslint/config-helpers": "^0.2.0", "@eslint/config-helpers": "^0.2.1",
"@eslint/core": "^0.12.0", "@eslint/core": "^0.13.0",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.24.0", "@eslint/js": "9.25.1",
"@eslint/plugin-kit": "^0.2.7", "@eslint/plugin-kit": "^0.2.8",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2", "@humanwhocodes/retry": "^0.4.2",
@@ -4197,15 +4184,15 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.30.1", "version": "8.31.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.30.1.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.0.tgz",
"integrity": "sha512-D7lC0kcehVH7Mb26MRQi64LMyRJsj3dToJxM1+JVTl53DQSV5/7oUGWQLcKl1C1KnoVHxMMU2FNQMffr7F3Row==", "integrity": "sha512-u+93F0sB0An8WEAPtwxVhFby573E8ckdjwUUQUj9QA4v8JAvgtoDdIyYR3XFwFHq2W1KJ1AurwJCO+w+Y1ixyQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.30.1", "@typescript-eslint/eslint-plugin": "8.31.0",
"@typescript-eslint/parser": "8.30.1", "@typescript-eslint/parser": "8.31.0",
"@typescript-eslint/utils": "8.30.1" "@typescript-eslint/utils": "8.31.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4292,18 +4279,18 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "6.3.2", "version": "6.3.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.2.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz",
"integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==", "integrity": "sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.4.3", "fdir": "^6.4.4",
"picomatch": "^4.0.2", "picomatch": "^4.0.2",
"postcss": "^8.5.3", "postcss": "^8.5.3",
"rollup": "^4.34.9", "rollup": "^4.34.9",
"tinyglobby": "^0.2.12" "tinyglobby": "^0.2.13"
}, },
"bin": { "bin": {
"vite": "bin/vite.js" "vite": "bin/vite.js"
@@ -4367,9 +4354,9 @@
} }
}, },
"node_modules/vite-node": { "node_modules/vite-node": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.1.tgz", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.2.tgz",
"integrity": "sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==", "integrity": "sha512-/8iMryv46J3aK13iUXsei5G/A3CUlW4665THCPS+K8xAaqrVWiGB4RfXMQXCLjpK9P2eK//BczrVkn5JLAk6DA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -4438,31 +4425,32 @@
} }
}, },
"node_modules/vitest": { "node_modules/vitest": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.1.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.2.tgz",
"integrity": "sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==", "integrity": "sha512-WaxpJe092ID1C0mr+LH9MmNrhfzi8I65EX/NRU/Ld016KqQNRgxSOlGNP1hHN+a/F8L15Mh8klwaF77zR3GeDQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/expect": "3.1.1", "@vitest/expect": "3.1.2",
"@vitest/mocker": "3.1.1", "@vitest/mocker": "3.1.2",
"@vitest/pretty-format": "^3.1.1", "@vitest/pretty-format": "^3.1.2",
"@vitest/runner": "3.1.1", "@vitest/runner": "3.1.2",
"@vitest/snapshot": "3.1.1", "@vitest/snapshot": "3.1.2",
"@vitest/spy": "3.1.1", "@vitest/spy": "3.1.2",
"@vitest/utils": "3.1.1", "@vitest/utils": "3.1.2",
"chai": "^5.2.0", "chai": "^5.2.0",
"debug": "^4.4.0", "debug": "^4.4.0",
"expect-type": "^1.2.0", "expect-type": "^1.2.1",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"pathe": "^2.0.3", "pathe": "^2.0.3",
"std-env": "^3.8.1", "std-env": "^3.9.0",
"tinybench": "^2.9.0", "tinybench": "^2.9.0",
"tinyexec": "^0.3.2", "tinyexec": "^0.3.2",
"tinyglobby": "^0.2.13",
"tinypool": "^1.0.2", "tinypool": "^1.0.2",
"tinyrainbow": "^2.0.0", "tinyrainbow": "^2.0.0",
"vite": "^5.0.0 || ^6.0.0", "vite": "^5.0.0 || ^6.0.0",
"vite-node": "3.1.1", "vite-node": "3.1.2",
"why-is-node-running": "^2.3.0" "why-is-node-running": "^2.3.0"
}, },
"bin": { "bin": {
@@ -4478,8 +4466,8 @@
"@edge-runtime/vm": "*", "@edge-runtime/vm": "*",
"@types/debug": "^4.1.12", "@types/debug": "^4.1.12",
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"@vitest/browser": "3.1.1", "@vitest/browser": "3.1.2",
"@vitest/ui": "3.1.1", "@vitest/ui": "3.1.2",
"happy-dom": "*", "happy-dom": "*",
"jsdom": "*" "jsdom": "*"
}, },
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.64", "version": "2.2.65",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"type": "module", "type": "module",
"exports": "./dist/index.js", "exports": "./dist/index.js",
+1 -1
View File
@@ -116,7 +116,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:8-bookworm@sha256:42cba146593a5ea9a622002c1b7cba5da7be248650cbb64ecb9c6c33d29794b1 image: docker.io/valkey/valkey:8-bookworm@sha256:c855f98e09d558a0d7cc1a4e56473231206a4c54c0114ada9c485b47aeb92ec8
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
+2 -2
View File
@@ -56,7 +56,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:8-bookworm@sha256:42cba146593a5ea9a622002c1b7cba5da7be248650cbb64ecb9c6c33d29794b1 image: docker.io/valkey/valkey:8-bookworm@sha256:c855f98e09d558a0d7cc1a4e56473231206a4c54c0114ada9c485b47aeb92ec8
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
restart: always restart: always
@@ -102,7 +102,7 @@ services:
command: [ './run.sh', '-disable-reporting' ] command: [ './run.sh', '-disable-reporting' ]
ports: ports:
- 3000:3000 - 3000:3000
image: grafana/grafana:11.6.0-ubuntu@sha256:fd8fa48213c624e1a95122f1d93abbf1cf1cbe85fc73212c1e599dbd76c63ff8 image: grafana/grafana:11.6.1-ubuntu@sha256:6fc273288470ef499dd3c6b36aeade093170d4f608f864c5dd3a7fabeae77b50
volumes: volumes:
- grafana-data:/var/lib/grafana - grafana-data:/var/lib/grafana
+1 -1
View File
@@ -49,7 +49,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: docker.io/valkey/valkey:8-bookworm@sha256:42cba146593a5ea9a622002c1b7cba5da7be248650cbb64ecb9c6c33d29794b1 image: docker.io/valkey/valkey:8-bookworm@sha256:c855f98e09d558a0d7cc1a4e56473231206a4c54c0114ada9c485b47aeb92ec8
healthcheck: healthcheck:
test: redis-cli ping || exit 1 test: redis-cli ping || exit 1
restart: always restart: always
+5 -5
View File
@@ -14,14 +14,14 @@ online generators you can use.
2. Paste the link to your JSON style in either the **Light Style** or **Dark Style**. (You can add different styles which will help make the map style more appropriate depending on whether you set **Immich** to Light or Dark mode.) 2. Paste the link to your JSON style in either the **Light Style** or **Dark Style**. (You can add different styles which will help make the map style more appropriate depending on whether you set **Immich** to Light or Dark mode.)
3. Save your selections. Reload the map, and enjoy your custom map style! 3. Save your selections. Reload the map, and enjoy your custom map style!
## Use Maptiler to build a custom style ## Use MapTiler to build a custom style
Customizing the map style can be done easily using Maptiler, if you do not want to write an entire JSON document by hand. Customizing the map style can be done easily using MapTiler, if you do not want to write an entire JSON document by hand.
1. Create a free account at https://cloud.maptiler.com 1. Create a free account at https://cloud.maptiler.com
2. Once logged in, you can either create a brand new map by clicking on **New Map**, selecting a starter map, and then clicking **Customize**, OR by selecting a **Standard Map** and customizing it from there. 2. Once logged in, you can either create a brand new map by clicking on **New Map**, selecting a starter map, and then clicking **Customize**, OR by selecting a **Standard Map** and customizing it from there.
3. The **editor** interface is self-explanatory. You can change colors, remove visible layers, or add optional layers (e.g., administrative, topo, hydro, etc.) in the composer. 3. The **editor** interface is self-explanatory. You can change colors, remove visible layers, or add optional layers (e.g., administrative, topo, hydro, etc.) in the composer.
4. Once you have your map composed, click on **Save** at the top right. Give it a unique name to save it to your account. 4. Once you have your map composed, click on **Save** at the top right. Give it a unique name to save it to your account.
5. Next, **Publish** your style using the **Publish** button at the top right. This will deploy it to production, which means it is able to be exposed over the Internet. Maptiler will present an interactive side-by-side map with the original and your changes prior to publication.<br/>![Maptiler Publication Settings](img/immich_map_styles_publish.webp) 5. Next, **Publish** your style using the **Publish** button at the top right. This will deploy it to production, which means it is able to be exposed over the Internet. MapTiler will present an interactive side-by-side map with the original and your changes prior to publication.<br/>![MapTiler Publication Settings](img/immich_map_styles_publish.webp)
6. Maptiler will warn you that changing the map will change it across all apps using the map. Since no apps are using the map yet, this is okay. 6. MapTiler will warn you that changing the map will change it across all apps using the map. Since no apps are using the map yet, this is okay.
7. Clicking on the name of your new map at the top left will bring you to the item's **details** page. From here, copy the link to the JSON style under **Use vector style**. This link will automatically contain your personal API key to Maptiler. 7. Clicking on the name of your new map at the top left will bring you to the item's **details** page. From here, copy the link to the JSON style under **Use vector style**. This link will automatically contain your personal API key to MapTiler.
+1 -1
View File
@@ -1,7 +1,7 @@
# Database Queries # Database Queries
:::danger :::danger
Keep in mind that mucking around in the database might set the moon on fire. Avoid modifying the database directly when possible, and always have current backups. Keep in mind that mucking around in the database might set the Moon on fire. Avoid modifying the database directly when possible, and always have current backups.
::: :::
:::tip :::tip
+5
View File
@@ -0,0 +1,5 @@
Policy: https://github.com/immich-app/immich/blob/main/SECURITY.md
Contact: mailto:security@immich.app
Preferred-Languages: en
Expires: 2026-05-01T23:59:00.000Z
Canonical: https://immich.app/.well-known/security.txt
+4
View File
@@ -1,4 +1,8 @@
[ [
{
"label": "v1.132.3",
"url": "https://v1.132.3.archive.immich.app"
},
{ {
"label": "v1.132.2", "label": "v1.132.2",
"url": "https://v1.132.2.archive.immich.app" "url": "https://v1.132.2.archive.immich.app"
+1 -1
View File
@@ -34,7 +34,7 @@ services:
- 2285:2285 - 2285:2285
redis: redis:
image: redis:6.2-alpine@sha256:148bb5411c184abd288d9aaed139c98123eeb8824c5d3fce03cf721db58066d8 image: redis:6.2-alpine@sha256:3211c33a618c457e5d241922c975dbc4f446d0bdb2dc75694f5573ef8e2d01fa
database: database:
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52 image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52
+408 -344
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.132.2", "version": "1.132.3",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
+13 -13
View File
@@ -215,6 +215,19 @@ describe('/admin/users', () => {
const user = await getMyUser({ headers: asBearerAuth(token.accessToken) }); const user = await getMyUser({ headers: asBearerAuth(token.accessToken) });
expect(user).toMatchObject({ email: nonAdmin.userEmail }); expect(user).toMatchObject({ email: nonAdmin.userEmail });
}); });
it('should update the avatar color', async () => {
const { status, body } = await request(app)
.put(`/admin/users/${admin.userId}`)
.send({ avatarColor: 'orange' })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({ avatarColor: 'orange' });
const after = await getUserAdmin({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) });
expect(after).toMatchObject({ avatarColor: 'orange' });
});
}); });
describe('PUT /admin/users/:id/preferences', () => { describe('PUT /admin/users/:id/preferences', () => {
@@ -240,19 +253,6 @@ describe('/admin/users', () => {
expect(after).toMatchObject({ memories: { enabled: false } }); expect(after).toMatchObject({ memories: { enabled: false } });
}); });
it('should update the avatar color', async () => {
const { status, body } = await request(app)
.put(`/admin/users/${admin.userId}/preferences`)
.send({ avatar: { color: 'orange' } })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({ avatar: { color: 'orange' } });
const after = await getUserPreferencesAdmin({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) });
expect(after).toMatchObject({ avatar: { color: 'orange' } });
});
it('should update download archive size', async () => { it('should update download archive size', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.put(`/admin/users/${admin.userId}/preferences`) .put(`/admin/users/${admin.userId}/preferences`)
+13 -13
View File
@@ -139,6 +139,19 @@ describe('/users', () => {
profileChangedAt: expect.anything(), profileChangedAt: expect.anything(),
}); });
}); });
it('should update avatar color', async () => {
const { status, body } = await request(app)
.put(`/users/me`)
.send({ avatarColor: 'blue' })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({ avatarColor: 'blue' });
const after = await getMyUser({ headers: asBearerAuth(admin.accessToken) });
expect(after).toMatchObject({ avatarColor: 'blue' });
});
}); });
describe('PUT /users/me/preferences', () => { describe('PUT /users/me/preferences', () => {
@@ -158,19 +171,6 @@ describe('/users', () => {
expect(after).toMatchObject({ memories: { enabled: false } }); expect(after).toMatchObject({ memories: { enabled: false } });
}); });
it('should update avatar color', async () => {
const { status, body } = await request(app)
.put(`/users/me/preferences`)
.send({ avatar: { color: 'blue' } })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({ avatar: { color: 'blue' } });
const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
expect(after).toMatchObject({ avatar: { color: 'blue' } });
});
it('should require an integer for download archive size', async () => { it('should require an integer for download archive size', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.put(`/users/me/preferences`) .put(`/users/me/preferences`)
+3 -3
View File
@@ -25,7 +25,7 @@ test.describe('Registration', () => {
// login // login
await expect(page).toHaveTitle(/Login/); await expect(page).toHaveTitle(/Login/);
await page.goto('/auth/login'); await page.goto('/auth/login?autoLaunch=0');
await page.getByLabel('Email').fill('admin@immich.app'); await page.getByLabel('Email').fill('admin@immich.app');
await page.getByLabel('Password').fill('password'); await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Login' }).click(); await page.getByRole('button', { name: 'Login' }).click();
@@ -59,7 +59,7 @@ test.describe('Registration', () => {
await context.clearCookies(); await context.clearCookies();
// login // login
await page.goto('/auth/login'); await page.goto('/auth/login?autoLaunch=0');
await page.getByLabel('Email').fill('user@immich.cloud'); await page.getByLabel('Email').fill('user@immich.cloud');
await page.getByLabel('Password').fill('password'); await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Login' }).click(); await page.getByRole('button', { name: 'Login' }).click();
@@ -72,7 +72,7 @@ test.describe('Registration', () => {
await page.getByRole('button', { name: 'Change password' }).click(); await page.getByRole('button', { name: 'Change password' }).click();
// login with new password // login with new password
await expect(page).toHaveURL('/auth/login'); await expect(page).toHaveURL('/auth/login?autoLaunch=0');
await page.getByLabel('Email').fill('user@immich.cloud'); await page.getByLabel('Email').fill('user@immich.cloud');
await page.getByLabel('Password').fill('new-password'); await page.getByLabel('Password').fill('new-password');
await page.getByRole('button', { name: 'Login' }).click(); await page.getByRole('button', { name: 'Login' }).click();
@@ -21,23 +21,9 @@ test.describe('Photo Viewer', () => {
test.beforeEach(async ({ context, page }) => { test.beforeEach(async ({ context, page }) => {
// before each test, login as user // before each test, login as user
await utils.setAuthCookies(context, admin.accessToken); await utils.setAuthCookies(context, admin.accessToken);
await page.goto('/photos');
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
}); });
test('initially shows a loading spinner', async ({ page }) => {
await page.route(`/api/assets/${asset.id}/thumbnail**`, async (route) => {
// slow down the request for thumbnail, so spinner has chance to show up
await new Promise((f) => setTimeout(f, 2000));
await route.continue();
});
await page.goto(`/photos/${asset.id}`);
await page.waitForLoadState('load');
// this is the spinner
await page.waitForSelector('svg[role=status]');
await expect(page.getByTestId('loading-spinner')).toBeVisible();
});
test('loads original photo when zoomed', async ({ page }) => { test('loads original photo when zoomed', async ({ page }) => {
await page.goto(`/photos/${asset.id}`); await page.goto(`/photos/${asset.id}`);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail'); await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
+11 -7
View File
@@ -192,26 +192,22 @@
"oauth_auto_register": "Auto register", "oauth_auto_register": "Auto register",
"oauth_auto_register_description": "Automatically register new users after signing in with OAuth", "oauth_auto_register_description": "Automatically register new users after signing in with OAuth",
"oauth_button_text": "Button text", "oauth_button_text": "Button text",
"oauth_client_id": "Client ID", "oauth_client_secret_description": "Required if PKCE (Proof Key for Code Exchange) is not supported by the OAuth provider",
"oauth_client_secret": "Client Secret",
"oauth_enable_description": "Login with OAuth", "oauth_enable_description": "Login with OAuth",
"oauth_issuer_url": "Issuer URL",
"oauth_mobile_redirect_uri": "Mobile redirect URI", "oauth_mobile_redirect_uri": "Mobile redirect URI",
"oauth_mobile_redirect_uri_override": "Mobile redirect URI override", "oauth_mobile_redirect_uri_override": "Mobile redirect URI override",
"oauth_mobile_redirect_uri_override_description": "Enable when OAuth provider does not allow a mobile URI, like '{callback}'", "oauth_mobile_redirect_uri_override_description": "Enable when OAuth provider does not allow a mobile URI, like '{callback}'",
"oauth_profile_signing_algorithm": "Profile signing algorithm",
"oauth_profile_signing_algorithm_description": "Algorithm used to sign the user profile.",
"oauth_scope": "Scope",
"oauth_settings": "OAuth", "oauth_settings": "OAuth",
"oauth_settings_description": "Manage OAuth login settings", "oauth_settings_description": "Manage OAuth login settings",
"oauth_settings_more_details": "For more details about this feature, refer to the <link>docs</link>.", "oauth_settings_more_details": "For more details about this feature, refer to the <link>docs</link>.",
"oauth_signing_algorithm": "Signing algorithm",
"oauth_storage_label_claim": "Storage label claim", "oauth_storage_label_claim": "Storage label claim",
"oauth_storage_label_claim_description": "Automatically set the user's storage label to the value of this claim.", "oauth_storage_label_claim_description": "Automatically set the user's storage label to the value of this claim.",
"oauth_storage_quota_claim": "Storage quota claim", "oauth_storage_quota_claim": "Storage quota claim",
"oauth_storage_quota_claim_description": "Automatically set the user's storage quota to the value of this claim.", "oauth_storage_quota_claim_description": "Automatically set the user's storage quota to the value of this claim.",
"oauth_storage_quota_default": "Default storage quota (GiB)", "oauth_storage_quota_default": "Default storage quota (GiB)",
"oauth_storage_quota_default_description": "Quota in GiB to be used when no claim is provided (Enter 0 for unlimited quota).", "oauth_storage_quota_default_description": "Quota in GiB to be used when no claim is provided (Enter 0 for unlimited quota).",
"oauth_timeout": "Request Timeout",
"oauth_timeout_description": "Timeout for requests in milliseconds",
"offline_paths": "Offline Paths", "offline_paths": "Offline Paths",
"offline_paths_description": "These results may be due to manual deletion of files that are not part of an external library.", "offline_paths_description": "These results may be due to manual deletion of files that are not part of an external library.",
"password_enable_description": "Login with email and password", "password_enable_description": "Login with email and password",
@@ -853,10 +849,12 @@
"failed_to_keep_this_delete_others": "Failed to keep this asset and delete the other assets", "failed_to_keep_this_delete_others": "Failed to keep this asset and delete the other assets",
"failed_to_load_asset": "Failed to load asset", "failed_to_load_asset": "Failed to load asset",
"failed_to_load_assets": "Failed to load assets", "failed_to_load_assets": "Failed to load assets",
"failed_to_load_notifications": "Failed to load notifications",
"failed_to_load_people": "Failed to load people", "failed_to_load_people": "Failed to load people",
"failed_to_remove_product_key": "Failed to remove product key", "failed_to_remove_product_key": "Failed to remove product key",
"failed_to_stack_assets": "Failed to stack assets", "failed_to_stack_assets": "Failed to stack assets",
"failed_to_unstack_assets": "Failed to un-stack assets", "failed_to_unstack_assets": "Failed to un-stack assets",
"failed_to_update_notification_status": "Failed to update notification status",
"import_path_already_exists": "This import path already exists.", "import_path_already_exists": "This import path already exists.",
"incorrect_email_or_password": "Incorrect email or password", "incorrect_email_or_password": "Incorrect email or password",
"paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation", "paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation",
@@ -1199,6 +1197,9 @@
"map_settings_only_show_favorites": "Show Favorite Only", "map_settings_only_show_favorites": "Show Favorite Only",
"map_settings_theme_settings": "Map Theme", "map_settings_theme_settings": "Map Theme",
"map_zoom_to_see_photos": "Zoom out to see photos", "map_zoom_to_see_photos": "Zoom out to see photos",
"mark_as_read": "Mark as read",
"mark_all_as_read": "Mark all as read",
"marked_all_as_read": "Marked all as read",
"matches": "Matches", "matches": "Matches",
"media_type": "Media type", "media_type": "Media type",
"memories": "Memories", "memories": "Memories",
@@ -1225,6 +1226,8 @@
"month": "Month", "month": "Month",
"monthly_title_text_date_format": "MMMM y", "monthly_title_text_date_format": "MMMM y",
"more": "More", "more": "More",
"moved_to_archive": "Moved {count, plural, one {# asset} other {# assets}} to archive",
"moved_to_library": "Moved {count, plural, one {# asset} other {# assets}} to library",
"moved_to_trash": "Moved to trash", "moved_to_trash": "Moved to trash",
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping", "multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping",
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping", "multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
@@ -1260,6 +1263,7 @@
"no_places": "No places", "no_places": "No places",
"no_results": "No results", "no_results": "No results",
"no_results_description": "Try a synonym or more general keyword", "no_results_description": "Try a synonym or more general keyword",
"no_notifications": "No notifications",
"no_shared_albums_message": "Create an album to share photos and videos with people in your network", "no_shared_albums_message": "Create an album to share photos and videos with people in your network",
"not_in_any_album": "Not in any album", "not_in_any_album": "Not in any album",
"not_selected": "Not selected", "not_selected": "Not selected",
+4 -4
View File
@@ -1,6 +1,6 @@
ARG DEVICE=cpu ARG DEVICE=cpu
FROM python:3.11-bookworm@sha256:a3e280261e448b95d49423532ccd6e5329c39d171c10df1457891ff7c5e2301b AS builder-cpu FROM python:3.11-bookworm@sha256:ab60e444e04215a62671149f24c59cc2893b49cb5dad26f9d139077a86be760e AS builder-cpu
FROM builder-cpu AS builder-openvino FROM builder-cpu AS builder-openvino
@@ -54,7 +54,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
RUN apt-get update && apt-get install -y --no-install-recommends g++ RUN apt-get update && apt-get install -y --no-install-recommends g++
COPY --from=ghcr.io/astral-sh/uv:latest@sha256:db305ce8edc1c2df4988b9d23471465d90d599cc55571e6501421c173a33bb0b /uv /uvx /bin/ COPY --from=ghcr.io/astral-sh/uv:latest@sha256:4a6c9444b126bd325fba904bff796bf91fb777bf6148d60109c4cb1de2ffc497 /uv /uvx /bin/
RUN --mount=type=cache,target=/root/.cache/uv \ RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \ --mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
@@ -63,11 +63,11 @@ RUN if [ "$DEVICE" = "rocm" ]; then \
uv pip install /opt/onnxruntime_rocm-*.whl; \ uv pip install /opt/onnxruntime_rocm-*.whl; \
fi fi
FROM python:3.11-slim-bookworm@sha256:82c07f2f6e35255b92eb16f38dbd22679d5e8fb523064138d7c6468e7bf0c15b AS prod-cpu FROM python:3.11-slim-bookworm@sha256:97ef3198ec8c78690587167bb6a4905d00ffe053900687bdae93ad667e507cbb AS prod-cpu
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2
FROM python:3.11-slim-bookworm@sha256:82c07f2f6e35255b92eb16f38dbd22679d5e8fb523064138d7c6468e7bf0c15b AS prod-openvino FROM python:3.11-slim-bookworm@sha256:97ef3198ec8c78690587167bb6a4905d00ffe053900687bdae93ad667e507cbb AS prod-openvino
RUN apt-get update && \ RUN apt-get update && \
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \ apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
+116 -26
View File
@@ -69,6 +69,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/cd/d6d9bb1dadf73e7af02d18225cbd2c93f8552e13130484f1c8dcfece292b/anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee", size = 85481, upload_time = "2023-12-16T17:06:55.989Z" }, { url = "https://files.pythonhosted.org/packages/bf/cd/d6d9bb1dadf73e7af02d18225cbd2c93f8552e13130484f1c8dcfece292b/anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee", size = 85481, upload_time = "2023-12-16T17:06:55.989Z" },
] ]
[[package]]
name = "bidict"
version = "0.23.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9a/6e/026678aa5a830e07cd9498a05d3e7e650a4f56a42f267a53d22bcda1bdc9/bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71", size = 29093, upload_time = "2024-02-18T19:09:05.748Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload_time = "2024-02-18T19:09:04.156Z" },
]
[[package]] [[package]]
name = "black" name = "black"
version = "25.1.0" version = "25.1.0"
@@ -1200,15 +1209,16 @@ wheels = [
[[package]] [[package]]
name = "locust" name = "locust"
version = "2.35.0" version = "2.36.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "configargparse" }, { name = "configargparse" },
{ name = "flask" }, { name = "flask" },
{ name = "flask-cors" }, { name = "flask-cors" },
{ name = "flask-login" }, { name = "flask-login" },
{ name = "gevent", marker = "python_full_version != '3.13.*'" }, { name = "gevent" },
{ name = "geventhttpclient" }, { name = "geventhttpclient" },
{ name = "locust-cloud" },
{ name = "msgpack" }, { name = "msgpack" },
{ name = "psutil" }, { name = "psutil" },
{ name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "pywin32", marker = "sys_platform == 'win32'" },
@@ -1219,9 +1229,25 @@ dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.11'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" },
{ name = "werkzeug" }, { name = "werkzeug" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/79/21/d5aeeee74173d73d7d8d392e307ec24d8281fca69a2bf1f19199bd84c498/locust-2.35.0.tar.gz", hash = "sha256:97f83e591646ca3227644cfb6d4fa590e9a3e3d791ab18b216ca98be235b9b24", size = 2240690, upload_time = "2025-04-16T12:10:25.037Z" } sdist = { url = "https://files.pythonhosted.org/packages/6d/90/55d4fbc8911e5e6ec4072caaca9e8b7b2b11279435c0d1330c9966b0c898/locust-2.36.2.tar.gz", hash = "sha256:604aff7535f5a83b7f666d32373b2dc74ad260c7c3d1dc274f4c82844be72eb6", size = 2251110, upload_time = "2025-04-25T14:03:35.919Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/3d/15/9e92757c08af3f0c0168ab6480315ed6374396d635f70055df5c42cfa672/locust-2.35.0-py3-none-any.whl", hash = "sha256:fb9e0ec25c5db3ed6a3c6d48e7236d7c2c370b0ddae102e9badcb2d3d101abde", size = 2258054, upload_time = "2025-04-16T12:10:22.608Z" }, { url = "https://files.pythonhosted.org/packages/ab/f5/99dab104be69122eee3513dcdc6e0b32d59ca1f4cfd8715470c5f3aa7643/locust-2.36.2-py3-none-any.whl", hash = "sha256:74239f493f44035b25a87a0665deadf41d213b3dcd45774398e511dec15e26eb", size = 2267937, upload_time = "2025-04-25T14:03:33.671Z" },
]
[[package]]
name = "locust-cloud"
version = "1.20.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "configargparse" },
{ name = "gevent" },
{ name = "platformdirs" },
{ name = "python-socketio", extra = ["client"] },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/94/47/1ec2478f3d4e526fb8d667b01a75b22093b2e66aea665b5369dd656ceec9/locust_cloud-1.20.7.tar.gz", hash = "sha256:24c16b767adffab51b97f489bcf142e16e2439354fb4296ecbb3e87ad20e220a", size = 448622, upload_time = "2025-04-28T11:01:49.381Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8c/07/62b5b174c77d4281235405f1ffd439f12a877e434e007a24a5299c461e39/locust_cloud-1.20.7-py3-none-any.whl", hash = "sha256:f38214e77993d0ee87114dafa857e1689789ed4bfe4ae57c2b9dc754674f08bc", size = 406619, upload_time = "2025-04-28T11:01:43.135Z" },
] ]
[[package]] [[package]]
@@ -1722,11 +1748,11 @@ wheels = [
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "4.1.0" version = "4.3.7"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/62/d1/7feaaacb1a3faeba96c06e6c5091f90695cc0f94b7e8e1a3a3fe2b33ff9a/platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420", size = 19760, upload_time = "2023-12-04T15:32:15.925Z" } sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291, upload_time = "2025-03-19T20:36:10.989Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/be/53/42fe5eab4a09d251a76d0043e018172db324a23fcdac70f77a551c11f618/platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380", size = 17420, upload_time = "2023-12-04T15:32:13.795Z" }, { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload_time = "2025-03-19T20:36:09.038Z" },
] ]
[[package]] [[package]]
@@ -2005,6 +2031,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/44/2f/62ea1c8b593f4e093cc1a7768f0d46112107e790c3e478532329e434f00b/python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a", size = 19482, upload_time = "2023-02-24T06:46:36.009Z" }, { url = "https://files.pythonhosted.org/packages/44/2f/62ea1c8b593f4e093cc1a7768f0d46112107e790c3e478532329e434f00b/python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a", size = 19482, upload_time = "2023-02-24T06:46:36.009Z" },
] ]
[[package]]
name = "python-engineio"
version = "4.12.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "simple-websocket" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f7/e1/eee1129544b7f78fa2afa9fa0fce153cdcb21015b9b331d1b8adf90f45cb/python_engineio-4.12.0.tar.gz", hash = "sha256:f42a36a868d7063aa10ddccf6bd6117a169b6bd00d7ca53999772093b62014f9", size = 91503, upload_time = "2025-04-12T15:30:23.905Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2b/f7/0aeea75424c47633c1d98557a2323be23bed31fa950f00161b34a5150d06/python_engineio-4.12.0-py3-none-any.whl", hash = "sha256:a0c47c129c39777e8ebc6d18011efd50db2144e4e8f08983acae8a3614626535", size = 59319, upload_time = "2025-04-12T15:30:22.325Z" },
]
[[package]] [[package]]
name = "python-multipart" name = "python-multipart"
version = "0.0.20" version = "0.0.20"
@@ -2014,6 +2052,25 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload_time = "2024-12-16T19:45:44.423Z" }, { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload_time = "2024-12-16T19:45:44.423Z" },
] ]
[[package]]
name = "python-socketio"
version = "5.13.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "bidict" },
{ name = "python-engineio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/21/1a/396d50ccf06ee539fa758ce5623b59a9cb27637fc4b2dc07ed08bf495e77/python_socketio-5.13.0.tar.gz", hash = "sha256:ac4e19a0302ae812e23b712ec8b6427ca0521f7c582d6abb096e36e24a263029", size = 121125, upload_time = "2025-04-12T15:46:59.933Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl", hash = "sha256:51f68d6499f2df8524668c24bcec13ba1414117cfb3a90115c559b601ab10caf", size = 77800, upload_time = "2025-04-12T15:46:58.412Z" },
]
[package.optional-dependencies]
client = [
{ name = "requests" },
{ name = "websocket-client" },
]
[[package]] [[package]]
name = "pywin32" name = "pywin32"
version = "306" version = "306"
@@ -2226,27 +2283,27 @@ wheels = [
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.11.6" version = "0.11.7"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d9/11/bcef6784c7e5d200b8a1f5c2ddf53e5da0efec37e6e5a44d163fb97e04ba/ruff-0.11.6.tar.gz", hash = "sha256:bec8bcc3ac228a45ccc811e45f7eb61b950dbf4cf31a67fa89352574b01c7d79", size = 4010053, upload_time = "2025-04-17T13:35:53.905Z" } sdist = { url = "https://files.pythonhosted.org/packages/5b/89/6f9c9674818ac2e9cc2f2b35b704b7768656e6b7c139064fc7ba8fbc99f1/ruff-0.11.7.tar.gz", hash = "sha256:655089ad3224070736dc32844fde783454f8558e71f501cb207485fe4eee23d4", size = 4054861, upload_time = "2025-04-24T18:49:37.007Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/1f/8848b625100ebcc8740c8bac5b5dd8ba97dd4ee210970e98832092c1635b/ruff-0.11.6-py3-none-linux_armv6l.whl", hash = "sha256:d84dcbe74cf9356d1bdb4a78cf74fd47c740bf7bdeb7529068f69b08272239a1", size = 10248105, upload_time = "2025-04-17T13:35:14.758Z" }, { url = "https://files.pythonhosted.org/packages/b4/ec/21927cb906c5614b786d1621dba405e3d44f6e473872e6df5d1a6bca0455/ruff-0.11.7-py3-none-linux_armv6l.whl", hash = "sha256:d29e909d9a8d02f928d72ab7837b5cbc450a5bdf578ab9ebee3263d0a525091c", size = 10245403, upload_time = "2025-04-24T18:48:40.459Z" },
{ url = "https://files.pythonhosted.org/packages/e0/47/c44036e70c6cc11e6ee24399c2a1e1f1e99be5152bd7dff0190e4b325b76/ruff-0.11.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9bc583628e1096148011a5d51ff3c836f51899e61112e03e5f2b1573a9b726de", size = 11001494, upload_time = "2025-04-17T13:35:18.444Z" }, { url = "https://files.pythonhosted.org/packages/e2/af/fec85b6c2c725bcb062a354dd7cbc1eed53c33ff3aa665165871c9c16ddf/ruff-0.11.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dd1fb86b168ae349fb01dd497d83537b2c5541fe0626e70c786427dd8363aaee", size = 11007166, upload_time = "2025-04-24T18:48:44.742Z" },
{ url = "https://files.pythonhosted.org/packages/ed/5b/170444061650202d84d316e8f112de02d092bff71fafe060d3542f5bc5df/ruff-0.11.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f2959049faeb5ba5e3b378709e9d1bf0cab06528b306b9dd6ebd2a312127964a", size = 10352151, upload_time = "2025-04-17T13:35:20.563Z" }, { url = "https://files.pythonhosted.org/packages/31/9a/2d0d260a58e81f388800343a45898fd8df73c608b8261c370058b675319a/ruff-0.11.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d3d7d2e140a6fbbc09033bce65bd7ea29d6a0adeb90b8430262fbacd58c38ada", size = 10378076, upload_time = "2025-04-24T18:48:47.918Z" },
{ url = "https://files.pythonhosted.org/packages/ff/91/f02839fb3787c678e112c8865f2c3e87cfe1744dcc96ff9fc56cfb97dda2/ruff-0.11.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63c5d4e30d9d0de7fedbfb3e9e20d134b73a30c1e74b596f40f0629d5c28a193", size = 10541951, upload_time = "2025-04-17T13:35:22.522Z" }, { url = "https://files.pythonhosted.org/packages/c2/c4/9b09b45051404d2e7dd6d9dbcbabaa5ab0093f9febcae664876a77b9ad53/ruff-0.11.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4809df77de390a1c2077d9b7945d82f44b95d19ceccf0c287c56e4dc9b91ca64", size = 10557138, upload_time = "2025-04-24T18:48:51.707Z" },
{ url = "https://files.pythonhosted.org/packages/9e/f3/c09933306096ff7a08abede3cc2534d6fcf5529ccd26504c16bf363989b5/ruff-0.11.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4b9a4e1439f7d0a091c6763a100cef8fbdc10d68593df6f3cfa5abdd9246e", size = 10079195, upload_time = "2025-04-17T13:35:24.485Z" }, { url = "https://files.pythonhosted.org/packages/5e/5e/f62a1b6669870a591ed7db771c332fabb30f83c967f376b05e7c91bccd14/ruff-0.11.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3a0c2e169e6b545f8e2dba185eabbd9db4f08880032e75aa0e285a6d3f48201", size = 10095726, upload_time = "2025-04-24T18:48:54.243Z" },
{ url = "https://files.pythonhosted.org/packages/e0/0d/a87f8933fccbc0d8c653cfbf44bedda69c9582ba09210a309c066794e2ee/ruff-0.11.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5edf270223dd622218256569636dc3e708c2cb989242262fe378609eccf1308", size = 11698918, upload_time = "2025-04-17T13:35:26.504Z" }, { url = "https://files.pythonhosted.org/packages/45/59/a7aa8e716f4cbe07c3500a391e58c52caf665bb242bf8be42c62adef649c/ruff-0.11.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49b888200a320dd96a68e86736cf531d6afba03e4f6cf098401406a257fcf3d6", size = 11672265, upload_time = "2025-04-24T18:48:57.639Z" },
{ url = "https://files.pythonhosted.org/packages/52/7d/8eac0bd083ea8a0b55b7e4628428203441ca68cd55e0b67c135a4bc6e309/ruff-0.11.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f55844e818206a9dd31ff27f91385afb538067e2dc0beb05f82c293ab84f7d55", size = 12319426, upload_time = "2025-04-17T13:35:28.452Z" }, { url = "https://files.pythonhosted.org/packages/dd/e3/101a8b707481f37aca5f0fcc3e42932fa38b51add87bfbd8e41ab14adb24/ruff-0.11.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2b19cdb9cf7dae00d5ee2e7c013540cdc3b31c4f281f1dacb5a799d610e90db4", size = 12331418, upload_time = "2025-04-24T18:49:00.697Z" },
{ url = "https://files.pythonhosted.org/packages/c2/dc/d0c17d875662d0c86fadcf4ca014ab2001f867621b793d5d7eef01b9dcce/ruff-0.11.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d8f782286c5ff562e4e00344f954b9320026d8e3fae2ba9e6948443fafd9ffc", size = 11791012, upload_time = "2025-04-17T13:35:30.455Z" }, { url = "https://files.pythonhosted.org/packages/dd/71/037f76cbe712f5cbc7b852e4916cd3cf32301a30351818d32ab71580d1c0/ruff-0.11.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64e0ee994c9e326b43539d133a36a455dbaab477bc84fe7bfbd528abe2f05c1e", size = 11794506, upload_time = "2025-04-24T18:49:03.545Z" },
{ url = "https://files.pythonhosted.org/packages/f9/f3/81a1aea17f1065449a72509fc7ccc3659cf93148b136ff2a8291c4bc3ef1/ruff-0.11.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01c63ba219514271cee955cd0adc26a4083df1956d57847978383b0e50ffd7d2", size = 13949947, upload_time = "2025-04-17T13:35:33.133Z" }, { url = "https://files.pythonhosted.org/packages/ca/de/e450b6bab1fc60ef263ef8fcda077fb4977601184877dce1c59109356084/ruff-0.11.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bad82052311479a5865f52c76ecee5d468a58ba44fb23ee15079f17dd4c8fd63", size = 13939084, upload_time = "2025-04-24T18:49:07.159Z" },
{ url = "https://files.pythonhosted.org/packages/61/9f/a3e34de425a668284e7024ee6fd41f452f6fa9d817f1f3495b46e5e3a407/ruff-0.11.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15adac20ef2ca296dd3d8e2bedc6202ea6de81c091a74661c3666e5c4c223ff6", size = 11471753, upload_time = "2025-04-17T13:35:35.416Z" }, { url = "https://files.pythonhosted.org/packages/0e/2c/1e364cc92970075d7d04c69c928430b23e43a433f044474f57e425cbed37/ruff-0.11.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7940665e74e7b65d427b82bffc1e46710ec7f30d58b4b2d5016e3f0321436502", size = 11450441, upload_time = "2025-04-24T18:49:11.41Z" },
{ url = "https://files.pythonhosted.org/packages/df/c5/4a57a86d12542c0f6e2744f262257b2aa5a3783098ec14e40f3e4b3a354a/ruff-0.11.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4dd6b09e98144ad7aec026f5588e493c65057d1b387dd937d7787baa531d9bc2", size = 10417121, upload_time = "2025-04-17T13:35:38.224Z" }, { url = "https://files.pythonhosted.org/packages/9d/7d/1b048eb460517ff9accd78bca0fa6ae61df2b276010538e586f834f5e402/ruff-0.11.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:169027e31c52c0e36c44ae9a9c7db35e505fee0b39f8d9fca7274a6305295a92", size = 10441060, upload_time = "2025-04-24T18:49:14.184Z" },
{ url = "https://files.pythonhosted.org/packages/58/3f/a3b4346dff07ef5b862e2ba06d98fcbf71f66f04cf01d375e871382b5e4b/ruff-0.11.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:45b2e1d6c0eed89c248d024ea95074d0e09988d8e7b1dad8d3ab9a67017a5b03", size = 10073829, upload_time = "2025-04-17T13:35:40.255Z" }, { url = "https://files.pythonhosted.org/packages/3a/57/8dc6ccfd8380e5ca3d13ff7591e8ba46a3b330323515a4996b991b10bd5d/ruff-0.11.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:305b93f9798aee582e91e34437810439acb28b5fc1fee6b8205c78c806845a94", size = 10058689, upload_time = "2025-04-24T18:49:17.559Z" },
{ url = "https://files.pythonhosted.org/packages/93/cc/7ed02e0b86a649216b845b3ac66ed55d8aa86f5898c5f1691797f408fcb9/ruff-0.11.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bd40de4115b2ec4850302f1a1d8067f42e70b4990b68838ccb9ccd9f110c5e8b", size = 11076108, upload_time = "2025-04-17T13:35:42.559Z" }, { url = "https://files.pythonhosted.org/packages/23/bf/20487561ed72654147817885559ba2aa705272d8b5dee7654d3ef2dbf912/ruff-0.11.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a681db041ef55550c371f9cd52a3cf17a0da4c75d6bd691092dfc38170ebc4b6", size = 11073703, upload_time = "2025-04-24T18:49:20.247Z" },
{ url = "https://files.pythonhosted.org/packages/39/5e/5b09840fef0eff1a6fa1dea6296c07d09c17cb6fb94ed5593aa591b50460/ruff-0.11.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:77cda2dfbac1ab73aef5e514c4cbfc4ec1fbef4b84a44c736cc26f61b3814cd9", size = 11512366, upload_time = "2025-04-17T13:35:45.702Z" }, { url = "https://files.pythonhosted.org/packages/9d/27/04f2db95f4ef73dccedd0c21daf9991cc3b7f29901a4362057b132075aa4/ruff-0.11.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:07f1496ad00a4a139f4de220b0c97da6d4c85e0e4aa9b2624167b7d4d44fd6b6", size = 11532822, upload_time = "2025-04-24T18:49:23.765Z" },
{ url = "https://files.pythonhosted.org/packages/6f/4c/1cd5a84a412d3626335ae69f5f9de2bb554eea0faf46deb1f0cb48534042/ruff-0.11.6-py3-none-win32.whl", hash = "sha256:5151a871554be3036cd6e51d0ec6eef56334d74dfe1702de717a995ee3d5b287", size = 10485900, upload_time = "2025-04-17T13:35:47.695Z" }, { url = "https://files.pythonhosted.org/packages/e1/72/43b123e4db52144c8add336581de52185097545981ff6e9e58a21861c250/ruff-0.11.7-py3-none-win32.whl", hash = "sha256:f25dfb853ad217e6e5f1924ae8a5b3f6709051a13e9dad18690de6c8ff299e26", size = 10362436, upload_time = "2025-04-24T18:49:27.377Z" },
{ url = "https://files.pythonhosted.org/packages/42/46/8997872bc44d43df986491c18d4418f1caff03bc47b7f381261d62c23442/ruff-0.11.6-py3-none-win_amd64.whl", hash = "sha256:cce85721d09c51f3b782c331b0abd07e9d7d5f775840379c640606d3159cae0e", size = 11558592, upload_time = "2025-04-17T13:35:49.837Z" }, { url = "https://files.pythonhosted.org/packages/c5/a0/3e58cd76fdee53d5c8ce7a56d84540833f924ccdf2c7d657cb009e604d82/ruff-0.11.7-py3-none-win_amd64.whl", hash = "sha256:0a931d85959ceb77e92aea4bbedfded0a31534ce191252721128f77e5ae1f98a", size = 11566676, upload_time = "2025-04-24T18:49:30.938Z" },
{ url = "https://files.pythonhosted.org/packages/d7/6a/65fecd51a9ca19e1477c3879a7fda24f8904174d1275b419422ac00f6eee/ruff-0.11.6-py3-none-win_arm64.whl", hash = "sha256:3567ba0d07fb170b1b48d944715e3294b77f5b7679e8ba258199a250383ccb79", size = 10682766, upload_time = "2025-04-17T13:35:52.014Z" }, { url = "https://files.pythonhosted.org/packages/68/ca/69d7c7752bce162d1516e5592b1cc6b6668e9328c0d270609ddbeeadd7cf/ruff-0.11.7-py3-none-win_arm64.whl", hash = "sha256:778c1e5d6f9e91034142dfd06110534ca13220bfaad5c3735f6cb844654f6177", size = 10677936, upload_time = "2025-04-24T18:49:34.392Z" },
] ]
[[package]] [[package]]
@@ -2349,6 +2406,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/15/88e46eb9387e905704b69849618e699dc2f54407d8953cc4ec4b8b46528d/setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc", size = 931070, upload_time = "2024-07-09T16:07:58.829Z" }, { url = "https://files.pythonhosted.org/packages/ef/15/88e46eb9387e905704b69849618e699dc2f54407d8953cc4ec4b8b46528d/setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc", size = 931070, upload_time = "2024-07-09T16:07:58.829Z" },
] ]
[[package]]
name = "simple-websocket"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "wsproto" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b0/d4/bfa032f961103eba93de583b161f0e6a5b63cebb8f2c7d0c6e6efe1e3d2e/simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4", size = 17300, upload_time = "2024-10-10T22:39:31.412Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842, upload_time = "2024-10-10T22:39:29.645Z" },
]
[[package]] [[package]]
name = "six" name = "six"
version = "1.16.0" version = "1.16.0"
@@ -2652,6 +2721,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload_time = "2024-01-06T02:10:55.763Z" }, { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload_time = "2024-01-06T02:10:55.763Z" },
] ]
[[package]]
name = "websocket-client"
version = "1.8.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload_time = "2024-04-23T22:16:16.976Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload_time = "2024-04-23T22:16:14.422Z" },
]
[[package]] [[package]]
name = "websockets" name = "websockets"
version = "12.0" version = "12.0"
@@ -2711,6 +2789,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9d/6e/e792999e816d19d7fcbfa94c730936750036d65656a76a5a688b57a656c4/werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8", size = 227274, upload_time = "2024-05-05T23:10:29.567Z" }, { url = "https://files.pythonhosted.org/packages/9d/6e/e792999e816d19d7fcbfa94c730936750036d65656a76a5a688b57a656c4/werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8", size = 227274, upload_time = "2024-05-05T23:10:29.567Z" },
] ]
[[package]]
name = "wsproto"
version = "1.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload_time = "2022-08-23T19:58:21.447Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload_time = "2022-08-23T19:58:19.96Z" },
]
[[package]] [[package]]
name = "zope-event" name = "zope-event"
version = "5.0" version = "5.0"
+2 -2
View File
@@ -35,8 +35,8 @@ platform :android do
task: 'bundle', task: 'bundle',
build_type: 'Release', build_type: 'Release',
properties: { properties: {
"android.injected.version.code" => 196, "android.injected.version.code" => 197,
"android.injected.version.name" => "1.132.2", "android.injected.version.name" => "1.132.3",
} }
) )
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
+11 -6
View File
@@ -261,9 +261,11 @@
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1; CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100; LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
}; };
FAC6F88F2D287C890078CB2F = { FAC6F88F2D287C890078CB2F = {
CreatedOnToolsVersion = 16.0; CreatedOnToolsVersion = 16.0;
ProvisioningStyle = Automatic;
}; };
}; };
}; };
@@ -541,7 +543,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 202; CURRENT_PROJECT_VERSION = 205;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -685,7 +687,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 202; CURRENT_PROJECT_VERSION = 205;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -715,7 +717,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 202; CURRENT_PROJECT_VERSION = 205;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -748,7 +750,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 202; CURRENT_PROJECT_VERSION = 205;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -769,6 +771,7 @@
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.ShareExtension; PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -791,7 +794,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 202; CURRENT_PROJECT_VERSION = 205;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -811,6 +814,7 @@
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.ShareExtension; PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -831,7 +835,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 202; CURRENT_PROJECT_VERSION = 205;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -851,6 +855,7 @@
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.ShareExtension; PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
+2 -2
View File
@@ -78,7 +78,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.132.0</string> <string>1.132.3</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@@ -93,7 +93,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>202</string> <string>205</string>
<key>FLTEnableImpeller</key> <key>FLTEnableImpeller</key>
<true/> <true/>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
+4 -1
View File
@@ -18,8 +18,11 @@ default_platform(:ios)
platform :ios do platform :ios do
desc "iOS Release" desc "iOS Release"
lane :release do lane :release do
enable_automatic_code_signing(
path: "./Runner.xcodeproj",
)
increment_version_number( increment_version_number(
version_number: "1.132.2" version_number: "1.132.3"
) )
increment_build_number( increment_build_number(
build_number: latest_testflight_build_number + 1, build_number: latest_testflight_build_number + 1,
+40 -57
View File
@@ -1,7 +1,6 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
@@ -13,7 +12,6 @@ import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:immich_mobile/utils/map_utils.dart';
import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart'; import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
import 'package:immich_mobile/widgets/common/immich_app_bar.dart'; import 'package:immich_mobile/widgets/common/immich_app_bar.dart';
import 'package:immich_mobile/widgets/common/user_avatar.dart'; import 'package:immich_mobile/widgets/common/user_avatar.dart';
@@ -357,66 +355,51 @@ class PlacesCollectionCard extends StatelessWidget {
final widthFactor = isTablet ? 0.25 : 0.5; final widthFactor = isTablet ? 0.25 : 0.5;
final size = context.width * widthFactor - 20.0; final size = context.width * widthFactor - 20.0;
return FutureBuilder<(Position?, LocationPermission?)>( return GestureDetector(
future: MapUtils.checkPermAndGetLocation( onTap: () => context.pushRoute(
context: context, PlacesCollectionRoute(
silent: true, currentLocation: null,
),
), ),
builder: (context, snapshot) { child: Column(
var position = snapshot.data?.$1; crossAxisAlignment: CrossAxisAlignment.start,
return GestureDetector( children: [
onTap: () => context.pushRoute( SizedBox(
PlacesCollectionRoute( height: size,
currentLocation: position != null width: size,
? LatLng(position.latitude, position.longitude) child: DecoratedBox(
: null, decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(20)),
color:
context.colorScheme.secondaryContainer.withAlpha(100),
),
child: IgnorePointer(
child: MapThumbnail(
zoom: 8,
centre: const LatLng(
21.44950,
-157.91959,
),
showAttribution: false,
themeMode: context.isDarkTheme
? ThemeMode.dark
: ThemeMode.light,
),
),
), ),
), ),
child: Column( Padding(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.all(8.0),
children: [ child: Text(
SizedBox( 'places'.tr(),
height: size, style: context.textTheme.titleSmall?.copyWith(
width: size, color: context.colorScheme.onSurface,
child: DecoratedBox( fontWeight: FontWeight.w500,
decoration: BoxDecoration(
borderRadius:
const BorderRadius.all(Radius.circular(20)),
color: context.colorScheme.secondaryContainer
.withAlpha(100),
),
child: IgnorePointer(
child: snapshot.connectionState ==
ConnectionState.waiting
? const Center(child: CircularProgressIndicator())
: MapThumbnail(
zoom: 8,
centre: LatLng(
position?.latitude ?? 21.44950,
position?.longitude ?? -157.91959,
),
showAttribution: false,
themeMode: context.isDarkTheme
? ThemeMode.dark
: ThemeMode.light,
),
),
),
), ),
Padding( ),
padding: const EdgeInsets.all(8.0),
child: Text(
'places'.tr(),
style: context.textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurface,
fontWeight: FontWeight.w500,
),
),
),
],
), ),
); ],
}, ),
); );
}, },
); );
@@ -44,7 +44,7 @@ class PermissionOnboardingPage extends HookConsumerWidget {
} }
}), }),
child: const Text( child: const Text(
'grant_permission', 'continue',
).tr(), ).tr(),
), ),
], ],
+5 -3
View File
@@ -8,6 +8,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/services/asset.service.dart'; import 'package:immich_mobile/services/asset.service.dart';
import 'package:immich_mobile/services/share.service.dart'; import 'package:immich_mobile/services/share.service.dart';
import 'package:immich_mobile/utils/translation.dart';
import 'package:immich_mobile/widgets/common/date_time_picker.dart'; import 'package:immich_mobile/widgets/common/date_time_picker.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/widgets/common/location_picker.dart'; import 'package:immich_mobile/widgets/common/location_picker.dart';
@@ -57,12 +58,13 @@ Future<void> handleArchiveAssets(
.read(assetProvider.notifier) .read(assetProvider.notifier)
.toggleArchive(selection, shouldArchive); .toggleArchive(selection, shouldArchive);
final assetOrAssets = selection.length > 1 ? 'assets' : 'asset'; final message = shouldArchive
final archiveOrLibrary = shouldArchive ? 'archive' : 'library'; ? t('moved_to_archive', {'count': selection.length})
: t('moved_to_library', {'count': selection.length});
if (context.mounted) { if (context.mounted) {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: 'Moved ${selection.length} $assetOrAssets to $archiveOrLibrary', msg: message,
gravity: toastGravity, gravity: toastGravity,
); );
} }
+14
View File
@@ -0,0 +1,14 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:intl/message_format.dart';
String t(String key, [Map<String, Object>? args]) {
try {
String message = key.tr();
if (args != null) {
return MessageFormat(message).format(args);
}
return message;
} catch (e) {
return key;
}
}
@@ -97,6 +97,7 @@ class ImmichAssetGrid extends HookConsumerWidget {
); );
if (7 - scaleFactor.value.toInt() != perRow.value) { if (7 - scaleFactor.value.toInt() != perRow.value) {
perRow.value = 7 - scaleFactor.value.toInt(); perRow.value = 7 - scaleFactor.value.toInt();
settings.setSetting(AppSettingsEnum.tilesPerRow, perRow.value);
} }
}; };
}), }),
@@ -755,7 +755,7 @@ class _MonthTitle extends StatelessWidget {
key: Key("month-$title"), key: Key("month-$title"),
padding: const EdgeInsets.only(left: 12.0, top: 24.0), padding: const EdgeInsets.only(left: 12.0, top: 24.0),
child: Text( child: Text(
title, toBeginningOfSentenceCase(title, context.locale.languageCode),
style: const TextStyle( style: const TextStyle(
fontSize: 26, fontSize: 26,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@@ -786,7 +786,7 @@ class _Title extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GroupDividerTitle( return GroupDividerTitle(
text: title, text: toBeginningOfSentenceCase(title, context.locale.languageCode),
multiselectEnabled: selectionActive, multiselectEnabled: selectionActive,
onSelect: () => selectAssets(assets), onSelect: () => selectAssets(assets),
onDeselect: () => deselectAssets(assets), onDeselect: () => deselectAssets(assets),
+22 -3
View File
@@ -207,9 +207,27 @@ class LoginForm extends HookConsumerWidget {
} }
String generateRandomString(int length) { String generateRandomString(int length) {
const chars =
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
final random = Random.secure(); final random = Random.secure();
return base64Url return String.fromCharCodes(
.encode(List<int>.generate(32, (i) => random.nextInt(256))); Iterable.generate(
length,
(_) => chars.codeUnitAt(random.nextInt(chars.length)),
),
);
}
List<int> randomBytes(int length) {
final random = Random.secure();
return List<int>.generate(length, (i) => random.nextInt(256));
}
/// Per specification, the code verifier must be 43-128 characters long
/// and consist of characters [A-Z, a-z, 0-9, "-", ".", "_", "~"]
/// https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
String randomCodeVerifier() {
return base64Url.encode(randomBytes(42));
} }
Future<String> generatePKCECodeChallenge(String codeVerifier) async { Future<String> generatePKCECodeChallenge(String codeVerifier) async {
@@ -223,7 +241,8 @@ class LoginForm extends HookConsumerWidget {
String? oAuthServerUrl; String? oAuthServerUrl;
final state = generateRandomString(32); final state = generateRandomString(32);
final codeVerifier = generateRandomString(64);
final codeVerifier = randomCodeVerifier();
final codeChallenge = await generatePKCECodeChallenge(codeVerifier); final codeChallenge = await generatePKCECodeChallenge(codeVerifier);
try { try {
+18 -4
View File
@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.132.2 - API version: 1.132.3
- Generator version: 7.8.0 - Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen - Build package: org.openapitools.codegen.languages.DartClientCodegen
@@ -145,8 +145,15 @@ Class | Method | HTTP request | Description
*MemoriesApi* | [**removeMemoryAssets**](doc//MemoriesApi.md#removememoryassets) | **DELETE** /memories/{id}/assets | *MemoriesApi* | [**removeMemoryAssets**](doc//MemoriesApi.md#removememoryassets) | **DELETE** /memories/{id}/assets |
*MemoriesApi* | [**searchMemories**](doc//MemoriesApi.md#searchmemories) | **GET** /memories | *MemoriesApi* | [**searchMemories**](doc//MemoriesApi.md#searchmemories) | **GET** /memories |
*MemoriesApi* | [**updateMemory**](doc//MemoriesApi.md#updatememory) | **PUT** /memories/{id} | *MemoriesApi* | [**updateMemory**](doc//MemoriesApi.md#updatememory) | **PUT** /memories/{id} |
*NotificationsAdminApi* | [**getNotificationTemplateAdmin**](doc//NotificationsAdminApi.md#getnotificationtemplateadmin) | **POST** /notifications/admin/templates/{name} | *NotificationsApi* | [**deleteNotification**](doc//NotificationsApi.md#deletenotification) | **DELETE** /notifications/{id} |
*NotificationsAdminApi* | [**sendTestEmailAdmin**](doc//NotificationsAdminApi.md#sendtestemailadmin) | **POST** /notifications/admin/test-email | *NotificationsApi* | [**deleteNotifications**](doc//NotificationsApi.md#deletenotifications) | **DELETE** /notifications |
*NotificationsApi* | [**getNotification**](doc//NotificationsApi.md#getnotification) | **GET** /notifications/{id} |
*NotificationsApi* | [**getNotifications**](doc//NotificationsApi.md#getnotifications) | **GET** /notifications |
*NotificationsApi* | [**updateNotification**](doc//NotificationsApi.md#updatenotification) | **PUT** /notifications/{id} |
*NotificationsApi* | [**updateNotifications**](doc//NotificationsApi.md#updatenotifications) | **PUT** /notifications |
*NotificationsAdminApi* | [**createNotification**](doc//NotificationsAdminApi.md#createnotification) | **POST** /admin/notifications |
*NotificationsAdminApi* | [**getNotificationTemplateAdmin**](doc//NotificationsAdminApi.md#getnotificationtemplateadmin) | **POST** /admin/notifications/templates/{name} |
*NotificationsAdminApi* | [**sendTestEmailAdmin**](doc//NotificationsAdminApi.md#sendtestemailadmin) | **POST** /admin/notifications/test-email |
*OAuthApi* | [**finishOAuth**](doc//OAuthApi.md#finishoauth) | **POST** /oauth/callback | *OAuthApi* | [**finishOAuth**](doc//OAuthApi.md#finishoauth) | **POST** /oauth/callback |
*OAuthApi* | [**linkOAuthAccount**](doc//OAuthApi.md#linkoauthaccount) | **POST** /oauth/link | *OAuthApi* | [**linkOAuthAccount**](doc//OAuthApi.md#linkoauthaccount) | **POST** /oauth/link |
*OAuthApi* | [**redirectOAuthToMobile**](doc//OAuthApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect | *OAuthApi* | [**redirectOAuthToMobile**](doc//OAuthApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect |
@@ -300,7 +307,6 @@ Class | Method | HTTP request | Description
- [AssetStatsResponseDto](doc//AssetStatsResponseDto.md) - [AssetStatsResponseDto](doc//AssetStatsResponseDto.md)
- [AssetTypeEnum](doc//AssetTypeEnum.md) - [AssetTypeEnum](doc//AssetTypeEnum.md)
- [AudioCodec](doc//AudioCodec.md) - [AudioCodec](doc//AudioCodec.md)
- [AvatarResponse](doc//AvatarResponse.md)
- [AvatarUpdate](doc//AvatarUpdate.md) - [AvatarUpdate](doc//AvatarUpdate.md)
- [BulkIdResponseDto](doc//BulkIdResponseDto.md) - [BulkIdResponseDto](doc//BulkIdResponseDto.md)
- [BulkIdsDto](doc//BulkIdsDto.md) - [BulkIdsDto](doc//BulkIdsDto.md)
@@ -361,9 +367,17 @@ Class | Method | HTTP request | Description
- [MemoryUpdateDto](doc//MemoryUpdateDto.md) - [MemoryUpdateDto](doc//MemoryUpdateDto.md)
- [MergePersonDto](doc//MergePersonDto.md) - [MergePersonDto](doc//MergePersonDto.md)
- [MetadataSearchDto](doc//MetadataSearchDto.md) - [MetadataSearchDto](doc//MetadataSearchDto.md)
- [NotificationCreateDto](doc//NotificationCreateDto.md)
- [NotificationDeleteAllDto](doc//NotificationDeleteAllDto.md)
- [NotificationDto](doc//NotificationDto.md)
- [NotificationLevel](doc//NotificationLevel.md)
- [NotificationType](doc//NotificationType.md)
- [NotificationUpdateAllDto](doc//NotificationUpdateAllDto.md)
- [NotificationUpdateDto](doc//NotificationUpdateDto.md)
- [OAuthAuthorizeResponseDto](doc//OAuthAuthorizeResponseDto.md) - [OAuthAuthorizeResponseDto](doc//OAuthAuthorizeResponseDto.md)
- [OAuthCallbackDto](doc//OAuthCallbackDto.md) - [OAuthCallbackDto](doc//OAuthCallbackDto.md)
- [OAuthConfigDto](doc//OAuthConfigDto.md) - [OAuthConfigDto](doc//OAuthConfigDto.md)
- [OAuthTokenEndpointAuthMethod](doc//OAuthTokenEndpointAuthMethod.md)
- [OnThisDayDto](doc//OnThisDayDto.md) - [OnThisDayDto](doc//OnThisDayDto.md)
- [PartnerDirection](doc//PartnerDirection.md) - [PartnerDirection](doc//PartnerDirection.md)
- [PartnerResponseDto](doc//PartnerResponseDto.md) - [PartnerResponseDto](doc//PartnerResponseDto.md)
+9 -1
View File
@@ -44,6 +44,7 @@ part 'api/jobs_api.dart';
part 'api/libraries_api.dart'; part 'api/libraries_api.dart';
part 'api/map_api.dart'; part 'api/map_api.dart';
part 'api/memories_api.dart'; part 'api/memories_api.dart';
part 'api/notifications_api.dart';
part 'api/notifications_admin_api.dart'; part 'api/notifications_admin_api.dart';
part 'api/o_auth_api.dart'; part 'api/o_auth_api.dart';
part 'api/partners_api.dart'; part 'api/partners_api.dart';
@@ -107,7 +108,6 @@ part 'model/asset_stack_response_dto.dart';
part 'model/asset_stats_response_dto.dart'; part 'model/asset_stats_response_dto.dart';
part 'model/asset_type_enum.dart'; part 'model/asset_type_enum.dart';
part 'model/audio_codec.dart'; part 'model/audio_codec.dart';
part 'model/avatar_response.dart';
part 'model/avatar_update.dart'; part 'model/avatar_update.dart';
part 'model/bulk_id_response_dto.dart'; part 'model/bulk_id_response_dto.dart';
part 'model/bulk_ids_dto.dart'; part 'model/bulk_ids_dto.dart';
@@ -168,9 +168,17 @@ part 'model/memory_type.dart';
part 'model/memory_update_dto.dart'; part 'model/memory_update_dto.dart';
part 'model/merge_person_dto.dart'; part 'model/merge_person_dto.dart';
part 'model/metadata_search_dto.dart'; part 'model/metadata_search_dto.dart';
part 'model/notification_create_dto.dart';
part 'model/notification_delete_all_dto.dart';
part 'model/notification_dto.dart';
part 'model/notification_level.dart';
part 'model/notification_type.dart';
part 'model/notification_update_all_dto.dart';
part 'model/notification_update_dto.dart';
part 'model/o_auth_authorize_response_dto.dart'; part 'model/o_auth_authorize_response_dto.dart';
part 'model/o_auth_callback_dto.dart'; part 'model/o_auth_callback_dto.dart';
part 'model/o_auth_config_dto.dart'; part 'model/o_auth_config_dto.dart';
part 'model/o_auth_token_endpoint_auth_method.dart';
part 'model/on_this_day_dto.dart'; part 'model/on_this_day_dto.dart';
part 'model/partner_direction.dart'; part 'model/partner_direction.dart';
part 'model/partner_response_dto.dart'; part 'model/partner_response_dto.dart';
+51 -4
View File
@@ -16,7 +16,54 @@ class NotificationsAdminApi {
final ApiClient apiClient; final ApiClient apiClient;
/// Performs an HTTP 'POST /notifications/admin/templates/{name}' operation and returns the [Response]. /// Performs an HTTP 'POST /admin/notifications' operation and returns the [Response].
/// Parameters:
///
/// * [NotificationCreateDto] notificationCreateDto (required):
Future<Response> createNotificationWithHttpInfo(NotificationCreateDto notificationCreateDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/admin/notifications';
// ignore: prefer_final_locals
Object? postBody = notificationCreateDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [NotificationCreateDto] notificationCreateDto (required):
Future<NotificationDto?> createNotification(NotificationCreateDto notificationCreateDto,) async {
final response = await createNotificationWithHttpInfo(notificationCreateDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'NotificationDto',) as NotificationDto;
}
return null;
}
/// Performs an HTTP 'POST /admin/notifications/templates/{name}' operation and returns the [Response].
/// Parameters: /// Parameters:
/// ///
/// * [String] name (required): /// * [String] name (required):
@@ -24,7 +71,7 @@ class NotificationsAdminApi {
/// * [TemplateDto] templateDto (required): /// * [TemplateDto] templateDto (required):
Future<Response> getNotificationTemplateAdminWithHttpInfo(String name, TemplateDto templateDto,) async { Future<Response> getNotificationTemplateAdminWithHttpInfo(String name, TemplateDto templateDto,) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final apiPath = r'/notifications/admin/templates/{name}' final apiPath = r'/admin/notifications/templates/{name}'
.replaceAll('{name}', name); .replaceAll('{name}', name);
// ignore: prefer_final_locals // ignore: prefer_final_locals
@@ -68,13 +115,13 @@ class NotificationsAdminApi {
return null; return null;
} }
/// Performs an HTTP 'POST /notifications/admin/test-email' operation and returns the [Response]. /// Performs an HTTP 'POST /admin/notifications/test-email' operation and returns the [Response].
/// Parameters: /// Parameters:
/// ///
/// * [SystemConfigSmtpDto] systemConfigSmtpDto (required): /// * [SystemConfigSmtpDto] systemConfigSmtpDto (required):
Future<Response> sendTestEmailAdminWithHttpInfo(SystemConfigSmtpDto systemConfigSmtpDto,) async { Future<Response> sendTestEmailAdminWithHttpInfo(SystemConfigSmtpDto systemConfigSmtpDto,) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final apiPath = r'/notifications/admin/test-email'; final apiPath = r'/admin/notifications/test-email';
// ignore: prefer_final_locals // ignore: prefer_final_locals
Object? postBody = systemConfigSmtpDto; Object? postBody = systemConfigSmtpDto;
+311
View File
@@ -0,0 +1,311 @@
//
// 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 NotificationsApi {
NotificationsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
final ApiClient apiClient;
/// Performs an HTTP 'DELETE /notifications/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> deleteNotificationWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final apiPath = r'/notifications/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'DELETE',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
Future<void> deleteNotification(String id,) async {
final response = await deleteNotificationWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Performs an HTTP 'DELETE /notifications' operation and returns the [Response].
/// Parameters:
///
/// * [NotificationDeleteAllDto] notificationDeleteAllDto (required):
Future<Response> deleteNotificationsWithHttpInfo(NotificationDeleteAllDto notificationDeleteAllDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/notifications';
// ignore: prefer_final_locals
Object? postBody = notificationDeleteAllDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'DELETE',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [NotificationDeleteAllDto] notificationDeleteAllDto (required):
Future<void> deleteNotifications(NotificationDeleteAllDto notificationDeleteAllDto,) async {
final response = await deleteNotificationsWithHttpInfo(notificationDeleteAllDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Performs an HTTP 'GET /notifications/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getNotificationWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final apiPath = r'/notifications/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
Future<NotificationDto?> getNotification(String id,) async {
final response = await getNotificationWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'NotificationDto',) as NotificationDto;
}
return null;
}
/// Performs an HTTP 'GET /notifications' operation and returns the [Response].
/// Parameters:
///
/// * [String] id:
///
/// * [NotificationLevel] level:
///
/// * [NotificationType] type:
///
/// * [bool] unread:
Future<Response> getNotificationsWithHttpInfo({ String? id, NotificationLevel? level, NotificationType? type, bool? unread, }) async {
// ignore: prefer_const_declarations
final apiPath = r'/notifications';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (id != null) {
queryParams.addAll(_queryParams('', 'id', id));
}
if (level != null) {
queryParams.addAll(_queryParams('', 'level', level));
}
if (type != null) {
queryParams.addAll(_queryParams('', 'type', type));
}
if (unread != null) {
queryParams.addAll(_queryParams('', 'unread', unread));
}
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id:
///
/// * [NotificationLevel] level:
///
/// * [NotificationType] type:
///
/// * [bool] unread:
Future<List<NotificationDto>?> getNotifications({ String? id, NotificationLevel? level, NotificationType? type, bool? unread, }) async {
final response = await getNotificationsWithHttpInfo( id: id, level: level, type: type, unread: unread, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<NotificationDto>') as List)
.cast<NotificationDto>()
.toList(growable: false);
}
return null;
}
/// Performs an HTTP 'PUT /notifications/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
///
/// * [NotificationUpdateDto] notificationUpdateDto (required):
Future<Response> updateNotificationWithHttpInfo(String id, NotificationUpdateDto notificationUpdateDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/notifications/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody = notificationUpdateDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'PUT',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
///
/// * [NotificationUpdateDto] notificationUpdateDto (required):
Future<NotificationDto?> updateNotification(String id, NotificationUpdateDto notificationUpdateDto,) async {
final response = await updateNotificationWithHttpInfo(id, notificationUpdateDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'NotificationDto',) as NotificationDto;
}
return null;
}
/// Performs an HTTP 'PUT /notifications' operation and returns the [Response].
/// Parameters:
///
/// * [NotificationUpdateAllDto] notificationUpdateAllDto (required):
Future<Response> updateNotificationsWithHttpInfo(NotificationUpdateAllDto notificationUpdateAllDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/notifications';
// ignore: prefer_final_locals
Object? postBody = notificationUpdateAllDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'PUT',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [NotificationUpdateAllDto] notificationUpdateAllDto (required):
Future<void> updateNotifications(NotificationUpdateAllDto notificationUpdateAllDto,) async {
final response = await updateNotificationsWithHttpInfo(notificationUpdateAllDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
}
+16 -2
View File
@@ -270,8 +270,6 @@ class ApiClient {
return AssetTypeEnumTypeTransformer().decode(value); return AssetTypeEnumTypeTransformer().decode(value);
case 'AudioCodec': case 'AudioCodec':
return AudioCodecTypeTransformer().decode(value); return AudioCodecTypeTransformer().decode(value);
case 'AvatarResponse':
return AvatarResponse.fromJson(value);
case 'AvatarUpdate': case 'AvatarUpdate':
return AvatarUpdate.fromJson(value); return AvatarUpdate.fromJson(value);
case 'BulkIdResponseDto': case 'BulkIdResponseDto':
@@ -392,12 +390,28 @@ class ApiClient {
return MergePersonDto.fromJson(value); return MergePersonDto.fromJson(value);
case 'MetadataSearchDto': case 'MetadataSearchDto':
return MetadataSearchDto.fromJson(value); return MetadataSearchDto.fromJson(value);
case 'NotificationCreateDto':
return NotificationCreateDto.fromJson(value);
case 'NotificationDeleteAllDto':
return NotificationDeleteAllDto.fromJson(value);
case 'NotificationDto':
return NotificationDto.fromJson(value);
case 'NotificationLevel':
return NotificationLevelTypeTransformer().decode(value);
case 'NotificationType':
return NotificationTypeTypeTransformer().decode(value);
case 'NotificationUpdateAllDto':
return NotificationUpdateAllDto.fromJson(value);
case 'NotificationUpdateDto':
return NotificationUpdateDto.fromJson(value);
case 'OAuthAuthorizeResponseDto': case 'OAuthAuthorizeResponseDto':
return OAuthAuthorizeResponseDto.fromJson(value); return OAuthAuthorizeResponseDto.fromJson(value);
case 'OAuthCallbackDto': case 'OAuthCallbackDto':
return OAuthCallbackDto.fromJson(value); return OAuthCallbackDto.fromJson(value);
case 'OAuthConfigDto': case 'OAuthConfigDto':
return OAuthConfigDto.fromJson(value); return OAuthConfigDto.fromJson(value);
case 'OAuthTokenEndpointAuthMethod':
return OAuthTokenEndpointAuthMethodTypeTransformer().decode(value);
case 'OnThisDayDto': case 'OnThisDayDto':
return OnThisDayDto.fromJson(value); return OnThisDayDto.fromJson(value);
case 'PartnerDirection': case 'PartnerDirection':
+9
View File
@@ -100,6 +100,15 @@ String parameterToString(dynamic value) {
if (value is MemoryType) { if (value is MemoryType) {
return MemoryTypeTypeTransformer().encode(value).toString(); return MemoryTypeTypeTransformer().encode(value).toString();
} }
if (value is NotificationLevel) {
return NotificationLevelTypeTransformer().encode(value).toString();
}
if (value is NotificationType) {
return NotificationTypeTypeTransformer().encode(value).toString();
}
if (value is OAuthTokenEndpointAuthMethod) {
return OAuthTokenEndpointAuthMethodTypeTransformer().encode(value).toString();
}
if (value is PartnerDirection) { if (value is PartnerDirection) {
return PartnerDirectionTypeTransformer().encode(value).toString(); return PartnerDirectionTypeTransformer().encode(value).toString();
} }
+180
View File
@@ -0,0 +1,180 @@
//
// 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 NotificationCreateDto {
/// Returns a new [NotificationCreateDto] instance.
NotificationCreateDto({
this.data,
this.description,
this.level,
this.readAt,
required this.title,
this.type,
required this.userId,
});
///
/// 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.
///
Object? data;
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.
///
NotificationLevel? level;
DateTime? readAt;
String title;
///
/// 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.
///
NotificationType? type;
String userId;
@override
bool operator ==(Object other) => identical(this, other) || other is NotificationCreateDto &&
other.data == data &&
other.description == description &&
other.level == level &&
other.readAt == readAt &&
other.title == title &&
other.type == type &&
other.userId == userId;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(data == null ? 0 : data!.hashCode) +
(description == null ? 0 : description!.hashCode) +
(level == null ? 0 : level!.hashCode) +
(readAt == null ? 0 : readAt!.hashCode) +
(title.hashCode) +
(type == null ? 0 : type!.hashCode) +
(userId.hashCode);
@override
String toString() => 'NotificationCreateDto[data=$data, description=$description, level=$level, readAt=$readAt, title=$title, type=$type, userId=$userId]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.data != null) {
json[r'data'] = this.data;
} else {
// json[r'data'] = null;
}
if (this.description != null) {
json[r'description'] = this.description;
} else {
// json[r'description'] = null;
}
if (this.level != null) {
json[r'level'] = this.level;
} else {
// json[r'level'] = null;
}
if (this.readAt != null) {
json[r'readAt'] = this.readAt!.toUtc().toIso8601String();
} else {
// json[r'readAt'] = null;
}
json[r'title'] = this.title;
if (this.type != null) {
json[r'type'] = this.type;
} else {
// json[r'type'] = null;
}
json[r'userId'] = this.userId;
return json;
}
/// Returns a new [NotificationCreateDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static NotificationCreateDto? fromJson(dynamic value) {
upgradeDto(value, "NotificationCreateDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return NotificationCreateDto(
data: mapValueOfType<Object>(json, r'data'),
description: mapValueOfType<String>(json, r'description'),
level: NotificationLevel.fromJson(json[r'level']),
readAt: mapDateTime(json, r'readAt', r''),
title: mapValueOfType<String>(json, r'title')!,
type: NotificationType.fromJson(json[r'type']),
userId: mapValueOfType<String>(json, r'userId')!,
);
}
return null;
}
static List<NotificationCreateDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <NotificationCreateDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = NotificationCreateDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, NotificationCreateDto> mapFromJson(dynamic json) {
final map = <String, NotificationCreateDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = NotificationCreateDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of NotificationCreateDto-objects as value to a dart map
static Map<String, List<NotificationCreateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<NotificationCreateDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = NotificationCreateDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'title',
'userId',
};
}
+101
View File
@@ -0,0 +1,101 @@
//
// 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 NotificationDeleteAllDto {
/// Returns a new [NotificationDeleteAllDto] instance.
NotificationDeleteAllDto({
this.ids = const [],
});
List<String> ids;
@override
bool operator ==(Object other) => identical(this, other) || other is NotificationDeleteAllDto &&
_deepEquality.equals(other.ids, ids);
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(ids.hashCode);
@override
String toString() => 'NotificationDeleteAllDto[ids=$ids]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'ids'] = this.ids;
return json;
}
/// Returns a new [NotificationDeleteAllDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static NotificationDeleteAllDto? fromJson(dynamic value) {
upgradeDto(value, "NotificationDeleteAllDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return NotificationDeleteAllDto(
ids: json[r'ids'] is Iterable
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
: const [],
);
}
return null;
}
static List<NotificationDeleteAllDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <NotificationDeleteAllDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = NotificationDeleteAllDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, NotificationDeleteAllDto> mapFromJson(dynamic json) {
final map = <String, NotificationDeleteAllDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = NotificationDeleteAllDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of NotificationDeleteAllDto-objects as value to a dart map
static Map<String, List<NotificationDeleteAllDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<NotificationDeleteAllDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = NotificationDeleteAllDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'ids',
};
}
+182
View File
@@ -0,0 +1,182 @@
//
// 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 NotificationDto {
/// Returns a new [NotificationDto] instance.
NotificationDto({
required this.createdAt,
this.data,
this.description,
required this.id,
required this.level,
this.readAt,
required this.title,
required this.type,
});
DateTime createdAt;
///
/// 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.
///
Object? data;
///
/// 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.
///
String? description;
String id;
NotificationLevel level;
///
/// 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.
///
DateTime? readAt;
String title;
NotificationType type;
@override
bool operator ==(Object other) => identical(this, other) || other is NotificationDto &&
other.createdAt == createdAt &&
other.data == data &&
other.description == description &&
other.id == id &&
other.level == level &&
other.readAt == readAt &&
other.title == title &&
other.type == type;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(createdAt.hashCode) +
(data == null ? 0 : data!.hashCode) +
(description == null ? 0 : description!.hashCode) +
(id.hashCode) +
(level.hashCode) +
(readAt == null ? 0 : readAt!.hashCode) +
(title.hashCode) +
(type.hashCode);
@override
String toString() => 'NotificationDto[createdAt=$createdAt, data=$data, description=$description, id=$id, level=$level, readAt=$readAt, title=$title, type=$type]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
if (this.data != null) {
json[r'data'] = this.data;
} else {
// json[r'data'] = null;
}
if (this.description != null) {
json[r'description'] = this.description;
} else {
// json[r'description'] = null;
}
json[r'id'] = this.id;
json[r'level'] = this.level;
if (this.readAt != null) {
json[r'readAt'] = this.readAt!.toUtc().toIso8601String();
} else {
// json[r'readAt'] = null;
}
json[r'title'] = this.title;
json[r'type'] = this.type;
return json;
}
/// Returns a new [NotificationDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static NotificationDto? fromJson(dynamic value) {
upgradeDto(value, "NotificationDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return NotificationDto(
createdAt: mapDateTime(json, r'createdAt', r'')!,
data: mapValueOfType<Object>(json, r'data'),
description: mapValueOfType<String>(json, r'description'),
id: mapValueOfType<String>(json, r'id')!,
level: NotificationLevel.fromJson(json[r'level'])!,
readAt: mapDateTime(json, r'readAt', r''),
title: mapValueOfType<String>(json, r'title')!,
type: NotificationType.fromJson(json[r'type'])!,
);
}
return null;
}
static List<NotificationDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <NotificationDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = NotificationDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, NotificationDto> mapFromJson(dynamic json) {
final map = <String, NotificationDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = NotificationDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of NotificationDto-objects as value to a dart map
static Map<String, List<NotificationDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<NotificationDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = NotificationDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'createdAt',
'id',
'level',
'title',
'type',
};
}
+91
View File
@@ -0,0 +1,91 @@
//
// 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 NotificationLevel {
/// Instantiate a new enum with the provided [value].
const NotificationLevel._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const success = NotificationLevel._(r'success');
static const error = NotificationLevel._(r'error');
static const warning = NotificationLevel._(r'warning');
static const info = NotificationLevel._(r'info');
/// List of all possible values in this [enum][NotificationLevel].
static const values = <NotificationLevel>[
success,
error,
warning,
info,
];
static NotificationLevel? fromJson(dynamic value) => NotificationLevelTypeTransformer().decode(value);
static List<NotificationLevel> listFromJson(dynamic json, {bool growable = false,}) {
final result = <NotificationLevel>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = NotificationLevel.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [NotificationLevel] to String,
/// and [decode] dynamic data back to [NotificationLevel].
class NotificationLevelTypeTransformer {
factory NotificationLevelTypeTransformer() => _instance ??= const NotificationLevelTypeTransformer._();
const NotificationLevelTypeTransformer._();
String encode(NotificationLevel data) => data.value;
/// Decodes a [dynamic value][data] to a NotificationLevel.
///
/// 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.
NotificationLevel? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'success': return NotificationLevel.success;
case r'error': return NotificationLevel.error;
case r'warning': return NotificationLevel.warning;
case r'info': return NotificationLevel.info;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [NotificationLevelTypeTransformer] instance.
static NotificationLevelTypeTransformer? _instance;
}
+91
View File
@@ -0,0 +1,91 @@
//
// 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 NotificationType {
/// Instantiate a new enum with the provided [value].
const NotificationType._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const jobFailed = NotificationType._(r'JobFailed');
static const backupFailed = NotificationType._(r'BackupFailed');
static const systemMessage = NotificationType._(r'SystemMessage');
static const custom = NotificationType._(r'Custom');
/// List of all possible values in this [enum][NotificationType].
static const values = <NotificationType>[
jobFailed,
backupFailed,
systemMessage,
custom,
];
static NotificationType? fromJson(dynamic value) => NotificationTypeTypeTransformer().decode(value);
static List<NotificationType> listFromJson(dynamic json, {bool growable = false,}) {
final result = <NotificationType>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = NotificationType.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [NotificationType] to String,
/// and [decode] dynamic data back to [NotificationType].
class NotificationTypeTypeTransformer {
factory NotificationTypeTypeTransformer() => _instance ??= const NotificationTypeTypeTransformer._();
const NotificationTypeTypeTransformer._();
String encode(NotificationType data) => data.value;
/// Decodes a [dynamic value][data] to a NotificationType.
///
/// 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.
NotificationType? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'JobFailed': return NotificationType.jobFailed;
case r'BackupFailed': return NotificationType.backupFailed;
case r'SystemMessage': return NotificationType.systemMessage;
case r'Custom': return NotificationType.custom;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [NotificationTypeTypeTransformer] instance.
static NotificationTypeTypeTransformer? _instance;
}
+112
View File
@@ -0,0 +1,112 @@
//
// 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 NotificationUpdateAllDto {
/// Returns a new [NotificationUpdateAllDto] instance.
NotificationUpdateAllDto({
this.ids = const [],
this.readAt,
});
List<String> ids;
DateTime? readAt;
@override
bool operator ==(Object other) => identical(this, other) || other is NotificationUpdateAllDto &&
_deepEquality.equals(other.ids, ids) &&
other.readAt == readAt;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(ids.hashCode) +
(readAt == null ? 0 : readAt!.hashCode);
@override
String toString() => 'NotificationUpdateAllDto[ids=$ids, readAt=$readAt]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'ids'] = this.ids;
if (this.readAt != null) {
json[r'readAt'] = this.readAt!.toUtc().toIso8601String();
} else {
// json[r'readAt'] = null;
}
return json;
}
/// Returns a new [NotificationUpdateAllDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static NotificationUpdateAllDto? fromJson(dynamic value) {
upgradeDto(value, "NotificationUpdateAllDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return NotificationUpdateAllDto(
ids: json[r'ids'] is Iterable
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
: const [],
readAt: mapDateTime(json, r'readAt', r''),
);
}
return null;
}
static List<NotificationUpdateAllDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <NotificationUpdateAllDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = NotificationUpdateAllDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, NotificationUpdateAllDto> mapFromJson(dynamic json) {
final map = <String, NotificationUpdateAllDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = NotificationUpdateAllDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of NotificationUpdateAllDto-objects as value to a dart map
static Map<String, List<NotificationUpdateAllDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<NotificationUpdateAllDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = NotificationUpdateAllDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'ids',
};
}
@@ -10,52 +10,56 @@
part of openapi.api; part of openapi.api;
class AvatarResponse { class NotificationUpdateDto {
/// Returns a new [AvatarResponse] instance. /// Returns a new [NotificationUpdateDto] instance.
AvatarResponse({ NotificationUpdateDto({
required this.color, this.readAt,
}); });
UserAvatarColor color; DateTime? readAt;
@override @override
bool operator ==(Object other) => identical(this, other) || other is AvatarResponse && bool operator ==(Object other) => identical(this, other) || other is NotificationUpdateDto &&
other.color == color; other.readAt == readAt;
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(color.hashCode); (readAt == null ? 0 : readAt!.hashCode);
@override @override
String toString() => 'AvatarResponse[color=$color]'; String toString() => 'NotificationUpdateDto[readAt=$readAt]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json[r'color'] = this.color; if (this.readAt != null) {
json[r'readAt'] = this.readAt!.toUtc().toIso8601String();
} else {
// json[r'readAt'] = null;
}
return json; return json;
} }
/// Returns a new [AvatarResponse] instance and imports its values from /// Returns a new [NotificationUpdateDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AvatarResponse? fromJson(dynamic value) { static NotificationUpdateDto? fromJson(dynamic value) {
upgradeDto(value, "AvatarResponse"); upgradeDto(value, "NotificationUpdateDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return AvatarResponse( return NotificationUpdateDto(
color: UserAvatarColor.fromJson(json[r'color'])!, readAt: mapDateTime(json, r'readAt', r''),
); );
} }
return null; return null;
} }
static List<AvatarResponse> listFromJson(dynamic json, {bool growable = false,}) { static List<NotificationUpdateDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AvatarResponse>[]; final result = <NotificationUpdateDto>[];
if (json is List && json.isNotEmpty) { if (json is List && json.isNotEmpty) {
for (final row in json) { for (final row in json) {
final value = AvatarResponse.fromJson(row); final value = NotificationUpdateDto.fromJson(row);
if (value != null) { if (value != null) {
result.add(value); result.add(value);
} }
@@ -64,12 +68,12 @@ class AvatarResponse {
return result.toList(growable: growable); return result.toList(growable: growable);
} }
static Map<String, AvatarResponse> mapFromJson(dynamic json) { static Map<String, NotificationUpdateDto> mapFromJson(dynamic json) {
final map = <String, AvatarResponse>{}; final map = <String, NotificationUpdateDto>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) { for (final entry in json.entries) {
final value = AvatarResponse.fromJson(entry.value); final value = NotificationUpdateDto.fromJson(entry.value);
if (value != null) { if (value != null) {
map[entry.key] = value; map[entry.key] = value;
} }
@@ -78,14 +82,14 @@ class AvatarResponse {
return map; return map;
} }
// maps a json object with a list of AvatarResponse-objects as value to a dart map // maps a json object with a list of NotificationUpdateDto-objects as value to a dart map
static Map<String, List<AvatarResponse>> mapListFromJson(dynamic json, {bool growable = false,}) { static Map<String, List<NotificationUpdateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AvatarResponse>>{}; final map = <String, List<NotificationUpdateDto>>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments // ignore: parameter_assignments
json = json.cast<String, dynamic>(); json = json.cast<String, dynamic>();
for (final entry in json.entries) { for (final entry in json.entries) {
map[entry.key] = AvatarResponse.listFromJson(entry.value, growable: growable,); map[entry.key] = NotificationUpdateDto.listFromJson(entry.value, growable: growable,);
} }
} }
return map; return map;
@@ -93,7 +97,6 @@ class AvatarResponse {
/// The list of required keys that must be present in a JSON. /// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{ static const requiredKeys = <String>{
'color',
}; };
} }
@@ -0,0 +1,85 @@
//
// 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 OAuthTokenEndpointAuthMethod {
/// Instantiate a new enum with the provided [value].
const OAuthTokenEndpointAuthMethod._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const post = OAuthTokenEndpointAuthMethod._(r'client_secret_post');
static const basic = OAuthTokenEndpointAuthMethod._(r'client_secret_basic');
/// List of all possible values in this [enum][OAuthTokenEndpointAuthMethod].
static const values = <OAuthTokenEndpointAuthMethod>[
post,
basic,
];
static OAuthTokenEndpointAuthMethod? fromJson(dynamic value) => OAuthTokenEndpointAuthMethodTypeTransformer().decode(value);
static List<OAuthTokenEndpointAuthMethod> listFromJson(dynamic json, {bool growable = false,}) {
final result = <OAuthTokenEndpointAuthMethod>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = OAuthTokenEndpointAuthMethod.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [OAuthTokenEndpointAuthMethod] to String,
/// and [decode] dynamic data back to [OAuthTokenEndpointAuthMethod].
class OAuthTokenEndpointAuthMethodTypeTransformer {
factory OAuthTokenEndpointAuthMethodTypeTransformer() => _instance ??= const OAuthTokenEndpointAuthMethodTypeTransformer._();
const OAuthTokenEndpointAuthMethodTypeTransformer._();
String encode(OAuthTokenEndpointAuthMethod data) => data.value;
/// Decodes a [dynamic value][data] to a OAuthTokenEndpointAuthMethod.
///
/// 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.
OAuthTokenEndpointAuthMethod? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'client_secret_post': return OAuthTokenEndpointAuthMethod.post;
case r'client_secret_basic': return OAuthTokenEndpointAuthMethod.basic;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [OAuthTokenEndpointAuthMethodTypeTransformer] instance.
static OAuthTokenEndpointAuthMethodTypeTransformer? _instance;
}
+12
View File
@@ -66,6 +66,10 @@ class Permission {
static const memoryPeriodRead = Permission._(r'memory.read'); static const memoryPeriodRead = Permission._(r'memory.read');
static const memoryPeriodUpdate = Permission._(r'memory.update'); static const memoryPeriodUpdate = Permission._(r'memory.update');
static const memoryPeriodDelete = Permission._(r'memory.delete'); static const memoryPeriodDelete = Permission._(r'memory.delete');
static const notificationPeriodCreate = Permission._(r'notification.create');
static const notificationPeriodRead = Permission._(r'notification.read');
static const notificationPeriodUpdate = Permission._(r'notification.update');
static const notificationPeriodDelete = Permission._(r'notification.delete');
static const partnerPeriodCreate = Permission._(r'partner.create'); static const partnerPeriodCreate = Permission._(r'partner.create');
static const partnerPeriodRead = Permission._(r'partner.read'); static const partnerPeriodRead = Permission._(r'partner.read');
static const partnerPeriodUpdate = Permission._(r'partner.update'); static const partnerPeriodUpdate = Permission._(r'partner.update');
@@ -147,6 +151,10 @@ class Permission {
memoryPeriodRead, memoryPeriodRead,
memoryPeriodUpdate, memoryPeriodUpdate,
memoryPeriodDelete, memoryPeriodDelete,
notificationPeriodCreate,
notificationPeriodRead,
notificationPeriodUpdate,
notificationPeriodDelete,
partnerPeriodCreate, partnerPeriodCreate,
partnerPeriodRead, partnerPeriodRead,
partnerPeriodUpdate, partnerPeriodUpdate,
@@ -263,6 +271,10 @@ class PermissionTypeTransformer {
case r'memory.read': return Permission.memoryPeriodRead; case r'memory.read': return Permission.memoryPeriodRead;
case r'memory.update': return Permission.memoryPeriodUpdate; case r'memory.update': return Permission.memoryPeriodUpdate;
case r'memory.delete': return Permission.memoryPeriodDelete; case r'memory.delete': return Permission.memoryPeriodDelete;
case r'notification.create': return Permission.notificationPeriodCreate;
case r'notification.read': return Permission.notificationPeriodRead;
case r'notification.update': return Permission.notificationPeriodUpdate;
case r'notification.delete': return Permission.notificationPeriodDelete;
case r'partner.create': return Permission.partnerPeriodCreate; case r'partner.create': return Permission.partnerPeriodCreate;
case r'partner.read': return Permission.partnerPeriodRead; case r'partner.read': return Permission.partnerPeriodRead;
case r'partner.update': return Permission.partnerPeriodUpdate; case r'partner.update': return Permission.partnerPeriodUpdate;
+20 -3
View File
@@ -28,6 +28,8 @@ class SystemConfigOAuthDto {
required this.signingAlgorithm, required this.signingAlgorithm,
required this.storageLabelClaim, required this.storageLabelClaim,
required this.storageQuotaClaim, required this.storageQuotaClaim,
required this.timeout,
required this.tokenEndpointAuthMethod,
}); });
bool autoLaunch; bool autoLaunch;
@@ -61,6 +63,11 @@ class SystemConfigOAuthDto {
String storageQuotaClaim; String storageQuotaClaim;
/// Minimum value: 1
int timeout;
OAuthTokenEndpointAuthMethod tokenEndpointAuthMethod;
@override @override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigOAuthDto && bool operator ==(Object other) => identical(this, other) || other is SystemConfigOAuthDto &&
other.autoLaunch == autoLaunch && other.autoLaunch == autoLaunch &&
@@ -77,7 +84,9 @@ class SystemConfigOAuthDto {
other.scope == scope && other.scope == scope &&
other.signingAlgorithm == signingAlgorithm && other.signingAlgorithm == signingAlgorithm &&
other.storageLabelClaim == storageLabelClaim && other.storageLabelClaim == storageLabelClaim &&
other.storageQuotaClaim == storageQuotaClaim; other.storageQuotaClaim == storageQuotaClaim &&
other.timeout == timeout &&
other.tokenEndpointAuthMethod == tokenEndpointAuthMethod;
@override @override
int get hashCode => int get hashCode =>
@@ -96,10 +105,12 @@ class SystemConfigOAuthDto {
(scope.hashCode) + (scope.hashCode) +
(signingAlgorithm.hashCode) + (signingAlgorithm.hashCode) +
(storageLabelClaim.hashCode) + (storageLabelClaim.hashCode) +
(storageQuotaClaim.hashCode); (storageQuotaClaim.hashCode) +
(timeout.hashCode) +
(tokenEndpointAuthMethod.hashCode);
@override @override
String toString() => 'SystemConfigOAuthDto[autoLaunch=$autoLaunch, autoRegister=$autoRegister, buttonText=$buttonText, clientId=$clientId, clientSecret=$clientSecret, defaultStorageQuota=$defaultStorageQuota, enabled=$enabled, issuerUrl=$issuerUrl, mobileOverrideEnabled=$mobileOverrideEnabled, mobileRedirectUri=$mobileRedirectUri, profileSigningAlgorithm=$profileSigningAlgorithm, scope=$scope, signingAlgorithm=$signingAlgorithm, storageLabelClaim=$storageLabelClaim, storageQuotaClaim=$storageQuotaClaim]'; String toString() => 'SystemConfigOAuthDto[autoLaunch=$autoLaunch, autoRegister=$autoRegister, buttonText=$buttonText, clientId=$clientId, clientSecret=$clientSecret, defaultStorageQuota=$defaultStorageQuota, enabled=$enabled, issuerUrl=$issuerUrl, mobileOverrideEnabled=$mobileOverrideEnabled, mobileRedirectUri=$mobileRedirectUri, profileSigningAlgorithm=$profileSigningAlgorithm, scope=$scope, signingAlgorithm=$signingAlgorithm, storageLabelClaim=$storageLabelClaim, storageQuotaClaim=$storageQuotaClaim, timeout=$timeout, tokenEndpointAuthMethod=$tokenEndpointAuthMethod]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -118,6 +129,8 @@ class SystemConfigOAuthDto {
json[r'signingAlgorithm'] = this.signingAlgorithm; json[r'signingAlgorithm'] = this.signingAlgorithm;
json[r'storageLabelClaim'] = this.storageLabelClaim; json[r'storageLabelClaim'] = this.storageLabelClaim;
json[r'storageQuotaClaim'] = this.storageQuotaClaim; json[r'storageQuotaClaim'] = this.storageQuotaClaim;
json[r'timeout'] = this.timeout;
json[r'tokenEndpointAuthMethod'] = this.tokenEndpointAuthMethod;
return json; return json;
} }
@@ -145,6 +158,8 @@ class SystemConfigOAuthDto {
signingAlgorithm: mapValueOfType<String>(json, r'signingAlgorithm')!, signingAlgorithm: mapValueOfType<String>(json, r'signingAlgorithm')!,
storageLabelClaim: mapValueOfType<String>(json, r'storageLabelClaim')!, storageLabelClaim: mapValueOfType<String>(json, r'storageLabelClaim')!,
storageQuotaClaim: mapValueOfType<String>(json, r'storageQuotaClaim')!, storageQuotaClaim: mapValueOfType<String>(json, r'storageQuotaClaim')!,
timeout: mapValueOfType<int>(json, r'timeout')!,
tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.fromJson(json[r'tokenEndpointAuthMethod'])!,
); );
} }
return null; return null;
@@ -207,6 +222,8 @@ class SystemConfigOAuthDto {
'signingAlgorithm', 'signingAlgorithm',
'storageLabelClaim', 'storageLabelClaim',
'storageQuotaClaim', 'storageQuotaClaim',
'timeout',
'tokenEndpointAuthMethod',
}; };
} }
+12 -1
View File
@@ -13,6 +13,7 @@ part of openapi.api;
class UserAdminCreateDto { class UserAdminCreateDto {
/// Returns a new [UserAdminCreateDto] instance. /// Returns a new [UserAdminCreateDto] instance.
UserAdminCreateDto({ UserAdminCreateDto({
this.avatarColor,
required this.email, required this.email,
required this.name, required this.name,
this.notify, this.notify,
@@ -22,6 +23,8 @@ class UserAdminCreateDto {
this.storageLabel, this.storageLabel,
}); });
UserAvatarColor? avatarColor;
String email; String email;
String name; String name;
@@ -51,6 +54,7 @@ class UserAdminCreateDto {
@override @override
bool operator ==(Object other) => identical(this, other) || other is UserAdminCreateDto && bool operator ==(Object other) => identical(this, other) || other is UserAdminCreateDto &&
other.avatarColor == avatarColor &&
other.email == email && other.email == email &&
other.name == name && other.name == name &&
other.notify == notify && other.notify == notify &&
@@ -62,6 +66,7 @@ class UserAdminCreateDto {
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(avatarColor == null ? 0 : avatarColor!.hashCode) +
(email.hashCode) + (email.hashCode) +
(name.hashCode) + (name.hashCode) +
(notify == null ? 0 : notify!.hashCode) + (notify == null ? 0 : notify!.hashCode) +
@@ -71,10 +76,15 @@ class UserAdminCreateDto {
(storageLabel == null ? 0 : storageLabel!.hashCode); (storageLabel == null ? 0 : storageLabel!.hashCode);
@override @override
String toString() => 'UserAdminCreateDto[email=$email, name=$name, notify=$notify, password=$password, quotaSizeInBytes=$quotaSizeInBytes, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]'; String toString() => 'UserAdminCreateDto[avatarColor=$avatarColor, email=$email, name=$name, notify=$notify, password=$password, quotaSizeInBytes=$quotaSizeInBytes, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
if (this.avatarColor != null) {
json[r'avatarColor'] = this.avatarColor;
} else {
// json[r'avatarColor'] = null;
}
json[r'email'] = this.email; json[r'email'] = this.email;
json[r'name'] = this.name; json[r'name'] = this.name;
if (this.notify != null) { if (this.notify != null) {
@@ -110,6 +120,7 @@ class UserAdminCreateDto {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return UserAdminCreateDto( return UserAdminCreateDto(
avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']),
email: mapValueOfType<String>(json, r'email')!, email: mapValueOfType<String>(json, r'email')!,
name: mapValueOfType<String>(json, r'name')!, name: mapValueOfType<String>(json, r'name')!,
notify: mapValueOfType<bool>(json, r'notify'), notify: mapValueOfType<bool>(json, r'notify'),
+12 -1
View File
@@ -13,6 +13,7 @@ part of openapi.api;
class UserAdminUpdateDto { class UserAdminUpdateDto {
/// Returns a new [UserAdminUpdateDto] instance. /// Returns a new [UserAdminUpdateDto] instance.
UserAdminUpdateDto({ UserAdminUpdateDto({
this.avatarColor,
this.email, this.email,
this.name, this.name,
this.password, this.password,
@@ -21,6 +22,8 @@ class UserAdminUpdateDto {
this.storageLabel, this.storageLabel,
}); });
UserAvatarColor? avatarColor;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated /// does not include a default value (using the "default:" property), however, the generated
@@ -60,6 +63,7 @@ class UserAdminUpdateDto {
@override @override
bool operator ==(Object other) => identical(this, other) || other is UserAdminUpdateDto && bool operator ==(Object other) => identical(this, other) || other is UserAdminUpdateDto &&
other.avatarColor == avatarColor &&
other.email == email && other.email == email &&
other.name == name && other.name == name &&
other.password == password && other.password == password &&
@@ -70,6 +74,7 @@ class UserAdminUpdateDto {
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(avatarColor == null ? 0 : avatarColor!.hashCode) +
(email == null ? 0 : email!.hashCode) + (email == null ? 0 : email!.hashCode) +
(name == null ? 0 : name!.hashCode) + (name == null ? 0 : name!.hashCode) +
(password == null ? 0 : password!.hashCode) + (password == null ? 0 : password!.hashCode) +
@@ -78,10 +83,15 @@ class UserAdminUpdateDto {
(storageLabel == null ? 0 : storageLabel!.hashCode); (storageLabel == null ? 0 : storageLabel!.hashCode);
@override @override
String toString() => 'UserAdminUpdateDto[email=$email, name=$name, password=$password, quotaSizeInBytes=$quotaSizeInBytes, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]'; String toString() => 'UserAdminUpdateDto[avatarColor=$avatarColor, email=$email, name=$name, password=$password, quotaSizeInBytes=$quotaSizeInBytes, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
if (this.avatarColor != null) {
json[r'avatarColor'] = this.avatarColor;
} else {
// json[r'avatarColor'] = null;
}
if (this.email != null) { if (this.email != null) {
json[r'email'] = this.email; json[r'email'] = this.email;
} else { } else {
@@ -124,6 +134,7 @@ class UserAdminUpdateDto {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return UserAdminUpdateDto( return UserAdminUpdateDto(
avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']),
email: mapValueOfType<String>(json, r'email'), email: mapValueOfType<String>(json, r'email'),
name: mapValueOfType<String>(json, r'name'), name: mapValueOfType<String>(json, r'name'),
password: mapValueOfType<String>(json, r'password'), password: mapValueOfType<String>(json, r'password'),
+1 -9
View File
@@ -13,7 +13,6 @@ part of openapi.api;
class UserPreferencesResponseDto { class UserPreferencesResponseDto {
/// Returns a new [UserPreferencesResponseDto] instance. /// Returns a new [UserPreferencesResponseDto] instance.
UserPreferencesResponseDto({ UserPreferencesResponseDto({
required this.avatar,
required this.download, required this.download,
required this.emailNotifications, required this.emailNotifications,
required this.folders, required this.folders,
@@ -25,8 +24,6 @@ class UserPreferencesResponseDto {
required this.tags, required this.tags,
}); });
AvatarResponse avatar;
DownloadResponse download; DownloadResponse download;
EmailNotificationsResponse emailNotifications; EmailNotificationsResponse emailNotifications;
@@ -47,7 +44,6 @@ class UserPreferencesResponseDto {
@override @override
bool operator ==(Object other) => identical(this, other) || other is UserPreferencesResponseDto && bool operator ==(Object other) => identical(this, other) || other is UserPreferencesResponseDto &&
other.avatar == avatar &&
other.download == download && other.download == download &&
other.emailNotifications == emailNotifications && other.emailNotifications == emailNotifications &&
other.folders == folders && other.folders == folders &&
@@ -61,7 +57,6 @@ class UserPreferencesResponseDto {
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(avatar.hashCode) +
(download.hashCode) + (download.hashCode) +
(emailNotifications.hashCode) + (emailNotifications.hashCode) +
(folders.hashCode) + (folders.hashCode) +
@@ -73,11 +68,10 @@ class UserPreferencesResponseDto {
(tags.hashCode); (tags.hashCode);
@override @override
String toString() => 'UserPreferencesResponseDto[avatar=$avatar, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, sharedLinks=$sharedLinks, tags=$tags]'; String toString() => 'UserPreferencesResponseDto[download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, sharedLinks=$sharedLinks, tags=$tags]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json[r'avatar'] = this.avatar;
json[r'download'] = this.download; json[r'download'] = this.download;
json[r'emailNotifications'] = this.emailNotifications; json[r'emailNotifications'] = this.emailNotifications;
json[r'folders'] = this.folders; json[r'folders'] = this.folders;
@@ -99,7 +93,6 @@ class UserPreferencesResponseDto {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return UserPreferencesResponseDto( return UserPreferencesResponseDto(
avatar: AvatarResponse.fromJson(json[r'avatar'])!,
download: DownloadResponse.fromJson(json[r'download'])!, download: DownloadResponse.fromJson(json[r'download'])!,
emailNotifications: EmailNotificationsResponse.fromJson(json[r'emailNotifications'])!, emailNotifications: EmailNotificationsResponse.fromJson(json[r'emailNotifications'])!,
folders: FoldersResponse.fromJson(json[r'folders'])!, folders: FoldersResponse.fromJson(json[r'folders'])!,
@@ -156,7 +149,6 @@ class UserPreferencesResponseDto {
/// The list of required keys that must be present in a JSON. /// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{ static const requiredKeys = <String>{
'avatar',
'download', 'download',
'emailNotifications', 'emailNotifications',
'folders', 'folders',
+12 -1
View File
@@ -13,11 +13,14 @@ part of openapi.api;
class UserUpdateMeDto { class UserUpdateMeDto {
/// Returns a new [UserUpdateMeDto] instance. /// Returns a new [UserUpdateMeDto] instance.
UserUpdateMeDto({ UserUpdateMeDto({
this.avatarColor,
this.email, this.email,
this.name, this.name,
this.password, this.password,
}); });
UserAvatarColor? avatarColor;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated /// does not include a default value (using the "default:" property), however, the generated
@@ -44,6 +47,7 @@ class UserUpdateMeDto {
@override @override
bool operator ==(Object other) => identical(this, other) || other is UserUpdateMeDto && bool operator ==(Object other) => identical(this, other) || other is UserUpdateMeDto &&
other.avatarColor == avatarColor &&
other.email == email && other.email == email &&
other.name == name && other.name == name &&
other.password == password; other.password == password;
@@ -51,15 +55,21 @@ class UserUpdateMeDto {
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(avatarColor == null ? 0 : avatarColor!.hashCode) +
(email == null ? 0 : email!.hashCode) + (email == null ? 0 : email!.hashCode) +
(name == null ? 0 : name!.hashCode) + (name == null ? 0 : name!.hashCode) +
(password == null ? 0 : password!.hashCode); (password == null ? 0 : password!.hashCode);
@override @override
String toString() => 'UserUpdateMeDto[email=$email, name=$name, password=$password]'; String toString() => 'UserUpdateMeDto[avatarColor=$avatarColor, email=$email, name=$name, password=$password]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
if (this.avatarColor != null) {
json[r'avatarColor'] = this.avatarColor;
} else {
// json[r'avatarColor'] = null;
}
if (this.email != null) { if (this.email != null) {
json[r'email'] = this.email; json[r'email'] = this.email;
} else { } else {
@@ -87,6 +97,7 @@ class UserUpdateMeDto {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return UserUpdateMeDto( return UserUpdateMeDto(
avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']),
email: mapValueOfType<String>(json, r'email'), email: mapValueOfType<String>(json, r'email'),
name: mapValueOfType<String>(json, r'name'), name: mapValueOfType<String>(json, r'name'),
password: mapValueOfType<String>(json, r'password'), password: mapValueOfType<String>(json, r'password'),
+1 -1
View File
@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none' publish_to: 'none'
version: 1.132.2+196 version: 1.132.3+197
environment: environment:
sdk: '>=3.3.0 <4.0.0' sdk: '>=3.3.0 <4.0.0'
+552 -70
View File
@@ -206,6 +206,141 @@
] ]
} }
}, },
"/admin/notifications": {
"post": {
"operationId": "createNotification",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationCreateDto"
}
}
},
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Notifications (Admin)"
]
}
},
"/admin/notifications/templates/{name}": {
"post": {
"operationId": "getNotificationTemplateAdmin",
"parameters": [
{
"name": "name",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TemplateDto"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TemplateResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Notifications (Admin)"
]
}
},
"/admin/notifications/test-email": {
"post": {
"operationId": "sendTestEmailAdmin",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SystemConfigSmtpDto"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TestEmailResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Notifications (Admin)"
]
}
},
"/admin/users": { "/admin/users": {
"get": { "get": {
"operationId": "searchUsersAdmin", "operationId": "searchUsersAdmin",
@@ -3485,15 +3620,224 @@
] ]
} }
}, },
"/notifications/admin/templates/{name}": { "/notifications": {
"post": { "delete": {
"operationId": "getNotificationTemplateAdmin", "operationId": "deleteNotifications",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationDeleteAllDto"
}
}
},
"required": true
},
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Notifications"
]
},
"get": {
"operationId": "getNotifications",
"parameters": [ "parameters": [
{ {
"name": "name", "name": "id",
"required": false,
"in": "query",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "level",
"required": false,
"in": "query",
"schema": {
"$ref": "#/components/schemas/NotificationLevel"
}
},
{
"name": "type",
"required": false,
"in": "query",
"schema": {
"$ref": "#/components/schemas/NotificationType"
}
},
{
"name": "unread",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/NotificationDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Notifications"
]
},
"put": {
"operationId": "updateNotifications",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationUpdateAllDto"
}
}
},
"required": true
},
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Notifications"
]
}
},
"/notifications/{id}": {
"delete": {
"operationId": "deleteNotification",
"parameters": [
{
"name": "id",
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Notifications"
]
},
"get": {
"operationId": "getNotification",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Notifications"
]
},
"put": {
"operationId": "updateNotification",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string" "type": "string"
} }
} }
@@ -3502,7 +3846,7 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/TemplateDto" "$ref": "#/components/schemas/NotificationUpdateDto"
} }
} }
}, },
@@ -3513,7 +3857,7 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/TemplateResponseDto" "$ref": "#/components/schemas/NotificationDto"
} }
} }
}, },
@@ -3532,49 +3876,7 @@
} }
], ],
"tags": [ "tags": [
"Notifications (Admin)" "Notifications"
]
}
},
"/notifications/admin/test-email": {
"post": {
"operationId": "sendTestEmailAdmin",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SystemConfigSmtpDto"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TestEmailResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Notifications (Admin)"
] ]
} }
}, },
@@ -7656,7 +7958,7 @@
"info": { "info": {
"title": "Immich", "title": "Immich",
"description": "Immich API", "description": "Immich API",
"version": "1.132.2", "version": "1.132.3",
"contact": {} "contact": {}
}, },
"tags": [], "tags": [],
@@ -8884,21 +9186,6 @@
], ],
"type": "string" "type": "string"
}, },
"AvatarResponse": {
"properties": {
"color": {
"allOf": [
{
"$ref": "#/components/schemas/UserAvatarColor"
}
]
}
},
"required": [
"color"
],
"type": "object"
},
"AvatarUpdate": { "AvatarUpdate": {
"properties": { "properties": {
"color": { "color": {
@@ -10341,6 +10628,157 @@
}, },
"type": "object" "type": "object"
}, },
"NotificationCreateDto": {
"properties": {
"data": {
"type": "object"
},
"description": {
"nullable": true,
"type": "string"
},
"level": {
"allOf": [
{
"$ref": "#/components/schemas/NotificationLevel"
}
]
},
"readAt": {
"format": "date-time",
"nullable": true,
"type": "string"
},
"title": {
"type": "string"
},
"type": {
"allOf": [
{
"$ref": "#/components/schemas/NotificationType"
}
]
},
"userId": {
"format": "uuid",
"type": "string"
}
},
"required": [
"title",
"userId"
],
"type": "object"
},
"NotificationDeleteAllDto": {
"properties": {
"ids": {
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
}
},
"required": [
"ids"
],
"type": "object"
},
"NotificationDto": {
"properties": {
"createdAt": {
"format": "date-time",
"type": "string"
},
"data": {
"type": "object"
},
"description": {
"type": "string"
},
"id": {
"type": "string"
},
"level": {
"allOf": [
{
"$ref": "#/components/schemas/NotificationLevel"
}
]
},
"readAt": {
"format": "date-time",
"type": "string"
},
"title": {
"type": "string"
},
"type": {
"allOf": [
{
"$ref": "#/components/schemas/NotificationType"
}
]
}
},
"required": [
"createdAt",
"id",
"level",
"title",
"type"
],
"type": "object"
},
"NotificationLevel": {
"enum": [
"success",
"error",
"warning",
"info"
],
"type": "string"
},
"NotificationType": {
"enum": [
"JobFailed",
"BackupFailed",
"SystemMessage",
"Custom"
],
"type": "string"
},
"NotificationUpdateAllDto": {
"properties": {
"ids": {
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
},
"readAt": {
"format": "date-time",
"nullable": true,
"type": "string"
}
},
"required": [
"ids"
],
"type": "object"
},
"NotificationUpdateDto": {
"properties": {
"readAt": {
"format": "date-time",
"nullable": true,
"type": "string"
}
},
"type": "object"
},
"OAuthAuthorizeResponseDto": { "OAuthAuthorizeResponseDto": {
"properties": { "properties": {
"url": { "url": {
@@ -10386,6 +10824,13 @@
], ],
"type": "object" "type": "object"
}, },
"OAuthTokenEndpointAuthMethod": {
"enum": [
"client_secret_post",
"client_secret_basic"
],
"type": "string"
},
"OnThisDayDto": { "OnThisDayDto": {
"properties": { "properties": {
"year": { "year": {
@@ -10615,6 +11060,10 @@
"memory.read", "memory.read",
"memory.update", "memory.update",
"memory.delete", "memory.delete",
"notification.create",
"notification.read",
"notification.update",
"notification.delete",
"partner.create", "partner.create",
"partner.read", "partner.read",
"partner.update", "partner.update",
@@ -12962,6 +13411,17 @@
}, },
"storageQuotaClaim": { "storageQuotaClaim": {
"type": "string" "type": "string"
},
"timeout": {
"minimum": 1,
"type": "integer"
},
"tokenEndpointAuthMethod": {
"allOf": [
{
"$ref": "#/components/schemas/OAuthTokenEndpointAuthMethod"
}
]
} }
}, },
"required": [ "required": [
@@ -12979,7 +13439,9 @@
"scope", "scope",
"signingAlgorithm", "signingAlgorithm",
"storageLabelClaim", "storageLabelClaim",
"storageQuotaClaim" "storageQuotaClaim",
"timeout",
"tokenEndpointAuthMethod"
], ],
"type": "object" "type": "object"
}, },
@@ -13621,6 +14083,14 @@
}, },
"UserAdminCreateDto": { "UserAdminCreateDto": {
"properties": { "properties": {
"avatarColor": {
"allOf": [
{
"$ref": "#/components/schemas/UserAvatarColor"
}
],
"nullable": true
},
"email": { "email": {
"format": "email", "format": "email",
"type": "string" "type": "string"
@@ -13763,6 +14233,14 @@
}, },
"UserAdminUpdateDto": { "UserAdminUpdateDto": {
"properties": { "properties": {
"avatarColor": {
"allOf": [
{
"$ref": "#/components/schemas/UserAvatarColor"
}
],
"nullable": true
},
"email": { "email": {
"format": "email", "format": "email",
"type": "string" "type": "string"
@@ -13826,9 +14304,6 @@
}, },
"UserPreferencesResponseDto": { "UserPreferencesResponseDto": {
"properties": { "properties": {
"avatar": {
"$ref": "#/components/schemas/AvatarResponse"
},
"download": { "download": {
"$ref": "#/components/schemas/DownloadResponse" "$ref": "#/components/schemas/DownloadResponse"
}, },
@@ -13858,7 +14333,6 @@
} }
}, },
"required": [ "required": [
"avatar",
"download", "download",
"emailNotifications", "emailNotifications",
"folders", "folders",
@@ -13952,6 +14426,14 @@
}, },
"UserUpdateMeDto": { "UserUpdateMeDto": {
"properties": { "properties": {
"avatarColor": {
"allOf": [
{
"$ref": "#/components/schemas/UserAvatarColor"
}
],
"nullable": true
},
"email": { "email": {
"format": "email", "format": "email",
"type": "string" "type": "string"
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.132.2", "version": "1.132.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.132.2", "version": "1.132.3",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.132.2", "version": "1.132.3",
"description": "Auto-generated TypeScript SDK for the Immich API", "description": "Auto-generated TypeScript SDK for the Immich API",
"type": "module", "type": "module",
"main": "./build/index.js", "main": "./build/index.js",
+169 -40
View File
@@ -1,6 +1,6 @@
/** /**
* Immich * Immich
* 1.132.2 * 1.132.3
* DO NOT MODIFY - This file has been generated using oazapfts. * DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts * See https://www.npmjs.com/package/oazapfts
*/ */
@@ -39,6 +39,48 @@ export type ActivityCreateDto = {
export type ActivityStatisticsResponseDto = { export type ActivityStatisticsResponseDto = {
comments: number; comments: number;
}; };
export type NotificationCreateDto = {
data?: object;
description?: string | null;
level?: NotificationLevel;
readAt?: string | null;
title: string;
"type"?: NotificationType;
userId: string;
};
export type NotificationDto = {
createdAt: string;
data?: object;
description?: string;
id: string;
level: NotificationLevel;
readAt?: string;
title: string;
"type": NotificationType;
};
export type TemplateDto = {
template: string;
};
export type TemplateResponseDto = {
html: string;
name: string;
};
export type SystemConfigSmtpTransportDto = {
host: string;
ignoreCert: boolean;
password: string;
port: number;
username: string;
};
export type SystemConfigSmtpDto = {
enabled: boolean;
"from": string;
replyTo: string;
transport: SystemConfigSmtpTransportDto;
};
export type TestEmailResponseDto = {
messageId: string;
};
export type UserLicense = { export type UserLicense = {
activatedAt: string; activatedAt: string;
activationKey: string; activationKey: string;
@@ -64,6 +106,7 @@ export type UserAdminResponseDto = {
updatedAt: string; updatedAt: string;
}; };
export type UserAdminCreateDto = { export type UserAdminCreateDto = {
avatarColor?: (UserAvatarColor) | null;
email: string; email: string;
name: string; name: string;
notify?: boolean; notify?: boolean;
@@ -76,6 +119,7 @@ export type UserAdminDeleteDto = {
force?: boolean; force?: boolean;
}; };
export type UserAdminUpdateDto = { export type UserAdminUpdateDto = {
avatarColor?: (UserAvatarColor) | null;
email?: string; email?: string;
name?: string; name?: string;
password?: string; password?: string;
@@ -83,9 +127,6 @@ export type UserAdminUpdateDto = {
shouldChangePassword?: boolean; shouldChangePassword?: boolean;
storageLabel?: string | null; storageLabel?: string | null;
}; };
export type AvatarResponse = {
color: UserAvatarColor;
};
export type DownloadResponse = { export type DownloadResponse = {
archiveSize: number; archiveSize: number;
includeEmbeddedVideos: boolean; includeEmbeddedVideos: boolean;
@@ -122,7 +163,6 @@ export type TagsResponse = {
sidebarWeb: boolean; sidebarWeb: boolean;
}; };
export type UserPreferencesResponseDto = { export type UserPreferencesResponseDto = {
avatar: AvatarResponse;
download: DownloadResponse; download: DownloadResponse;
emailNotifications: EmailNotificationsResponse; emailNotifications: EmailNotificationsResponse;
folders: FoldersResponse; folders: FoldersResponse;
@@ -663,28 +703,15 @@ export type MemoryUpdateDto = {
memoryAt?: string; memoryAt?: string;
seenAt?: string; seenAt?: string;
}; };
export type TemplateDto = { export type NotificationDeleteAllDto = {
template: string; ids: string[];
}; };
export type TemplateResponseDto = { export type NotificationUpdateAllDto = {
html: string; ids: string[];
name: string; readAt?: string | null;
}; };
export type SystemConfigSmtpTransportDto = { export type NotificationUpdateDto = {
host: string; readAt?: string | null;
ignoreCert: boolean;
password: string;
port: number;
username: string;
};
export type SystemConfigSmtpDto = {
enabled: boolean;
"from": string;
replyTo: string;
transport: SystemConfigSmtpTransportDto;
};
export type TestEmailResponseDto = {
messageId: string;
}; };
export type OAuthConfigDto = { export type OAuthConfigDto = {
codeChallenge?: string; codeChallenge?: string;
@@ -1288,6 +1315,8 @@ export type SystemConfigOAuthDto = {
signingAlgorithm: string; signingAlgorithm: string;
storageLabelClaim: string; storageLabelClaim: string;
storageQuotaClaim: string; storageQuotaClaim: string;
timeout: number;
tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod;
}; };
export type SystemConfigPasswordLoginDto = { export type SystemConfigPasswordLoginDto = {
enabled: boolean; enabled: boolean;
@@ -1388,6 +1417,7 @@ export type TrashResponseDto = {
count: number; count: number;
}; };
export type UserUpdateMeDto = { export type UserUpdateMeDto = {
avatarColor?: (UserAvatarColor) | null;
email?: string; email?: string;
name?: string; name?: string;
password?: string; password?: string;
@@ -1454,6 +1484,43 @@ export function deleteActivity({ id }: {
method: "DELETE" method: "DELETE"
})); }));
} }
export function createNotification({ notificationCreateDto }: {
notificationCreateDto: NotificationCreateDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: NotificationDto;
}>("/admin/notifications", oazapfts.json({
...opts,
method: "POST",
body: notificationCreateDto
})));
}
export function getNotificationTemplateAdmin({ name, templateDto }: {
name: string;
templateDto: TemplateDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: TemplateResponseDto;
}>(`/admin/notifications/templates/${encodeURIComponent(name)}`, oazapfts.json({
...opts,
method: "POST",
body: templateDto
})));
}
export function sendTestEmailAdmin({ systemConfigSmtpDto }: {
systemConfigSmtpDto: SystemConfigSmtpDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: TestEmailResponseDto;
}>("/admin/notifications/test-email", oazapfts.json({
...opts,
method: "POST",
body: systemConfigSmtpDto
})));
}
export function searchUsersAdmin({ withDeleted }: { export function searchUsersAdmin({ withDeleted }: {
withDeleted?: boolean; withDeleted?: boolean;
}, opts?: Oazapfts.RequestOpts) { }, opts?: Oazapfts.RequestOpts) {
@@ -2322,29 +2389,71 @@ export function addMemoryAssets({ id, bulkIdsDto }: {
body: bulkIdsDto body: bulkIdsDto
}))); })));
} }
export function getNotificationTemplateAdmin({ name, templateDto }: { export function deleteNotifications({ notificationDeleteAllDto }: {
name: string; notificationDeleteAllDto: NotificationDeleteAllDto;
templateDto: TemplateDto;
}, opts?: Oazapfts.RequestOpts) { }, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{ return oazapfts.ok(oazapfts.fetchText("/notifications", oazapfts.json({
status: 200;
data: TemplateResponseDto;
}>(`/notifications/admin/templates/${encodeURIComponent(name)}`, oazapfts.json({
...opts, ...opts,
method: "POST", method: "DELETE",
body: templateDto body: notificationDeleteAllDto
}))); })));
} }
export function sendTestEmailAdmin({ systemConfigSmtpDto }: { export function getNotifications({ id, level, $type, unread }: {
systemConfigSmtpDto: SystemConfigSmtpDto; id?: string;
level?: NotificationLevel;
$type?: NotificationType;
unread?: boolean;
}, opts?: Oazapfts.RequestOpts) { }, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{ return oazapfts.ok(oazapfts.fetchJson<{
status: 200; status: 200;
data: TestEmailResponseDto; data: NotificationDto[];
}>("/notifications/admin/test-email", oazapfts.json({ }>(`/notifications${QS.query(QS.explode({
id,
level,
"type": $type,
unread
}))}`, {
...opts
}));
}
export function updateNotifications({ notificationUpdateAllDto }: {
notificationUpdateAllDto: NotificationUpdateAllDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText("/notifications", oazapfts.json({
...opts, ...opts,
method: "POST", method: "PUT",
body: systemConfigSmtpDto body: notificationUpdateAllDto
})));
}
export function deleteNotification({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/notifications/${encodeURIComponent(id)}`, {
...opts,
method: "DELETE"
}));
}
export function getNotification({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: NotificationDto;
}>(`/notifications/${encodeURIComponent(id)}`, {
...opts
}));
}
export function updateNotification({ id, notificationUpdateDto }: {
id: string;
notificationUpdateDto: NotificationUpdateDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: NotificationDto;
}>(`/notifications/${encodeURIComponent(id)}`, oazapfts.json({
...opts,
method: "PUT",
body: notificationUpdateDto
}))); })));
} }
export function startOAuth({ oAuthConfigDto }: { export function startOAuth({ oAuthConfigDto }: {
@@ -3453,6 +3562,18 @@ export enum UserAvatarColor {
Gray = "gray", Gray = "gray",
Amber = "amber" Amber = "amber"
} }
export enum NotificationLevel {
Success = "success",
Error = "error",
Warning = "warning",
Info = "info"
}
export enum NotificationType {
JobFailed = "JobFailed",
BackupFailed = "BackupFailed",
SystemMessage = "SystemMessage",
Custom = "Custom"
}
export enum UserStatus { export enum UserStatus {
Active = "active", Active = "active",
Removing = "removing", Removing = "removing",
@@ -3527,6 +3648,10 @@ export enum Permission {
MemoryRead = "memory.read", MemoryRead = "memory.read",
MemoryUpdate = "memory.update", MemoryUpdate = "memory.update",
MemoryDelete = "memory.delete", MemoryDelete = "memory.delete",
NotificationCreate = "notification.create",
NotificationRead = "notification.read",
NotificationUpdate = "notification.update",
NotificationDelete = "notification.delete",
PartnerCreate = "partner.create", PartnerCreate = "partner.create",
PartnerRead = "partner.read", PartnerRead = "partner.read",
PartnerUpdate = "partner.update", PartnerUpdate = "partner.update",
@@ -3736,6 +3861,10 @@ export enum LogLevel {
Error = "error", Error = "error",
Fatal = "fatal" Fatal = "fatal"
} }
export enum OAuthTokenEndpointAuthMethod {
ClientSecretPost = "client_secret_post",
ClientSecretBasic = "client_secret_basic"
}
export enum TimeBucketSize { export enum TimeBucketSize {
Day = "DAY", Day = "DAY",
Month = "MONTH" Month = "MONTH"
+1 -1
View File
@@ -26,7 +26,7 @@ COPY --from=dev /usr/src/app/node_modules/@img ./node_modules/@img
COPY --from=dev /usr/src/app/node_modules/exiftool-vendored.pl ./node_modules/exiftool-vendored.pl COPY --from=dev /usr/src/app/node_modules/exiftool-vendored.pl ./node_modules/exiftool-vendored.pl
# web build # web build
FROM node:22.14.0-alpine3.20@sha256:40be979442621049f40b1d51a26b55e281246b5de4e5f51a18da7beb6e17e3f9 AS web FROM node:22.15.0-alpine3.20@sha256:686b8892b69879ef5bfd6047589666933508f9a5451c67320df3070ba0e9807b AS web
WORKDIR /usr/src/open-api/typescript-sdk WORKDIR /usr/src/open-api/typescript-sdk
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
+425 -223
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich", "name": "immich",
"version": "1.132.2", "version": "1.132.3",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -57,6 +57,7 @@
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"compression": "^1.8.0",
"cookie": "^1.0.2", "cookie": "^1.0.2",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"exiftool-vendored": "^28.3.1", "exiftool-vendored": "^28.3.1",
@@ -108,6 +109,7 @@
"@types/archiver": "^6.0.0", "@types/archiver": "^6.0.0",
"@types/async-lock": "^1.4.2", "@types/async-lock": "^1.4.2",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/compression": "^1.7.5",
"@types/cookie-parser": "^1.4.8", "@types/cookie-parser": "^1.4.8",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/fluent-ffmpeg": "^2.1.21", "@types/fluent-ffmpeg": "^2.1.21",
+5
View File
@@ -5,6 +5,7 @@ import {
CQMode, CQMode,
ImageFormat, ImageFormat,
LogLevel, LogLevel,
OAuthTokenEndpointAuthMethod,
QueueName, QueueName,
ToneMapping, ToneMapping,
TranscodeHWAccel, TranscodeHWAccel,
@@ -96,6 +97,8 @@ export interface SystemConfig {
scope: string; scope: string;
signingAlgorithm: string; signingAlgorithm: string;
profileSigningAlgorithm: string; profileSigningAlgorithm: string;
tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod;
timeout: number;
storageLabelClaim: string; storageLabelClaim: string;
storageQuotaClaim: string; storageQuotaClaim: string;
}; };
@@ -260,6 +263,8 @@ export const defaults = Object.freeze<SystemConfig>({
profileSigningAlgorithm: 'none', profileSigningAlgorithm: 'none',
storageLabelClaim: 'preferred_username', storageLabelClaim: 'preferred_username',
storageQuotaClaim: 'immich_quota', storageQuotaClaim: 'immich_quota',
tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.CLIENT_SECRET_POST,
timeout: 30_000,
}, },
passwordLogin: { passwordLogin: {
enabled: true, enabled: true,
+2
View File
@@ -14,6 +14,7 @@ import { LibraryController } from 'src/controllers/library.controller';
import { MapController } from 'src/controllers/map.controller'; import { MapController } from 'src/controllers/map.controller';
import { MemoryController } from 'src/controllers/memory.controller'; import { MemoryController } from 'src/controllers/memory.controller';
import { NotificationAdminController } from 'src/controllers/notification-admin.controller'; import { NotificationAdminController } from 'src/controllers/notification-admin.controller';
import { NotificationController } from 'src/controllers/notification.controller';
import { OAuthController } from 'src/controllers/oauth.controller'; import { OAuthController } from 'src/controllers/oauth.controller';
import { PartnerController } from 'src/controllers/partner.controller'; import { PartnerController } from 'src/controllers/partner.controller';
import { PersonController } from 'src/controllers/person.controller'; import { PersonController } from 'src/controllers/person.controller';
@@ -47,6 +48,7 @@ export const controllers = [
LibraryController, LibraryController,
MapController, MapController,
MemoryController, MemoryController,
NotificationController,
NotificationAdminController, NotificationAdminController,
OAuthController, OAuthController,
PartnerController, PartnerController,
@@ -1,16 +1,28 @@
import { Body, Controller, HttpCode, HttpStatus, Param, Post } from '@nestjs/common'; import { Body, Controller, HttpCode, HttpStatus, Param, Post } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger'; import { ApiTags } from '@nestjs/swagger';
import { AuthDto } from 'src/dtos/auth.dto'; import { AuthDto } from 'src/dtos/auth.dto';
import { TemplateDto, TemplateResponseDto, TestEmailResponseDto } from 'src/dtos/notification.dto'; import {
NotificationCreateDto,
NotificationDto,
TemplateDto,
TemplateResponseDto,
TestEmailResponseDto,
} from 'src/dtos/notification.dto';
import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto'; import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto';
import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { EmailTemplate } from 'src/repositories/email.repository'; import { EmailTemplate } from 'src/repositories/email.repository';
import { NotificationService } from 'src/services/notification.service'; import { NotificationAdminService } from 'src/services/notification-admin.service';
@ApiTags('Notifications (Admin)') @ApiTags('Notifications (Admin)')
@Controller('notifications/admin') @Controller('admin/notifications')
export class NotificationAdminController { export class NotificationAdminController {
constructor(private service: NotificationService) {} constructor(private service: NotificationAdminService) {}
@Post()
@Authenticated({ admin: true })
createNotification(@Auth() auth: AuthDto, @Body() dto: NotificationCreateDto): Promise<NotificationDto> {
return this.service.create(auth, dto);
}
@Post('test-email') @Post('test-email')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@@ -0,0 +1,60 @@
import { Body, Controller, Delete, Get, Param, Put, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AuthDto } from 'src/dtos/auth.dto';
import {
NotificationDeleteAllDto,
NotificationDto,
NotificationSearchDto,
NotificationUpdateAllDto,
NotificationUpdateDto,
} from 'src/dtos/notification.dto';
import { Permission } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { NotificationService } from 'src/services/notification.service';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Notifications')
@Controller('notifications')
export class NotificationController {
constructor(private service: NotificationService) {}
@Get()
@Authenticated({ permission: Permission.NOTIFICATION_READ })
getNotifications(@Auth() auth: AuthDto, @Query() dto: NotificationSearchDto): Promise<NotificationDto[]> {
return this.service.search(auth, dto);
}
@Put()
@Authenticated({ permission: Permission.NOTIFICATION_UPDATE })
updateNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationUpdateAllDto): Promise<void> {
return this.service.updateAll(auth, dto);
}
@Delete()
@Authenticated({ permission: Permission.NOTIFICATION_DELETE })
deleteNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationDeleteAllDto): Promise<void> {
return this.service.deleteAll(auth, dto);
}
@Get(':id')
@Authenticated({ permission: Permission.NOTIFICATION_READ })
getNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<NotificationDto> {
return this.service.get(auth, id);
}
@Put(':id')
@Authenticated({ permission: Permission.NOTIFICATION_UPDATE })
updateNotification(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: NotificationUpdateDto,
): Promise<NotificationDto> {
return this.service.update(auth, id, dto);
}
@Delete(':id')
@Authenticated({ permission: Permission.NOTIFICATION_DELETE })
deleteNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(auth, id);
}
}
+1 -1
View File
@@ -90,7 +90,7 @@ export class StorageCore {
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, person.ownerId, `${person.id}.jpeg`); return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, person.ownerId, `${person.id}.jpeg`);
} }
static getImagePath(asset: ThumbnailPathEntity, type: GeneratedImageType, format: ImageFormat) { static getImagePath(asset: ThumbnailPathEntity, type: GeneratedImageType, format: 'jpeg' | 'webp') {
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}-${type}.${format}`); return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}-${type}.${format}`);
} }
+13 -2
View File
@@ -9,6 +9,7 @@ import {
Permission, Permission,
SharedLinkType, SharedLinkType,
SourceType, SourceType,
UserAvatarColor,
UserStatus, UserStatus,
} from 'src/enum'; } from 'src/enum';
import { OnThisDayData, UserMetadataItem } from 'src/types'; import { OnThisDayData, UserMetadataItem } from 'src/types';
@@ -122,6 +123,7 @@ export type User = {
id: string; id: string;
name: string; name: string;
email: string; email: string;
avatarColor: UserAvatarColor | null;
profileImagePath: string; profileImagePath: string;
profileChangedAt: Date; profileChangedAt: Date;
}; };
@@ -264,7 +266,15 @@ export type AssetFace = {
person?: Person | null; person?: Person | null;
}; };
const userColumns = ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'] as const; const userColumns = ['id', 'name', 'email', 'avatarColor', 'profileImagePath', 'profileChangedAt'] as const;
const userWithPrefixColumns = [
'users.id',
'users.name',
'users.email',
'users.avatarColor',
'users.profileImagePath',
'users.profileChangedAt',
] as const;
export const columns = { export const columns = {
asset: [ asset: [
@@ -306,7 +316,7 @@ export const columns = {
'shared_links.password', 'shared_links.password',
], ],
user: userColumns, user: userColumns,
userWithPrefix: ['users.id', 'users.name', 'users.email', 'users.profileImagePath', 'users.profileChangedAt'], userWithPrefix: userWithPrefixColumns,
userAdmin: [ userAdmin: [
...userColumns, ...userColumns,
'createdAt', 'createdAt',
@@ -323,6 +333,7 @@ export const columns = {
], ],
tag: ['tags.id', 'tags.value', 'tags.createdAt', 'tags.updatedAt', 'tags.color', 'tags.parentId'], tag: ['tags.id', 'tags.value', 'tags.createdAt', 'tags.updatedAt', 'tags.color', 'tags.parentId'],
apiKey: ['id', 'name', 'userId', 'createdAt', 'updatedAt', 'permissions'], apiKey: ['id', 'name', 'userId', 'createdAt', 'updatedAt', 'permissions'],
notification: ['id', 'createdAt', 'level', 'type', 'title', 'description', 'data', 'readAt'],
syncAsset: [ syncAsset: [
'id', 'id',
'ownerId', 'ownerId',
+18
View File
@@ -11,6 +11,8 @@ import {
AssetStatus, AssetStatus,
AssetType, AssetType,
MemoryType, MemoryType,
NotificationLevel,
NotificationType,
Permission, Permission,
SharedLinkType, SharedLinkType,
SourceType, SourceType,
@@ -263,6 +265,21 @@ export interface Memories {
updateId: Generated<string>; updateId: Generated<string>;
} }
export interface Notifications {
id: Generated<string>;
createdAt: Generated<Timestamp>;
updatedAt: Generated<Timestamp>;
deletedAt: Timestamp | null;
updateId: Generated<string>;
userId: string;
level: Generated<NotificationLevel>;
type: NotificationType;
title: string;
description: string | null;
data: any | null;
readAt: Timestamp | null;
}
export interface MemoriesAssetsAssets { export interface MemoriesAssetsAssets {
assetsId: string; assetsId: string;
memoriesId: string; memoriesId: string;
@@ -463,6 +480,7 @@ export interface DB {
memories: Memories; memories: Memories;
memories_assets_assets: MemoriesAssetsAssets; memories_assets_assets: MemoriesAssetsAssets;
migrations: Migrations; migrations: Migrations;
notifications: Notifications;
move_history: MoveHistory; move_history: MoveHistory;
naturalearth_countries: NaturalearthCountries; naturalearth_countries: NaturalearthCountries;
partners_audit: PartnersAudit; partners_audit: PartnersAudit;
+107 -1
View File
@@ -1,4 +1,7 @@
import { IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsString } from 'class-validator';
import { NotificationLevel, NotificationType } from 'src/enum';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
export class TestEmailResponseDto { export class TestEmailResponseDto {
messageId!: string; messageId!: string;
@@ -11,3 +14,106 @@ export class TemplateDto {
@IsString() @IsString()
template!: string; template!: string;
} }
export class NotificationDto {
id!: string;
@ValidateDate()
createdAt!: Date;
@ApiProperty({ enum: NotificationLevel, enumName: 'NotificationLevel' })
level!: NotificationLevel;
@ApiProperty({ enum: NotificationType, enumName: 'NotificationType' })
type!: NotificationType;
title!: string;
description?: string;
data?: any;
readAt?: Date;
}
export class NotificationSearchDto {
@Optional()
@ValidateUUID({ optional: true })
id?: string;
@IsEnum(NotificationLevel)
@Optional()
@ApiProperty({ enum: NotificationLevel, enumName: 'NotificationLevel' })
level?: NotificationLevel;
@IsEnum(NotificationType)
@Optional()
@ApiProperty({ enum: NotificationType, enumName: 'NotificationType' })
type?: NotificationType;
@ValidateBoolean({ optional: true })
unread?: boolean;
}
export class NotificationCreateDto {
@Optional()
@IsEnum(NotificationLevel)
@ApiProperty({ enum: NotificationLevel, enumName: 'NotificationLevel' })
level?: NotificationLevel;
@IsEnum(NotificationType)
@Optional()
@ApiProperty({ enum: NotificationType, enumName: 'NotificationType' })
type?: NotificationType;
@IsString()
title!: string;
@IsString()
@Optional({ nullable: true })
description?: string | null;
@Optional({ nullable: true })
data?: any;
@ValidateDate({ optional: true, nullable: true })
readAt?: Date | null;
@ValidateUUID()
userId!: string;
}
export class NotificationUpdateDto {
@ValidateDate({ optional: true, nullable: true })
readAt?: Date | null;
}
export class NotificationUpdateAllDto {
@ValidateUUID({ each: true, optional: true })
ids!: string[];
@ValidateDate({ optional: true, nullable: true })
readAt?: Date | null;
}
export class NotificationDeleteAllDto {
@ValidateUUID({ each: true })
ids!: string[];
}
export type MapNotification = {
id: string;
createdAt: Date;
updateId?: string;
level: NotificationLevel;
type: NotificationType;
data: any | null;
title: string;
description: string | null;
readAt: Date | null;
};
export const mapNotification = (notification: MapNotification): NotificationDto => {
return {
id: notification.id,
createdAt: notification.createdAt,
level: notification.level,
type: notification.type,
title: notification.title,
description: notification.description ?? undefined,
data: notification.data ?? undefined,
readAt: notification.readAt ?? undefined,
};
};
+12 -2
View File
@@ -25,6 +25,7 @@ import {
Colorspace, Colorspace,
ImageFormat, ImageFormat,
LogLevel, LogLevel,
OAuthTokenEndpointAuthMethod,
QueueName, QueueName,
ToneMapping, ToneMapping,
TranscodeHWAccel, TranscodeHWAccel,
@@ -33,7 +34,7 @@ import {
VideoContainer, VideoContainer,
} from 'src/enum'; } from 'src/enum';
import { ConcurrentQueueName } from 'src/types'; import { ConcurrentQueueName } from 'src/types';
import { IsCronExpression, ValidateBoolean } from 'src/validation'; import { IsCronExpression, Optional, ValidateBoolean } from 'src/validation';
const isLibraryScanEnabled = (config: SystemConfigLibraryScanDto) => config.enabled; const isLibraryScanEnabled = (config: SystemConfigLibraryScanDto) => config.enabled;
const isOAuthEnabled = (config: SystemConfigOAuthDto) => config.enabled; const isOAuthEnabled = (config: SystemConfigOAuthDto) => config.enabled;
@@ -344,10 +345,19 @@ class SystemConfigOAuthDto {
clientId!: string; clientId!: string;
@ValidateIf(isOAuthEnabled) @ValidateIf(isOAuthEnabled)
@IsNotEmpty()
@IsString() @IsString()
clientSecret!: string; clientSecret!: string;
@IsEnum(OAuthTokenEndpointAuthMethod)
@ApiProperty({ enum: OAuthTokenEndpointAuthMethod, enumName: 'OAuthTokenEndpointAuthMethod' })
tokenEndpointAuthMethod!: OAuthTokenEndpointAuthMethod;
@IsInt()
@IsPositive()
@Optional()
@ApiProperty({ type: 'integer' })
timeout!: number;
@IsNumber() @IsNumber()
@Min(0) @Min(0)
defaultStorageQuota!: number; defaultStorageQuota!: number;
-6
View File
@@ -137,11 +137,6 @@ export class UserPreferencesUpdateDto {
purchase?: PurchaseUpdate; purchase?: PurchaseUpdate;
} }
class AvatarResponse {
@ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor })
color!: UserAvatarColor;
}
class RatingsResponse { class RatingsResponse {
enabled: boolean = false; enabled: boolean = false;
} }
@@ -195,7 +190,6 @@ export class UserPreferencesResponseDto implements UserPreferences {
ratings!: RatingsResponse; ratings!: RatingsResponse;
sharedLinks!: SharedLinksResponse; sharedLinks!: SharedLinksResponse;
tags!: TagsResponse; tags!: TagsResponse;
avatar!: AvatarResponse;
emailNotifications!: EmailNotificationsResponse; emailNotifications!: EmailNotificationsResponse;
download!: DownloadResponse; download!: DownloadResponse;
purchase!: PurchaseResponse; purchase!: PurchaseResponse;
+25 -3
View File
@@ -1,10 +1,9 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer'; import { Transform } from 'class-transformer';
import { IsBoolean, IsEmail, IsNotEmpty, IsNumber, IsString, Min } from 'class-validator'; import { IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsNumber, IsString, Min } from 'class-validator';
import { User, UserAdmin } from 'src/database'; import { User, UserAdmin } from 'src/database';
import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum'; import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum';
import { UserMetadataItem } from 'src/types'; import { UserMetadataItem } from 'src/types';
import { getPreferences } from 'src/utils/preferences';
import { Optional, ValidateBoolean, toEmail, toSanitized } from 'src/validation'; import { Optional, ValidateBoolean, toEmail, toSanitized } from 'src/validation';
export class UserUpdateMeDto { export class UserUpdateMeDto {
@@ -23,6 +22,11 @@ export class UserUpdateMeDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
name?: string; name?: string;
@Optional({ nullable: true })
@IsEnum(UserAvatarColor)
@ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor })
avatarColor?: UserAvatarColor | null;
} }
export class UserResponseDto { export class UserResponseDto {
@@ -41,13 +45,21 @@ export class UserLicense {
activatedAt!: Date; activatedAt!: Date;
} }
const emailToAvatarColor = (email: string): UserAvatarColor => {
const values = Object.values(UserAvatarColor);
const randomIndex = Math.floor(
[...email].map((letter) => letter.codePointAt(0) ?? 0).reduce((a, b) => a + b, 0) % values.length,
);
return values[randomIndex];
};
export const mapUser = (entity: User | UserAdmin): UserResponseDto => { export const mapUser = (entity: User | UserAdmin): UserResponseDto => {
return { return {
id: entity.id, id: entity.id,
email: entity.email, email: entity.email,
name: entity.name, name: entity.name,
profileImagePath: entity.profileImagePath, profileImagePath: entity.profileImagePath,
avatarColor: getPreferences(entity.email, (entity as UserAdmin).metadata || []).avatar.color, avatarColor: entity.avatarColor ?? emailToAvatarColor(entity.email),
profileChangedAt: entity.profileChangedAt, profileChangedAt: entity.profileChangedAt,
}; };
}; };
@@ -69,6 +81,11 @@ export class UserAdminCreateDto {
@IsString() @IsString()
name!: string; name!: string;
@Optional({ nullable: true })
@IsEnum(UserAvatarColor)
@ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor })
avatarColor?: UserAvatarColor | null;
@Optional({ nullable: true }) @Optional({ nullable: true })
@IsString() @IsString()
@Transform(toSanitized) @Transform(toSanitized)
@@ -104,6 +121,11 @@ export class UserAdminUpdateDto {
@IsNotEmpty() @IsNotEmpty()
name?: string; name?: string;
@Optional({ nullable: true })
@IsEnum(UserAvatarColor)
@ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor })
avatarColor?: UserAvatarColor | null;
@Optional({ nullable: true }) @Optional({ nullable: true })
@IsString() @IsString()
@Transform(toSanitized) @Transform(toSanitized)
+30
View File
@@ -126,6 +126,11 @@ export enum Permission {
MEMORY_UPDATE = 'memory.update', MEMORY_UPDATE = 'memory.update',
MEMORY_DELETE = 'memory.delete', MEMORY_DELETE = 'memory.delete',
NOTIFICATION_CREATE = 'notification.create',
NOTIFICATION_READ = 'notification.read',
NOTIFICATION_UPDATE = 'notification.update',
NOTIFICATION_DELETE = 'notification.delete',
PARTNER_CREATE = 'partner.create', PARTNER_CREATE = 'partner.create',
PARTNER_READ = 'partner.read', PARTNER_READ = 'partner.read',
PARTNER_UPDATE = 'partner.update', PARTNER_UPDATE = 'partner.update',
@@ -332,6 +337,11 @@ export enum ImageFormat {
WEBP = 'webp', WEBP = 'webp',
} }
export enum RawExtractedFormat {
JPEG = 'jpeg',
JXL = 'jxl',
}
export enum LogLevel { export enum LogLevel {
VERBOSE = 'verbose', VERBOSE = 'verbose',
DEBUG = 'debug', DEBUG = 'debug',
@@ -515,6 +525,7 @@ export enum JobName {
NOTIFY_SIGNUP = 'notify-signup', NOTIFY_SIGNUP = 'notify-signup',
NOTIFY_ALBUM_INVITE = 'notify-album-invite', NOTIFY_ALBUM_INVITE = 'notify-album-invite',
NOTIFY_ALBUM_UPDATE = 'notify-album-update', NOTIFY_ALBUM_UPDATE = 'notify-album-update',
NOTIFICATIONS_CLEANUP = 'notifications-cleanup',
SEND_EMAIL = 'notification-send-email', SEND_EMAIL = 'notification-send-email',
// Version check // Version check
@@ -580,3 +591,22 @@ export enum SyncEntityType {
PartnerAssetDeleteV1 = 'PartnerAssetDeleteV1', PartnerAssetDeleteV1 = 'PartnerAssetDeleteV1',
PartnerAssetExifV1 = 'PartnerAssetExifV1', PartnerAssetExifV1 = 'PartnerAssetExifV1',
} }
export enum NotificationLevel {
Success = 'success',
Error = 'error',
Warning = 'warning',
Info = 'info',
}
export enum NotificationType {
JobFailed = 'JobFailed',
BackupFailed = 'BackupFailed',
SystemMessage = 'SystemMessage',
Custom = 'Custom',
}
export enum OAuthTokenEndpointAuthMethod {
CLIENT_SECRET_POST = 'client_secret_post',
CLIENT_SECRET_BASIC = 'client_secret_basic',
}
@@ -1,5 +1,5 @@
import { DatabaseExtension } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { vectorIndexQuery } from 'src/utils/database';
import { MigrationInterface, QueryRunner } from 'typeorm'; import { MigrationInterface, QueryRunner } from 'typeorm';
const vectorExtension = new ConfigRepository().getEnv().database.vectorExtension; const vectorExtension = new ConfigRepository().getEnv().database.vectorExtension;
@@ -8,15 +8,9 @@ export class AddCLIPEmbeddingIndex1700713994428 implements MigrationInterface {
name = 'AddCLIPEmbeddingIndex1700713994428'; name = 'AddCLIPEmbeddingIndex1700713994428';
public async up(queryRunner: QueryRunner): Promise<void> { public async up(queryRunner: QueryRunner): Promise<void> {
if (vectorExtension === DatabaseExtension.VECTORS) {
await queryRunner.query(`SET vectors.pgvector_compatibility=on`);
}
await queryRunner.query(`SET search_path TO "$user", public, vectors`); await queryRunner.query(`SET search_path TO "$user", public, vectors`);
await queryRunner.query(` await queryRunner.query(vectorIndexQuery({ vectorExtension, table: 'smart_search', indexName: 'clip_index' }));
CREATE INDEX IF NOT EXISTS clip_index ON smart_search
USING hnsw (embedding vector_cosine_ops)
WITH (ef_construction = 300, m = 16)`);
} }
public async down(queryRunner: QueryRunner): Promise<void> { public async down(queryRunner: QueryRunner): Promise<void> {
@@ -1,5 +1,5 @@
import { DatabaseExtension } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { vectorIndexQuery } from 'src/utils/database';
import { MigrationInterface, QueryRunner } from 'typeorm'; import { MigrationInterface, QueryRunner } from 'typeorm';
const vectorExtension = new ConfigRepository().getEnv().database.vectorExtension; const vectorExtension = new ConfigRepository().getEnv().database.vectorExtension;
@@ -8,15 +8,9 @@ export class AddFaceEmbeddingIndex1700714033632 implements MigrationInterface {
name = 'AddFaceEmbeddingIndex1700714033632'; name = 'AddFaceEmbeddingIndex1700714033632';
public async up(queryRunner: QueryRunner): Promise<void> { public async up(queryRunner: QueryRunner): Promise<void> {
if (vectorExtension === DatabaseExtension.VECTORS) {
await queryRunner.query(`SET vectors.pgvector_compatibility=on`);
}
await queryRunner.query(`SET search_path TO "$user", public, vectors`); await queryRunner.query(`SET search_path TO "$user", public, vectors`);
await queryRunner.query(` await queryRunner.query(vectorIndexQuery({ vectorExtension, table: 'asset_faces', indexName: 'face_index' }));
CREATE INDEX IF NOT EXISTS face_index ON asset_faces
USING hnsw (embedding vector_cosine_ops)
WITH (ef_construction = 300, m = 16)`);
} }
public async down(queryRunner: QueryRunner): Promise<void> { public async down(queryRunner: QueryRunner): Promise<void> {
@@ -1,5 +1,6 @@
import { DatabaseExtension } from 'src/enum'; import { DatabaseExtension } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { vectorIndexQuery } from 'src/utils/database';
import { MigrationInterface, QueryRunner } from 'typeorm'; import { MigrationInterface, QueryRunner } from 'typeorm';
const vectorExtension = new ConfigRepository().getEnv().database.vectorExtension; const vectorExtension = new ConfigRepository().getEnv().database.vectorExtension;
@@ -8,7 +9,6 @@ export class AddFaceSearchRelation1718486162779 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> { public async up(queryRunner: QueryRunner): Promise<void> {
if (vectorExtension === DatabaseExtension.VECTORS) { if (vectorExtension === DatabaseExtension.VECTORS) {
await queryRunner.query(`SET search_path TO "$user", public, vectors`); await queryRunner.query(`SET search_path TO "$user", public, vectors`);
await queryRunner.query(`SET vectors.pgvector_compatibility=on`);
} }
const hasEmbeddings = async (tableName: string): Promise<boolean> => { const hasEmbeddings = async (tableName: string): Promise<boolean> => {
@@ -47,21 +47,14 @@ export class AddFaceSearchRelation1718486162779 implements MigrationInterface {
await queryRunner.query(`ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE real[]`); await queryRunner.query(`ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE real[]`);
await queryRunner.query(`ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE vector(512)`); await queryRunner.query(`ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE vector(512)`);
await queryRunner.query(` await queryRunner.query(vectorIndexQuery({ vectorExtension, table: 'smart_search', indexName: 'clip_index' }));
CREATE INDEX IF NOT EXISTS clip_index ON smart_search
USING hnsw (embedding vector_cosine_ops)
WITH (ef_construction = 300, m = 16)`);
await queryRunner.query(` await queryRunner.query(vectorIndexQuery({ vectorExtension, table: 'face_search', indexName: 'face_index' }));
CREATE INDEX face_index ON face_search
USING hnsw (embedding vector_cosine_ops)
WITH (ef_construction = 300, m = 16)`);
} }
public async down(queryRunner: QueryRunner): Promise<void> { public async down(queryRunner: QueryRunner): Promise<void> {
if (vectorExtension === DatabaseExtension.VECTORS) { if (vectorExtension === DatabaseExtension.VECTORS) {
await queryRunner.query(`SET search_path TO "$user", public, vectors`); await queryRunner.query(`SET search_path TO "$user", public, vectors`);
await queryRunner.query(`SET vectors.pgvector_compatibility=on`);
} }
await queryRunner.query(`ALTER TABLE asset_faces ADD COLUMN "embedding" vector(512)`); await queryRunner.query(`ALTER TABLE asset_faces ADD COLUMN "embedding" vector(512)`);
@@ -74,9 +67,6 @@ export class AddFaceSearchRelation1718486162779 implements MigrationInterface {
WHERE id = fs."faceId"`); WHERE id = fs."faceId"`);
await queryRunner.query(`DROP TABLE face_search`); await queryRunner.query(`DROP TABLE face_search`);
await queryRunner.query(` await queryRunner.query(vectorIndexQuery({ vectorExtension, table: 'asset_faces', indexName: 'face_index' }));
CREATE INDEX face_index ON asset_faces
USING hnsw (embedding vector_cosine_ops)
WITH (ef_construction = 300, m = 16)`);
} }
} }
+9
View File
@@ -157,6 +157,15 @@ where
and "memories"."ownerId" = $2 and "memories"."ownerId" = $2
and "memories"."deletedAt" is null and "memories"."deletedAt" is null
-- AccessRepository.notification.checkOwnerAccess
select
"notifications"."id"
from
"notifications"
where
"notifications"."id" in ($1)
and "notifications"."userId" = $2
-- AccessRepository.person.checkOwnerAccess -- AccessRepository.person.checkOwnerAccess
select select
"person"."id" "person"."id"
@@ -13,6 +13,7 @@ from
"users"."id", "users"."id",
"users"."name", "users"."name",
"users"."email", "users"."email",
"users"."avatarColor",
"users"."profileImagePath", "users"."profileImagePath",
"users"."profileChangedAt" "users"."profileChangedAt"
from from
@@ -44,6 +45,7 @@ returning
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
+9
View File
@@ -12,6 +12,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -36,6 +37,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -100,6 +102,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -124,6 +127,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -191,6 +195,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -215,6 +220,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -269,6 +275,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -292,6 +299,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -353,6 +361,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
+142
View File
@@ -194,6 +194,24 @@ where
"asset_files"."assetId" = $1 "asset_files"."assetId" = $1
and "asset_files"."type" = $2 and "asset_files"."type" = $2
-- AssetJobRepository.streamForEncodeClip
select
"assets"."id"
from
"assets"
inner join "asset_job_status" as "job_status" on "assetId" = "assets"."id"
where
"job_status"."previewAt" is not null
and "assets"."isVisible" = $1
and not exists (
select
from
"smart_search"
where
"assetId" = "assets"."id"
)
and "assets"."deletedAt" is null
-- AssetJobRepository.getForClipEncoding -- AssetJobRepository.getForClipEncoding
select select
"assets"."id", "assets"."id",
@@ -259,6 +277,130 @@ from
where where
"assets"."id" = $2 "assets"."id" = $2
-- AssetJobRepository.getForSyncAssets
select
"assets"."id",
"assets"."isOffline",
"assets"."libraryId",
"assets"."originalPath",
"assets"."status",
"assets"."fileModifiedAt"
from
"assets"
where
"assets"."id" = any ($1::uuid[])
-- AssetJobRepository.getForAssetDeletion
select
"assets"."id",
"assets"."isVisible",
"assets"."libraryId",
"assets"."ownerId",
"assets"."livePhotoVideoId",
"assets"."sidecarPath",
"assets"."encodedVideoPath",
"assets"."originalPath",
to_json("exif") as "exifInfo",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
"asset_faces".*,
"person" as "person"
from
"asset_faces"
left join lateral (
select
"person".*
from
"person"
where
"asset_faces"."personId" = "person"."id"
) as "person" on true
where
"asset_faces"."assetId" = "assets"."id"
and "asset_faces"."deletedAt" is null
) as agg
) as "faces",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
"asset_files"."id",
"asset_files"."path",
"asset_files"."type"
from
"asset_files"
where
"asset_files"."assetId" = "assets"."id"
) as agg
) as "files",
to_json("stacked_assets") as "stack"
from
"assets"
left join "exif" on "assets"."id" = "exif"."assetId"
left join "asset_stack" on "asset_stack"."id" = "assets"."stackId"
left join lateral (
select
"asset_stack"."id",
"asset_stack"."primaryAssetId",
array_agg("stacked") as "assets"
from
"assets" as "stacked"
where
"stacked"."deletedAt" is not null
and "stacked"."isArchived" = $1
and "stacked"."stackId" = "asset_stack"."id"
group by
"asset_stack"."id"
) as "stacked_assets" on "asset_stack"."id" is not null
where
"assets"."id" = $2
-- AssetJobRepository.streamForVideoConversion
select
"assets"."id"
from
"assets"
where
"assets"."type" = $1
and (
"assets"."encodedVideoPath" is null
or "assets"."encodedVideoPath" = $2
)
and "assets"."isVisible" = $3
and "assets"."deletedAt" is null
-- AssetJobRepository.getForVideoConversion
select
"assets"."id",
"assets"."ownerId",
"assets"."originalPath",
"assets"."encodedVideoPath"
from
"assets"
where
"assets"."id" = $1
and "assets"."type" = $2
-- AssetJobRepository.streamForMetadataExtraction
select
"assets"."id"
from
"assets"
left join "asset_job_status" on "asset_job_status"."assetId" = "assets"."id"
where
(
"asset_job_status"."metadataExtractedAt" is null
or "asset_job_status"."assetId" is null
)
and "assets"."isVisible" = $1
and "assets"."deletedAt" is null
-- AssetJobRepository.getForStorageTemplateJob -- AssetJobRepository.getForStorageTemplateJob
select select
"assets"."id", "assets"."id",
@@ -0,0 +1,58 @@
-- NOTE: This file is auto generated by ./sql-generator
-- NotificationRepository.cleanup
delete from "notifications"
where
(
(
"deletedAt" is not null
and "deletedAt" < $1
)
or (
"readAt" > $2
and "createdAt" < $3
)
or (
"readAt" = $4
and "createdAt" < $5
)
)
-- NotificationRepository.search
select
"id",
"createdAt",
"level",
"type",
"title",
"description",
"data",
"readAt"
from
"notifications"
where
"userId" = $1
and "deletedAt" is null
order by
"createdAt" desc
-- NotificationRepository.search (unread)
select
"id",
"createdAt",
"level",
"type",
"title",
"description",
"data",
"readAt"
from
"notifications"
where
(
"userId" = $1
and "readAt" is null
)
and "deletedAt" is null
order by
"createdAt" desc
@@ -12,6 +12,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -29,6 +30,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -61,6 +63,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -78,6 +81,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -112,6 +116,7 @@ returning
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -129,6 +134,7 @@ returning
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -156,6 +162,7 @@ returning
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
@@ -173,6 +180,7 @@ returning
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt" "profileChangedAt"
from from
+7
View File
@@ -5,6 +5,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt", "profileChangedAt",
"createdAt", "createdAt",
@@ -43,6 +44,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt", "profileChangedAt",
"createdAt", "createdAt",
@@ -90,6 +92,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt", "profileChangedAt",
"createdAt", "createdAt",
@@ -128,6 +131,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt", "profileChangedAt",
"createdAt", "createdAt",
@@ -152,6 +156,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt", "profileChangedAt",
"createdAt", "createdAt",
@@ -198,6 +203,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt", "profileChangedAt",
"createdAt", "createdAt",
@@ -235,6 +241,7 @@ select
"id", "id",
"name", "name",
"email", "email",
"avatarColor",
"profileImagePath", "profileImagePath",
"profileChangedAt", "profileChangedAt",
"createdAt", "createdAt",
@@ -279,6 +279,26 @@ class AuthDeviceAccess {
} }
} }
class NotificationAccess {
constructor(private db: Kysely<DB>) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
async checkOwnerAccess(userId: string, notificationIds: Set<string>) {
if (notificationIds.size === 0) {
return new Set<string>();
}
return this.db
.selectFrom('notifications')
.select('notifications.id')
.where('notifications.id', 'in', [...notificationIds])
.where('notifications.userId', '=', userId)
.execute()
.then((stacks) => new Set(stacks.map((stack) => stack.id)));
}
}
class StackAccess { class StackAccess {
constructor(private db: Kysely<DB>) {} constructor(private db: Kysely<DB>) {}
@@ -426,6 +446,7 @@ export class AccessRepository {
asset: AssetAccess; asset: AssetAccess;
authDevice: AuthDeviceAccess; authDevice: AuthDeviceAccess;
memory: MemoryAccess; memory: MemoryAccess;
notification: NotificationAccess;
person: PersonAccess; person: PersonAccess;
partner: PartnerAccess; partner: PartnerAccess;
stack: StackAccess; stack: StackAccess;
@@ -438,6 +459,7 @@ export class AccessRepository {
this.asset = new AssetAccess(db); this.asset = new AssetAccess(db);
this.authDevice = new AuthDeviceAccess(db); this.authDevice = new AuthDeviceAccess(db);
this.memory = new MemoryAccess(db); this.memory = new MemoryAccess(db);
this.notification = new NotificationAccess(db);
this.person = new PersonAccess(db); this.person = new PersonAccess(db);
this.partner = new PartnerAccess(db); this.partner = new PartnerAccess(db);
this.stack = new StackAccess(db); this.stack = new StackAccess(db);
+108 -3
View File
@@ -2,12 +2,21 @@ import { Injectable } from '@nestjs/common';
import { Kysely } from 'kysely'; import { Kysely } from 'kysely';
import { jsonArrayFrom } from 'kysely/helpers/postgres'; import { jsonArrayFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely'; import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database'; import { Asset, columns } from 'src/database';
import { DB } from 'src/db'; import { DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { AssetFileType } from 'src/enum'; import { AssetFileType, AssetType } from 'src/enum';
import { StorageAsset } from 'src/types'; import { StorageAsset } from 'src/types';
import { anyUuid, asUuid, withExifInner, withFaces, withFiles } from 'src/utils/database'; import {
anyUuid,
asUuid,
toJson,
withExif,
withExifInner,
withFaces,
withFacesAndPeople,
withFiles,
} from 'src/utils/database';
@Injectable() @Injectable()
export class AssetJobRepository { export class AssetJobRepository {
@@ -126,6 +135,23 @@ export class AssetJobRepository {
.execute(); .execute();
} }
@GenerateSql({ params: [], stream: true })
streamForEncodeClip(force?: boolean) {
return this.db
.selectFrom('assets')
.select(['assets.id'])
.innerJoin('asset_job_status as job_status', 'assetId', 'assets.id')
.where('job_status.previewAt', 'is not', null)
.where('assets.isVisible', '=', true)
.$if(!force, (qb) =>
qb.where((eb) =>
eb.not((eb) => eb.exists(eb.selectFrom('smart_search').whereRef('assetId', '=', 'assets.id'))),
),
)
.where('assets.deletedAt', 'is', null)
.stream();
}
@GenerateSql({ params: [DummyValue.UUID] }) @GenerateSql({ params: [DummyValue.UUID] })
getForClipEncoding(id: string) { getForClipEncoding(id: string) {
return this.db return this.db
@@ -148,6 +174,7 @@ export class AssetJobRepository {
.executeTakeFirst(); .executeTakeFirst();
} }
@GenerateSql({ params: [[DummyValue.UUID]] })
getForSyncAssets(ids: string[]) { getForSyncAssets(ids: string[]) {
return this.db return this.db
.selectFrom('assets') .selectFrom('assets')
@@ -163,6 +190,84 @@ export class AssetJobRepository {
.execute(); .execute();
} }
@GenerateSql({ params: [DummyValue.UUID] })
getForAssetDeletion(id: string) {
return this.db
.selectFrom('assets')
.select([
'assets.id',
'assets.isVisible',
'assets.libraryId',
'assets.ownerId',
'assets.livePhotoVideoId',
'assets.sidecarPath',
'assets.encodedVideoPath',
'assets.originalPath',
])
.$call(withExif)
.select(withFacesAndPeople)
.select(withFiles)
.leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId')
.leftJoinLateral(
(eb) =>
eb
.selectFrom('assets as stacked')
.select(['asset_stack.id', 'asset_stack.primaryAssetId'])
.select((eb) => eb.fn<Asset[]>('array_agg', [eb.table('stacked')]).as('assets'))
.where('stacked.deletedAt', 'is not', null)
.where('stacked.isArchived', '=', false)
.whereRef('stacked.stackId', '=', 'asset_stack.id')
.groupBy('asset_stack.id')
.as('stacked_assets'),
(join) => join.on('asset_stack.id', 'is not', null),
)
.select((eb) => toJson(eb, 'stacked_assets').as('stack'))
.where('assets.id', '=', id)
.executeTakeFirst();
}
@GenerateSql({ params: [], stream: true })
streamForVideoConversion(force?: boolean) {
return this.db
.selectFrom('assets')
.select(['assets.id'])
.where('assets.type', '=', AssetType.VIDEO)
.$if(!force, (qb) =>
qb
.where((eb) => eb.or([eb('assets.encodedVideoPath', 'is', null), eb('assets.encodedVideoPath', '=', '')]))
.where('assets.isVisible', '=', true),
)
.where('assets.deletedAt', 'is', null)
.stream();
}
@GenerateSql({ params: [DummyValue.UUID] })
getForVideoConversion(id: string) {
return this.db
.selectFrom('assets')
.select(['assets.id', 'assets.ownerId', 'assets.originalPath', 'assets.encodedVideoPath'])
.where('assets.id', '=', id)
.where('assets.type', '=', AssetType.VIDEO)
.executeTakeFirst();
}
@GenerateSql({ params: [], stream: true })
streamForMetadataExtraction(force?: boolean) {
return this.db
.selectFrom('assets')
.select(['assets.id'])
.$if(!force, (qb) =>
qb
.leftJoin('asset_job_status', 'asset_job_status.assetId', 'assets.id')
.where((eb) =>
eb.or([eb('asset_job_status.metadataExtractedAt', 'is', null), eb('asset_job_status.assetId', 'is', null)]),
)
.where('assets.isVisible', '=', true),
)
.where('assets.deletedAt', 'is', null)
.stream();
}
private storageTemplateAssetQuery() { private storageTemplateAssetQuery() {
return this.db return this.db
.selectFrom('assets') .selectFrom('assets')
@@ -49,7 +49,6 @@ export enum WithoutProperty {
THUMBNAIL = 'thumbnail', THUMBNAIL = 'thumbnail',
ENCODED_VIDEO = 'encoded-video', ENCODED_VIDEO = 'encoded-video',
EXIF = 'exif', EXIF = 'exif',
SMART_SEARCH = 'smart-search',
DUPLICATE = 'duplicate', DUPLICATE = 'duplicate',
FACES = 'faces', FACES = 'faces',
SIDECAR = 'sidecar', SIDECAR = 'sidecar',
@@ -571,15 +570,6 @@ export class AssetRepository {
.where((eb) => eb.or([eb('assets.sidecarPath', '=', ''), eb('assets.sidecarPath', 'is', null)])) .where((eb) => eb.or([eb('assets.sidecarPath', '=', ''), eb('assets.sidecarPath', 'is', null)]))
.where('assets.isVisible', '=', true), .where('assets.isVisible', '=', true),
) )
.$if(property === WithoutProperty.SMART_SEARCH, (qb) =>
qb
.innerJoin('asset_job_status as job_status', 'assetId', 'assets.id')
.where('job_status.previewAt', 'is not', null)
.where('assets.isVisible', '=', true)
.where((eb) =>
eb.not((eb) => eb.exists(eb.selectFrom('smart_search').whereRef('assetId', '=', 'assets.id'))),
),
)
.$if(property === WithoutProperty.THUMBNAIL, (qb) => .$if(property === WithoutProperty.THUMBNAIL, (qb) =>
qb qb
.innerJoin('asset_job_status as job_status', 'assetId', 'assets.id') .innerJoin('asset_job_status as job_status', 'assetId', 'assets.id')
@@ -12,6 +12,7 @@ import { DatabaseExtension, DatabaseLock, VectorIndex } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { ExtensionVersion, VectorExtension, VectorUpdateResult } from 'src/types'; import { ExtensionVersion, VectorExtension, VectorUpdateResult } from 'src/types';
import { vectorIndexQuery } from 'src/utils/database';
import { isValidInteger } from 'src/validation'; import { isValidInteger } from 'src/validation';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
@@ -119,12 +120,7 @@ export class DatabaseRepository {
await sql`ALTER TABLE ${sql.raw(table)} ALTER COLUMN embedding SET DATA TYPE vector(${sql.raw(String(dimSize))})`.execute( await sql`ALTER TABLE ${sql.raw(table)} ALTER COLUMN embedding SET DATA TYPE vector(${sql.raw(String(dimSize))})`.execute(
tx, tx,
); );
await sql`SET vectors.pgvector_compatibility=on`.execute(tx); await sql.raw(vectorIndexQuery({ vectorExtension: this.vectorExtension, table, indexName: index })).execute(tx);
await sql`
CREATE INDEX IF NOT EXISTS ${sql.raw(index)} ON ${sql.raw(table)}
USING hnsw (embedding vector_cosine_ops)
WITH (ef_construction = 300, m = 16)
`.execute(tx);
}); });
} }
} }
@@ -14,6 +14,7 @@ import { SystemConfig } from 'src/config';
import { EventConfig } from 'src/decorators'; import { EventConfig } from 'src/decorators';
import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AssetResponseDto } from 'src/dtos/asset-response.dto';
import { AuthDto } from 'src/dtos/auth.dto'; import { AuthDto } from 'src/dtos/auth.dto';
import { NotificationDto } from 'src/dtos/notification.dto';
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
import { ImmichWorker, MetadataKey, QueueName } from 'src/enum'; import { ImmichWorker, MetadataKey, QueueName } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
@@ -64,6 +65,7 @@ type EventMap = {
'assets.restore': [{ assetIds: string[]; userId: string }]; 'assets.restore': [{ assetIds: string[]; userId: string }];
'job.start': [QueueName, JobItem]; 'job.start': [QueueName, JobItem];
'job.failed': [{ job: JobItem; error: Error | any }];
// session events // session events
'session.delete': [{ sessionId: string }]; 'session.delete': [{ sessionId: string }];
@@ -104,6 +106,7 @@ export interface ClientEventMap {
on_server_version: [ServerVersionResponseDto]; on_server_version: [ServerVersionResponseDto];
on_config_update: []; on_config_update: [];
on_new_release: [ReleaseNotification]; on_new_release: [ReleaseNotification];
on_notification: [NotificationDto];
on_session_delete: [string]; on_session_delete: [string];
} }
+3 -1
View File
@@ -22,6 +22,7 @@ import { MediaRepository } from 'src/repositories/media.repository';
import { MemoryRepository } from 'src/repositories/memory.repository'; import { MemoryRepository } from 'src/repositories/memory.repository';
import { MetadataRepository } from 'src/repositories/metadata.repository'; import { MetadataRepository } from 'src/repositories/metadata.repository';
import { MoveRepository } from 'src/repositories/move.repository'; import { MoveRepository } from 'src/repositories/move.repository';
import { NotificationRepository } from 'src/repositories/notification.repository';
import { OAuthRepository } from 'src/repositories/oauth.repository'; import { OAuthRepository } from 'src/repositories/oauth.repository';
import { PartnerRepository } from 'src/repositories/partner.repository'; import { PartnerRepository } from 'src/repositories/partner.repository';
import { PersonRepository } from 'src/repositories/person.repository'; import { PersonRepository } from 'src/repositories/person.repository';
@@ -55,6 +56,7 @@ export const repositories = [
CryptoRepository, CryptoRepository,
DatabaseRepository, DatabaseRepository,
DownloadRepository, DownloadRepository,
EmailRepository,
EventRepository, EventRepository,
JobRepository, JobRepository,
LibraryRepository, LibraryRepository,
@@ -65,7 +67,7 @@ export const repositories = [
MemoryRepository, MemoryRepository,
MetadataRepository, MetadataRepository,
MoveRepository, MoveRepository,
EmailRepository, NotificationRepository,
OAuthRepository, OAuthRepository,
PartnerRepository, PartnerRepository,
PersonRepository, PersonRepository,
@@ -5,7 +5,7 @@ import { Telemetry } from 'src/decorators';
import { LogLevel } from 'src/enum'; import { LogLevel } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
type LogDetails = any[]; type LogDetails = any;
type LogFunction = () => string; type LogFunction = () => string;
const LOG_LEVELS = [LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL]; const LOG_LEVELS = [LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
+40 -23
View File
@@ -7,7 +7,7 @@ import { Writable } from 'node:stream';
import sharp from 'sharp'; import sharp from 'sharp';
import { ORIENTATION_TO_SHARP_ROTATION } from 'src/constants'; import { ORIENTATION_TO_SHARP_ROTATION } from 'src/constants';
import { Exif } from 'src/database'; import { Exif } from 'src/database';
import { Colorspace, LogLevel } from 'src/enum'; import { Colorspace, LogLevel, RawExtractedFormat } from 'src/enum';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { import {
DecodeToBufferOptions, DecodeToBufferOptions,
@@ -36,34 +36,51 @@ type ProgressEvent = {
percent?: number; percent?: number;
}; };
export type ExtractResult = {
buffer: Buffer;
format: RawExtractedFormat;
};
@Injectable() @Injectable()
export class MediaRepository { export class MediaRepository {
constructor(private logger: LoggingRepository) { constructor(private logger: LoggingRepository) {
this.logger.setContext(MediaRepository.name); this.logger.setContext(MediaRepository.name);
} }
async extract(input: string, output: string): Promise<boolean> { /**
*
* @param input file path to the input image
* @returns ExtractResult if succeeded, or null if failed
*/
async extract(input: string): Promise<ExtractResult | null> {
try { try {
// remove existing output file if it exists const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw2', input);
// as exiftool-vendored does not support overwriting via "-w!" flag return { buffer, format: RawExtractedFormat.JPEG };
// and throws "1 files could not be read" error when the output file exists } catch (error: any) {
await fs.unlink(output).catch(() => null); this.logger.debug('Could not extract JpgFromRaw2 buffer from image, trying JPEG from RAW next', error.message);
await exiftool.extractBinaryTag('JpgFromRaw2', input, output); }
} catch {
try { try {
this.logger.debug('Extracting JPEG from RAW image:', input); const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw', input);
await exiftool.extractJpgFromRaw(input, output); return { buffer, format: RawExtractedFormat.JPEG };
} catch (error: any) { } catch (error: any) {
this.logger.debug('Could not extract JPEG from image, trying preview', error.message); this.logger.debug('Could not extract JPEG buffer from image, trying PreviewJXL next', error.message);
try { }
await exiftool.extractPreview(input, output);
} catch (error: any) { try {
this.logger.debug('Could not extract preview from image', error.message); const buffer = await exiftool.extractBinaryTagToBuffer('PreviewJXL', input);
return false; return { buffer, format: RawExtractedFormat.JXL };
} } catch (error: any) {
} this.logger.debug('Could not extract PreviewJXL buffer from image, trying PreviewImage next', error.message);
}
try {
const buffer = await exiftool.extractBinaryTagToBuffer('PreviewImage', input);
return { buffer, format: RawExtractedFormat.JPEG };
} catch (error: any) {
this.logger.debug('Could not extract preview buffer from image', error.message);
return null;
} }
return true;
} }
async writeExif(tags: Partial<Exif>, output: string): Promise<boolean> { async writeExif(tags: Partial<Exif>, output: string): Promise<boolean> {
@@ -104,7 +121,7 @@ export class MediaRepository {
} }
} }
decodeImage(input: string, options: DecodeToBufferOptions) { decodeImage(input: string | Buffer, options: DecodeToBufferOptions) {
return this.getImageDecodingPipeline(input, options).raw().toBuffer({ resolveWithObject: true }); return this.getImageDecodingPipeline(input, options).raw().toBuffer({ resolveWithObject: true });
} }
@@ -235,7 +252,7 @@ export class MediaRepository {
}); });
} }
async getImageDimensions(input: string): Promise<ImageDimensions> { async getImageDimensions(input: string | Buffer): Promise<ImageDimensions> {
const { width = 0, height = 0 } = await sharp(input).metadata(); const { width = 0, height = 0 } = await sharp(input).metadata();
return { width, height }; return { width, height };
} }
@@ -0,0 +1,103 @@
import { Insertable, Kysely, Updateable } from 'kysely';
import { DateTime } from 'luxon';
import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { DB, Notifications } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { NotificationSearchDto } from 'src/dtos/notification.dto';
export class NotificationRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
@GenerateSql({ params: [DummyValue.UUID] })
cleanup() {
return this.db
.deleteFrom('notifications')
.where((eb) =>
eb.or([
// remove soft-deleted notifications
eb.and([eb('deletedAt', 'is not', null), eb('deletedAt', '<', DateTime.now().minus({ days: 3 }).toJSDate())]),
// remove old, read notifications
eb.and([
// keep recently read messages around for a few days
eb('readAt', '>', DateTime.now().minus({ days: 2 }).toJSDate()),
eb('createdAt', '<', DateTime.now().minus({ days: 15 }).toJSDate()),
]),
eb.and([
// remove super old, unread notifications
eb('readAt', '=', null),
eb('createdAt', '<', DateTime.now().minus({ days: 30 }).toJSDate()),
]),
]),
)
.execute();
}
@GenerateSql({ params: [DummyValue.UUID, {}] }, { name: 'unread', params: [DummyValue.UUID, { unread: true }] })
search(userId: string, dto: NotificationSearchDto) {
return this.db
.selectFrom('notifications')
.select(columns.notification)
.where((qb) =>
qb.and({
userId,
id: dto.id,
level: dto.level,
type: dto.type,
readAt: dto.unread ? null : undefined,
}),
)
.where('deletedAt', 'is', null)
.orderBy('createdAt', 'desc')
.execute();
}
create(notification: Insertable<Notifications>) {
return this.db
.insertInto('notifications')
.values(notification)
.returning(columns.notification)
.executeTakeFirstOrThrow();
}
get(id: string) {
return this.db
.selectFrom('notifications')
.select(columns.notification)
.where('id', '=', id)
.where('deletedAt', 'is not', null)
.executeTakeFirst();
}
update(id: string, notification: Updateable<Notifications>) {
return this.db
.updateTable('notifications')
.set(notification)
.where('deletedAt', 'is', null)
.where('id', '=', id)
.returning(columns.notification)
.executeTakeFirstOrThrow();
}
async updateAll(ids: string[], notification: Updateable<Notifications>) {
await this.db.updateTable('notifications').set(notification).where('id', 'in', ids).execute();
}
async delete(id: string) {
await this.db
.updateTable('notifications')
.set({ deletedAt: DateTime.now().toJSDate() })
.where('id', '=', id)
.execute();
}
async deleteAll(ids: string[]) {
await this.db
.updateTable('notifications')
.set({ deletedAt: DateTime.now().toJSDate() })
.where('id', 'in', ids)
.execute();
}
}
+36 -9
View File
@@ -1,16 +1,19 @@
import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { Injectable, InternalServerErrorException } from '@nestjs/common';
import type { UserInfoResponse } from 'openid-client' with { 'resolution-mode': 'import' }; import type { UserInfoResponse } from 'openid-client' with { 'resolution-mode': 'import' };
import { OAuthTokenEndpointAuthMethod } from 'src/enum';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
export type OAuthConfig = { export type OAuthConfig = {
clientId: string; clientId: string;
clientSecret: string; clientSecret?: string;
issuerUrl: string; issuerUrl: string;
mobileOverrideEnabled: boolean; mobileOverrideEnabled: boolean;
mobileRedirectUri: string; mobileRedirectUri: string;
profileSigningAlgorithm: string; profileSigningAlgorithm: string;
scope: string; scope: string;
signingAlgorithm: string; signingAlgorithm: string;
tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod;
timeout: number;
}; };
export type OAuthProfile = UserInfoResponse; export type OAuthProfile = UserInfoResponse;
@@ -76,12 +79,10 @@ export class OAuthRepository {
); );
} }
if (error.code === 'OAUTH_INVALID_RESPONSE') { this.logger.error(`OAuth login failed: ${error.message}`);
this.logger.warn(`Invalid response from authorization server. Cause: ${error.cause?.message}`); this.logger.error(error);
throw error.cause;
}
throw error; throw new Error('OAuth login failed', { cause: error });
} }
} }
@@ -103,6 +104,8 @@ export class OAuthRepository {
clientSecret, clientSecret,
profileSigningAlgorithm, profileSigningAlgorithm,
signingAlgorithm, signingAlgorithm,
tokenEndpointAuthMethod,
timeout,
}: OAuthConfig) { }: OAuthConfig) {
try { try {
const { allowInsecureRequests, discovery } = await import('openid-client'); const { allowInsecureRequests, discovery } = await import('openid-client');
@@ -114,14 +117,38 @@ export class OAuthRepository {
response_types: ['code'], response_types: ['code'],
userinfo_signed_response_alg: profileSigningAlgorithm === 'none' ? undefined : profileSigningAlgorithm, userinfo_signed_response_alg: profileSigningAlgorithm === 'none' ? undefined : profileSigningAlgorithm,
id_token_signed_response_alg: signingAlgorithm, id_token_signed_response_alg: signingAlgorithm,
timeout: 30_000,
}, },
undefined, await this.getTokenAuthMethod(tokenEndpointAuthMethod, clientSecret),
{ execute: [allowInsecureRequests] }, {
execute: [allowInsecureRequests],
timeout,
},
); );
} catch (error: any | AggregateError) { } catch (error: any | AggregateError) {
this.logger.error(`Error in OAuth discovery: ${error}`, error?.stack, error?.errors); this.logger.error(`Error in OAuth discovery: ${error}`, error?.stack, error?.errors);
throw new InternalServerErrorException(`Error in OAuth discovery: ${error}`, { cause: error }); throw new InternalServerErrorException(`Error in OAuth discovery: ${error}`, { cause: error });
} }
} }
private async getTokenAuthMethod(tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod, clientSecret?: string) {
const { None, ClientSecretPost, ClientSecretBasic } = await import('openid-client');
if (!clientSecret) {
return None();
}
switch (tokenEndpointAuthMethod) {
case OAuthTokenEndpointAuthMethod.CLIENT_SECRET_POST: {
return ClientSecretPost(clientSecret);
}
case OAuthTokenEndpointAuthMethod.CLIENT_SECRET_BASIC: {
return ClientSecretBasic(clientSecret);
}
default: {
return None();
}
}
}
} }

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