Compare commits

...

50 Commits

Author SHA1 Message Date
Alex Tran
e53d43a3a9 fix: metadata extraction failed on some video 2025-12-02 02:24:07 +00:00
Mert
7c19b0591f fix(server): cjk migration (#24320)
* join string

* use pagination instead
2025-12-01 15:41:19 -06:00
Mert
95c29a8aea fix(server): use bigrams for cjk (#24285)
* use bigrams for cjk

* update sql

* linting

* actually migrate ocr

* fix backwards test

* use array

* tweaks
2025-12-01 17:24:37 +00:00
idubnori
d8ca210641 chore(web): minor UX improvements of "view asset owners" feature (#24319)
* feat: toggle in options modal

* feat(i18n): add labels to display who uploaded each asset and show asset owners

* feat: migrate asset owner settings to TimelineManager and update AlbumOptionsModal

* Revert "feat(i18n): add labels to display who uploaded each asset and show asset owners"

This reverts commit cf8f4eb135.

* fix: simplify AlbumOptionsModal invocation and update aria-label for asset owners

* feat(i18n): add label for viewing asset owners in the interface

* feat: add tests for showAssetOwners functionality in TimelineManager

* chore: move asset owner visibility toggle to kebabu menu
2025-12-01 10:25:12 -06:00
Min Idzelis
ab35afd3b1 refactor(web): reimplement operation-support as part of timeline-manager (#24056)
* refactor(web): reimplement operation-support as part of timeline-manager

Improve clarity of methods. 
Add inline method documentation.  
Make return type of AssetOperation optional.

* Review comments - self document code. remove optional return from callback
2025-12-01 09:04:39 -06:00
idubnori
65e4fdf98d refactor(web): i18n-ize "view asset owners" (#24317) 2025-12-01 15:01:57 +00:00
Matthew Momjian
fa43fae2a5 fix(mobile): docs link (#24277)
update docs link
2025-11-30 13:01:33 -06:00
Alex
46afd6a101 fix: only generate memory based on users assets (#24151) 2025-11-30 13:01:12 -06:00
Hai Sullivan
46e1967760 chore: optimisation of several UI components of the mobile app (#24098)
* fix(mobile): normalize scrolling behavior in networking settings

Remove ClampingScrollPhysics from networking settings page to match
the scrolling behavior of other settings pages. This restores the
standard iOS bounce/elastic scrolling effect.

* fix(mobile): use consistent native transitions for Library pages

Change Trash, Shared Links, and Folders routes from CustomRoute to AutoRoute to enable native iOS transitions with swipe-back gesture support.

* fix(mobile): remove SafeArea wrapper and ClampingScrollPhysics from Settings

Remove SafeArea wrapper (Scaffold handles safe areas automatically) and ClampingScrollPhysics to enable native iOS bounce scrolling.

* fix(mobile): remove bottom white space in Sync Status page

Replace Padding wrapper with ListView padding to match other Settings pages and eliminate bottom white space.

* chore: fix Dart formatting

Run dart format to fix formatting issues in settings.page.dart and sync_status_and_actions.dart

* Format Dart files

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: kao-byte <benjaminliu@MacBook-Air.local>
2025-11-30 13:01:01 -06:00
Chris Peckover
922282b2b4 feat(web): Shared album owner labels (#21171)
* - pass available album users along to the thumbnail through the asset-date-group
- show a small user-avatar in bottom right of thumbnail

* - change owner to their name in white text instead of the avatar

* cleanup

* - cleanup albumUsers creation
- use font-light for the user's name

* fix lint

* format

* - add toggle to show/hide asset owner names

* update new Timeline with albumUsers

* add @idubnori suggestion for the name font

* Don't show 'view owners' button if the album doesn't have editors

* add missing import

* format

* fix(web): #21171 (#24298)

fix: Bind timelineManager to Timeline component

---------

Co-authored-by: idubnori <i.dub.nori@gmail.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-11-30 18:56:03 +00:00
Alex
e3ab16a5bd chore: refactor mobile events (#24263)
chore: refactor mobile evets
2025-11-30 12:43:33 -06:00
Niklas von Moers
08f320c801 fix(web): use full tag path when creating nested subtags (#24249) 2025-11-29 12:09:32 +00:00
Mees Frensel
e36261b552 fix(web): integrate zoom toggle button into panorama photo viewer (#24189) 2025-11-28 18:50:16 +01:00
Daniel Dietzler
c0a3b58bba fix: rare cases of assets not loading in when scrolling backwards (#24245) 2025-11-28 10:18:49 -06:00
Yaros
f12f609038 fix(mobile): enable backup text overflows (#24227) 2025-11-28 10:18:44 -06:00
renovate[bot]
1f6eb662e5 chore(deps): update dependency opentofu to v1.10.7 (#23964)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-28 14:41:23 +00:00
renovate[bot]
0c1fe35f2f chore(deps): update dependency terragrunt to v0.93.10 (#24149)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-28 15:17:25 +01:00
Robert Schäfer
e98a33cf9d fix(docs): build cli for e2e tests (#24184) 2025-11-28 15:11:17 +01:00
Dionysius
d38305360c docs: DB_STORAGE_TYPE is only used by the database container (#24215)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-11-28 12:43:48 +00:00
Tijs
3e3ca4c104 feat(server): exclude syncthing folders from external libraries (#24240)
* Add SyncThing folders to External library exclusion

SyncThing is a popular library for syncing files (like pictures) between systems. It can really mess up your library if an external library, which is also used by SyncThing, is added and these folders are not excluded.

* Plural

* fix formatting

---------

Co-authored-by: Jonathan Jogenfors <jonathan@jogenfors.se>
2025-11-28 11:40:33 +00:00
Jacob Bundgaard
81edf0749f fix: label 'for' attributes in user-api-key-grid (#24232) 2025-11-27 23:28:38 +00:00
renovate[bot]
01f83ae964 fix(deps): update dependency exiftool-vendored to v33 (#24172)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-11-27 21:45:35 +00:00
renovate[bot]
5eec0dc981 chore(deps): update github-actions (#24038)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 21:41:41 +00:00
renovate[bot]
ca4fd07656 chore(deps): update dependency eslint-plugin-unicorn to v62 (#24167)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-11-27 21:31:16 +00:00
renovate[bot]
7ce43b3824 chore(deps): update dependency node-gyp to v12 (#24168)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 16:13:57 +01:00
Daniel Dietzler
ce00119926 chore: update sharp to 0.34.5 (#24170) 2025-11-27 15:13:16 +00:00
Daniel Dietzler
fffee80e2f feat: command palette (#23693) 2025-11-26 22:18:50 +01:00
Jason Rasmussen
64cd4e96e3 fix: theme switcher (#24209) 2025-11-26 21:17:26 +00:00
renovate[bot]
955a3bfaa6 chore(deps): update base-image to v202511261514 (major) (#24165)
chore(deps): update base-image to v202511261514

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-26 15:10:48 -05:00
renovate[bot]
e699d8f170 chore(deps): update machine-learning (#23352) 2025-11-26 19:09:39 +00:00
Jason Rasmussen
13104d49cd feat(web): shared link card tweaks (#24192) 2025-11-25 19:35:21 -06:00
Jason Rasmussen
2d5ec528d5 fix(web): user admin pages (#24185)
fix: user admin pages
2025-11-25 16:35:37 -05:00
Min Idzelis
5226898184 fix: update timeline-manager after archive actions (#24010)
* fix: update timeline-manager after archive actions

* Add locators to thumb icons
2025-11-25 15:06:29 -05:00
Luka Prebil Grintal
dd4169876c fix(ml): Upgrade ONNX Runtime to v1.22.1 to fix ROCm build failures (#24045)
* fix: update ONNX runtime version to 1.21.0 to fix the failing checksum of 1.20.1

* update patch

* update to 1.22.1

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2025-11-25 13:27:21 -05:00
renovate[bot]
8321c275b8 chore(deps): update dependency body-parser to v2.2.1 [security] (#24179)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 18:29:13 +01:00
renovate[bot]
3d6c26350a fix(deps): update typescript-projects (#24163)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-11-25 17:26:36 +00:00
Jason Rasmussen
db15e5e423 fix: duration extraction (#24178) 2025-11-25 10:26:25 -05:00
renovate[bot]
35d18da14a chore(deps): update node to v24 (major) (#24169)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 15:40:48 +01:00
renovate[bot]
cb56a11f0b chore(deps): update dependency @types/archiver to v7 (#24166)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 15:40:12 +01:00
Jason Rasmussen
104fa09f69 feat: queues (#24142) 2025-11-25 08:19:40 -05:00
Alex
66ae07ee39 fix: don't get OCR data in shared link (#24152) 2025-11-25 07:58:27 -05:00
Daniel Dietzler
939d2c8b27 chore: minor admin pages refactorings (#24160) 2025-11-25 07:57:30 -05:00
renovate[bot]
2801a6e672 chore(deps): update actions/download-artifact action to v6 (#24164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 13:46:34 +01:00
renovate[bot]
4742360469 chore(deps): update grafana/grafana docker tag to v12.3.0 (#24162)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 13:45:50 +01:00
Daniel Dietzler
b56fa62b32 fix: revert "chore(deps): update dependency sharp to v0.34.5" (#24173) 2025-11-25 12:37:08 +00:00
renovate[bot]
ddbe485074 chore(deps): update dependency sharp to v0.34.5 (#24146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 11:56:11 +01:00
renovate[bot]
01310c6d86 chore(deps): update node.js to v24.11.1 (#24147)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 11:55:36 +01:00
Brandon Wees
512327ef69 feat: java in mise (#24154) 2025-11-24 23:18:44 -06:00
Daniel Dietzler
8755cd59fd chore: refactor svelte reactivity (#24072) 2025-11-24 18:57:46 -05:00
Min Idzelis
7694b342ed refactor(web): Extract asset grid layout component from TimelineDateGroup and split into AssetLayout and Month components (#23338)
* refactor(web): Extract asset grid layout component from TimelineDateGroup and split into AssetLayout and Month components

* chore: cleanup

---------

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2025-11-24 23:09:46 +00:00
199 changed files with 8353 additions and 5485 deletions

2
.github/.nvmrc vendored
View File

@@ -1 +1 @@
24.11.0
24.11.1

View File

@@ -35,7 +35,7 @@ jobs:
needs: [get_body, should_run]
if: ${{ needs.should_run.outputs.should_run == 'true' }}
container:
image: ghcr.io/immich-app/mdq:main@sha256:9c905a4ff69f00c4b2f98b40b6090ab3ab18d1a15ed1379733b8691aa1fcb271
image: ghcr.io/immich-app/mdq:main@sha256:73a05fc805dfd3bd29bebc08442aedfec5c419c5ad3421ec73edc5647233891a
outputs:
checked: ${{ steps.get_checkbox.outputs.checked }}
steps:

View File

@@ -57,7 +57,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -70,7 +70,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
# 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
@@ -83,6 +83,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
with:
category: '/language:${{matrix.language}}'

View File

@@ -132,7 +132,7 @@ jobs:
suffixes: '-rocm'
platforms: linux/amd64
runner-mapping: '{"linux/amd64": "mich"}'
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@47a2ee86898ccff51592d6572391fb1abcd7f782 # multi-runner-build-workflow-v2.0.1
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@0477486d82313fba68f7c82c034120a4b8981297 # multi-runner-build-workflow-v2.1.0
permissions:
contents: read
actions: read
@@ -155,7 +155,7 @@ jobs:
name: Build and Push Server
needs: pre-job
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@47a2ee86898ccff51592d6572391fb1abcd7f782 # multi-runner-build-workflow-v2.0.1
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@0477486d82313fba68f7c82c034120a4b8981297 # multi-runner-build-workflow-v2.1.0
permissions:
contents: read
actions: read

View File

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

View File

@@ -31,7 +31,7 @@ jobs:
- name: Generate a token
id: generate_token
if: ${{ inputs.skip != true }}
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}

View File

@@ -49,7 +49,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -62,7 +62,7 @@ jobs:
ref: main
- name: Install uv
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
@@ -126,7 +126,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -30,7 +30,7 @@ jobs:
ref: main
- name: Install uv
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
@@ -159,7 +159,7 @@ jobs:
- name: Create PR
id: create-pr
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
with:
token: ${{ steps.generate-token.outputs.token }}
commit-message: 'chore: release ${{ steps.bump-type.outputs.next }}'

View File

@@ -52,7 +52,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
@@ -74,7 +74,7 @@ jobs:
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Download APK
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
name: release-apk-signed
github-token: ${{ steps.generate-token.outputs.token }}

View File

@@ -571,7 +571,7 @@ jobs:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Install uv
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
# TODO: add caching when supported (https://github.com/actions/setup-python/pull/818)
# with:

View File

@@ -52,7 +52,7 @@
},
"cSpell.words": ["immich"],
"editor.formatOnSave": true,
"eslint.validate": ["javascript", "svelte"],
"eslint.validate": ["javascript", "typescript", "svelte"],
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": {
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart",

View File

@@ -1 +1 @@
24.11.0
24.11.1

View File

@@ -1,4 +1,4 @@
FROM node:22.16.0-alpine3.20@sha256:2289fb1fba0f4633b08ec47b94a89c7e20b829fc5679f9b7b298eaa2f1ed8b7e AS core
FROM node:24.1.0-alpine3.20@sha256:8fe019e0d57dbdce5f5c27c0b63d2775cf34b00e3755a7dea969802d7e0c2b25 AS core
WORKDIR /usr/src/app
COPY package* pnpm* .pnpmfile.cjs ./

View File

@@ -20,7 +20,7 @@
"@types/lodash-es": "^4.17.12",
"@types/micromatch": "^4.0.9",
"@types/mock-fs": "^4.13.1",
"@types/node": "^22.19.1",
"@types/node": "^24.10.1",
"@vitest/coverage-v8": "^3.0.0",
"byte-size": "^9.0.0",
"cli-progress": "^3.12.0",
@@ -28,7 +28,7 @@
"eslint": "^9.14.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^60.0.0",
"eslint-plugin-unicorn": "^62.0.0",
"globals": "^16.0.0",
"mock-fs": "^5.2.0",
"prettier": "^3.2.5",
@@ -69,6 +69,6 @@
"micromatch": "^4.0.8"
},
"volta": {
"node": "24.11.0"
"node": "24.11.1"
}
}

View File

@@ -299,7 +299,7 @@ describe('crawl', () => {
.map(([file]) => file);
// Compare file's content instead of path since a file can be represent in multiple ways.
expect(actual.map((path) => readContent(path)).sort()).toEqual(expected.sort());
expect(actual.map((path) => readContent(path)).toSorted()).toEqual(expected.toSorted());
});
}
});

View File

@@ -160,7 +160,7 @@ export const crawl = async (options: CrawlOptions): Promise<string[]> => {
ignore: [`**/${exclusionPattern}`],
});
globbedFiles.push(...crawledFiles);
return globbedFiles.sort();
return globbedFiles.toSorted();
};
export const sha1 = (filepath: string) => {

View File

@@ -9,7 +9,7 @@
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"target": "es2022",
"target": "es2023",
"sourceMap": true,
"outDir": "./dist",
"incremental": true,

View File

@@ -1,6 +1,6 @@
[tools]
terragrunt = "0.91.2"
opentofu = "1.10.6"
terragrunt = "0.93.10"
opentofu = "1.10.7"
[tasks."tg:fmt"]
run = "terragrunt hclfmt"

View File

@@ -95,7 +95,7 @@ services:
command: ['./run.sh', '-disable-reporting']
ports:
- 3000:3000
image: grafana/grafana:12.2.1-ubuntu@sha256:797530c642f7b41ba7848c44cfda5e361ef1f3391a98bed1e5d448c472b6826a
image: grafana/grafana:12.3.0-ubuntu@sha256:cee936306135e1925ab21dffa16f8a411535d16ab086bef2309339a8e74d62df
volumes:
- grafana-data:/var/lib/grafana

View File

@@ -1 +1 @@
24.11.0
24.11.1

View File

@@ -18,6 +18,7 @@ make e2e
Before you can run the tests, you need to run the following commands _once_:
- `pnpm install` (in `e2e/`)
- `pnpm run build` (in `cli/`)
- `make open-api` (in the project root `/`)
Once the test environment is running, the e2e tests can be run via:

View File

@@ -80,7 +80,7 @@ Information on the current workers can be found [here](/administration/jobs-work
| `DB_SSL_MODE` | Database SSL mode | | server |
| `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`vectorchord`, `pgvector`, `pgvecto.rs`]) | | server |
| `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server |
| `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])<sup>\*3</sup> | `SSD` | server |
| `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])<sup>\*3</sup> | `SSD` | database |
\*1: The values of `DB_USERNAME`, `DB_PASSWORD`, and `DB_DATABASE_NAME` are passed to the Postgres container as the variables `POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB` in `docker-compose.yml`.

View File

@@ -57,6 +57,6 @@
"node": ">=20"
},
"volta": {
"node": "24.11.0"
"node": "24.11.1"
}
}

View File

@@ -1 +1 @@
24.11.0
24.11.1

View File

@@ -26,7 +26,7 @@
"@playwright/test": "^1.44.1",
"@socket.io/component-emitter": "^3.1.2",
"@types/luxon": "^3.4.2",
"@types/node": "^22.19.1",
"@types/node": "^24.10.1",
"@types/oidc-provider": "^9.0.0",
"@types/pg": "^8.15.1",
"@types/pngjs": "^6.0.4",
@@ -35,8 +35,8 @@
"eslint": "^9.14.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^60.0.0",
"exiftool-vendored": "^31.1.0",
"eslint-plugin-unicorn": "^62.0.0",
"exiftool-vendored": "^33.0.0",
"globals": "^16.0.0",
"jose": "^5.6.3",
"luxon": "^3.4.4",
@@ -45,7 +45,7 @@
"pngjs": "^7.0.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^4.0.0",
"sharp": "^0.34.4",
"sharp": "^0.34.5",
"socket.io-client": "^4.7.4",
"supertest": "^7.0.0",
"typescript": "^5.3.3",
@@ -54,6 +54,6 @@
"vitest": "^3.0.0"
},
"volta": {
"node": "24.11.0"
"node": "24.11.1"
}
}

View File

@@ -61,7 +61,7 @@ export function selectRandomDays(daysInMonth: number, numDays: number, rng: Seed
}
}
return [...selectedDays].sort((a, b) => b - a);
return [...selectedDays].toSorted((a, b) => b - a);
}
/**

View File

@@ -62,50 +62,60 @@ export const setupTimelineMockApiRoutes = async (
return route.continue();
});
await context.route('**/api/assets/**', async (route, request) => {
await context.route('**/api/assets/*', async (route, request) => {
const url = new URL(request.url());
const pathname = url.pathname;
const assetId = basename(pathname);
const asset = getAsset(timelineRestData, assetId);
return route.fulfill({
status: 200,
contentType: 'application/json',
json: asset,
});
});
await context.route('**/api/assets/*/ocr', async (route) => {
return route.fulfill({ status: 200, contentType: 'application/json', json: [] });
});
await context.route('**/api/assets/*/thumbnail?size=*', async (route, request) => {
const pattern = /\/api\/assets\/(?<assetId>[^/]+)\/thumbnail\?size=(?<size>preview|thumbnail)/;
const match = request.url().match(pattern);
if (!match) {
const url = new URL(request.url());
const pathname = url.pathname;
const assetId = basename(pathname);
const asset = getAsset(timelineRestData, assetId);
return route.fulfill({
status: 200,
contentType: 'application/json',
json: asset,
});
if (!match?.groups) {
throw new Error(`Invalid URL for thumbnail endpoint: ${request.url()}`);
}
if (match.groups?.size === 'preview') {
if (match.groups.size === 'preview') {
if (!route.request().serviceWorker()) {
return route.continue();
}
const asset = getAsset(timelineRestData, match.groups?.assetId);
const asset = getAsset(timelineRestData, match.groups.assetId);
return route.fulfill({
status: 200,
headers: { 'content-type': 'image/jpeg', ETag: 'abc123', 'Cache-Control': 'public, max-age=3600' },
body: await randomPreview(
match.groups?.assetId,
match.groups.assetId,
(asset?.exifInfo?.exifImageWidth ?? 0) / (asset?.exifInfo?.exifImageHeight ?? 1),
),
});
}
if (match.groups?.size === 'thumbnail') {
if (match.groups.size === 'thumbnail') {
if (!route.request().serviceWorker()) {
return route.continue();
}
const asset = getAsset(timelineRestData, match.groups?.assetId);
const asset = getAsset(timelineRestData, match.groups.assetId);
return route.fulfill({
status: 200,
headers: { 'content-type': 'image/jpeg' },
body: await randomThumbnail(
match.groups?.assetId,
match.groups.assetId,
(asset?.exifInfo?.exifImageWidth ?? 0) / (asset?.exifInfo?.exifImageHeight ?? 1),
),
});
}
return route.continue();
});
await context.route('**/api/albums/**', async (route, request) => {
const pattern = /\/api\/albums\/(?<albumId>[^/?]+)/;
const match = request.url().match(pattern);

View File

@@ -12,7 +12,7 @@ import {
PersonCreateDto,
QueueCommandDto,
QueueName,
QueuesResponseDto,
QueuesResponseLegacyDto,
SharedLinkCreateDto,
UpdateLibraryDto,
UserAdminCreateDto,
@@ -564,13 +564,13 @@ export const utils = {
await updateConfig({ systemConfigDto: defaultConfig }, { headers: asBearerAuth(accessToken) });
},
isQueueEmpty: async (accessToken: string, queue: keyof QueuesResponseDto) => {
isQueueEmpty: async (accessToken: string, queue: keyof QueuesResponseLegacyDto) => {
const queues = await getQueuesLegacy({ headers: asBearerAuth(accessToken) });
const jobCounts = queues[queue].jobCounts;
return !jobCounts.active && !jobCounts.waiting;
},
waitForQueueFinish: (accessToken: string, queue: keyof QueuesResponseDto, ms?: number) => {
waitForQueueFinish: (accessToken: string, queue: keyof QueuesResponseLegacyDto, ms?: number) => {
// eslint-disable-next-line no-async-promise-executor
return new Promise<void>(async (resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('Timed out waiting for queue to empty')), ms || 10_000);

View File

@@ -611,6 +611,53 @@ test.describe('Timeline', () => {
await page.getByText('Photos', { exact: true }).click();
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
});
test('open /archive, favorite photo, unfavorite', async ({ page }) => {
const assetToFavorite = assets[0];
changes.assetArchivals.push(assetToFavorite.id);
await pageUtils.openArchivePage(page);
const favorite = pageRoutePromise(page, '**/api/assets', async (route, request) => {
const requestJson = request.postDataJSON();
if (requestJson.isFavorite === undefined) {
return await route.continue();
}
const isFavorite = requestJson.isFavorite;
if (isFavorite) {
changes.assetFavorites.push(...requestJson.ids);
}
await route.fulfill({
status: 204,
});
});
await thumbnailUtils.withAssetId(page, assetToFavorite.id).hover();
await thumbnailUtils.selectButton(page, assetToFavorite.id).click();
await page.getByLabel('Favorite').click();
await expect(favorite).resolves.toEqual({
isFavorite: true,
ids: [assetToFavorite.id],
});
await expect(thumbnailUtils.withAssetId(page, assetToFavorite.id)).toHaveCount(1);
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id);
await thumbnailUtils.withAssetId(page, assetToFavorite.id).hover();
await thumbnailUtils.selectButton(page, assetToFavorite.id).click();
const unFavoriteRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => {
const requestJson = request.postDataJSON();
if (requestJson.isFavorite === undefined) {
return await route.continue();
}
changes.assetFavorites = changes.assetFavorites.filter((id) => !requestJson.ids.includes(id));
await route.fulfill({
status: 204,
});
});
await page.getByLabel('Remove from favorites').click();
await expect(unFavoriteRequest).resolves.toEqual({
isFavorite: false,
ids: [assetToFavorite.id],
});
await expect(thumbnailUtils.withAssetId(page, assetToFavorite.id)).toHaveCount(1);
await thumbnailUtils.expectThumbnailIsNotFavorite(page, assetToFavorite.id);
});
test('open album, archive photo, open album, unarchive', async ({ page }) => {
const album = timelineRestData.album;
await pageUtils.openAlbumPage(page, album.id);
@@ -633,8 +680,7 @@ test.describe('Timeline', () => {
visibility: 'archive',
ids: [assetToArchive.id],
});
console.log('Skipping assertion - TODO - fix that archiving in album doesnt add icon');
// await thumbnail.expectThumbnailIsArchive(page, assetToArchive.id);
await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id);
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
await page.getByRole('link').getByText('Archive').click();
await timelineUtils.waitForTimelineLoad(page);
@@ -656,8 +702,7 @@ test.describe('Timeline', () => {
visibility: 'timeline',
ids: [assetToArchive.id],
});
console.log('Skipping assertion - TODO - fix bug with not removing asset from timeline-manager after unarchive');
// await expect(thumbnail.withAssetId(page, assetToArchive.id)).toHaveCount(0);
await expect(thumbnailUtils.withAssetId(page, assetToArchive.id)).toHaveCount(0);
await pageUtils.openAlbumPage(page, album.id);
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
});
@@ -712,6 +757,50 @@ test.describe('Timeline', () => {
await page.getByText('Photos', { exact: true }).click();
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
});
test('open /favorites, archive photo, unarchive photo', async ({ page }) => {
await pageUtils.openFavorites(page);
const assetToArchive = getAsset(timelineRestData, 'ad31e29f-2069-4574-b9a9-ad86523c92cb')!;
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
await thumbnailUtils.selectButton(page, assetToArchive.id).click();
await page.getByLabel('Menu').click();
const archive = pageRoutePromise(page, '**/api/assets', async (route, request) => {
const requestJson = request.postDataJSON();
if (requestJson.visibility !== 'archive') {
return await route.continue();
}
await route.fulfill({
status: 204,
});
changes.assetArchivals.push(...requestJson.ids);
});
await page.getByRole('menuitem').getByText('Archive').click();
await expect(archive).resolves.toEqual({
visibility: 'archive',
ids: [assetToArchive.id],
});
await page.getByRole('link').getByText('Archive').click();
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
await thumbnailUtils.expectThumbnailIsNotArchive(page, assetToArchive.id);
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
await thumbnailUtils.selectButton(page, assetToArchive.id).click();
const unarchiveRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => {
const requestJson = request.postDataJSON();
if (requestJson.visibility !== 'timeline') {
return await route.continue();
}
changes.assetArchivals = changes.assetArchivals.filter((id) => !requestJson.ids.includes(id));
await route.fulfill({
status: 204,
});
});
await page.getByLabel('Unarchive').click();
await expect(unarchiveRequest).resolves.toEqual({
visibility: 'timeline',
ids: [assetToArchive.id],
});
await expect(thumbnailUtils.withAssetId(page, assetToArchive.id)).toHaveCount(0);
await thumbnailUtils.expectThumbnailIsNotArchive(page, assetToArchive.id);
});
test('Open album, favorite photo, open /favorites, remove favorite, Open album', async ({ page }) => {
const album = timelineRestData.album;
await pageUtils.openAlbumPage(page, album.id);

View File

@@ -105,20 +105,16 @@ export const thumbnailUtils = {
return await poll(page, () => thumbnailUtils.queryThumbnailInViewport(page, collector));
},
async expectThumbnailIsFavorite(page: Page, assetId: string) {
await expect(
thumbnailUtils
.withAssetId(page, assetId)
.locator(
'path[d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"]',
),
).toHaveCount(1);
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-favorite]')).toHaveCount(1);
},
async expectThumbnailIsNotFavorite(page: Page, assetId: string) {
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-favorite]')).toHaveCount(0);
},
async expectThumbnailIsArchive(page: Page, assetId: string) {
await expect(
thumbnailUtils
.withAssetId(page, assetId)
.locator('path[d="M20 21H4V10H6V19H18V10H20V21M3 3H21V9H3V3M5 5V7H19V5M10.5 11V14H8L12 18L16 14H13.5V11"]'),
).toHaveCount(1);
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(1);
},
async expectThumbnailIsNotArchive(page: Page, assetId: string) {
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0);
},
async expectSelectedReadonly(page: Page, assetId: string) {
// todo - need a data attribute for selected
@@ -208,10 +204,18 @@ export const pageUtils = {
await page.goto(`/photos`);
await timelineUtils.waitForTimelineLoad(page);
},
async openFavorites(page: Page) {
await page.goto(`/favorites`);
await timelineUtils.waitForTimelineLoad(page);
},
async openAlbumPage(page: Page, albumId: string) {
await page.goto(`/albums/${albumId}`);
await timelineUtils.waitForTimelineLoad(page);
},
async openArchivePage(page: Page) {
await page.goto(`/archive`);
await timelineUtils.waitForTimelineLoad(page);
},
async deepLinkAlbumPage(page: Page, albumId: string, assetId: string) {
await page.goto(`/albums/${albumId}?at=${assetId}`);
await timelineUtils.waitForTimelineLoad(page);

View File

@@ -54,7 +54,7 @@ test.describe('User Administration', () => {
await page.getByRole('button', { name: 'Edit' }).click();
await expect(page.getByLabel('Admin User')).not.toBeChecked();
await page.getByText('Admin User').click();
await page.getByLabel('Admin User').click();
await expect(page.getByLabel('Admin User')).toBeChecked();
await page.getByRole('button', { name: 'Confirm' }).click();
@@ -83,7 +83,7 @@ test.describe('User Administration', () => {
await page.getByRole('button', { name: 'Edit' }).click();
await expect(page.getByLabel('Admin User')).toBeChecked();
await page.getByText('Admin User').click();
await page.getByLabel('Admin User').click();
await expect(page.getByLabel('Admin User')).not.toBeChecked();
await page.getByRole('button', { name: 'Confirm' }).click();

View File

@@ -9,7 +9,7 @@
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"target": "es2022",
"target": "es2023",
"sourceMap": true,
"outDir": "./dist",
"incremental": true,

View File

@@ -67,6 +67,7 @@
"confirm_reprocess_all_faces": "Are you sure you want to reprocess all faces? This will also clear named people.",
"confirm_user_password_reset": "Are you sure you want to reset {user}'s password?",
"confirm_user_pin_code_reset": "Are you sure you want to reset {user}'s PIN code?",
"copy_config_to_clipboard_description": "Copy the current system config as a JSON object to the clipboard",
"create_job": "Create job",
"cron_expression": "Cron expression",
"cron_expression_description": "Set the scanning interval using the cron format. For more information please refer to e.g. <link>Crontab Guru</link>",
@@ -74,6 +75,8 @@
"disable_login": "Disable login",
"duplicate_detection_job_description": "Run machine learning on assets to detect similar images. Relies on Smart Search",
"exclusion_pattern_description": "Exclusion patterns lets you ignore files and folders when scanning your library. This is useful if you have folders that contain files you don't want to import, such as RAW files.",
"export_config_as_json_description": "Download the current system config as a JSON file",
"external_libraries_page_description": "Admin external library page",
"external_library_management": "External Library Management",
"face_detection": "Face detection",
"face_detection_description": "Detect the faces in assets using machine learning. For videos, only the thumbnail is considered. \"Refresh\" (re-)processes all assets. \"Reset\" additionally clears all current face data. \"Missing\" queues assets that haven't been processed yet. Detected faces will be queued for Facial Recognition after Face Detection is complete, grouping them into existing or new people.",
@@ -102,6 +105,7 @@
"image_thumbnail_description": "Small thumbnail with stripped metadata, used when viewing groups of photos like the main timeline",
"image_thumbnail_quality_description": "Thumbnail quality from 1-100. Higher is better, but produces larger files and can reduce app responsiveness.",
"image_thumbnail_title": "Thumbnail Settings",
"import_config_from_json_description": "Import system config by uploading a JSON config file",
"job_concurrency": "{job} concurrency",
"job_created": "Job created",
"job_not_concurrency_safe": "This job is not concurrency-safe.",
@@ -110,6 +114,7 @@
"job_status": "Job Status",
"jobs_delayed": "{jobCount, plural, other {# delayed}}",
"jobs_failed": "{jobCount, plural, other {# failed}}",
"jobs_page_description": "Admin jobs page",
"library_created": "Created library: {library}",
"library_deleted": "Library deleted",
"library_details": "Library details",
@@ -182,6 +187,7 @@
"maintenance_start": "Start maintenance mode",
"maintenance_start_error": "Failed to start maintenance mode.",
"manage_concurrency": "Manage Concurrency",
"manage_concurrency_description": "Navigate to the jobs page to manage job concurrency",
"manage_log_settings": "Manage log settings",
"map_dark_style": "Dark style",
"map_enable_description": "Enable map features",
@@ -287,8 +293,10 @@
"server_public_users_description": "All users (name and email) are listed when adding a user to shared albums. When disabled, the user list will only be available to admin users.",
"server_settings": "Server Settings",
"server_settings_description": "Manage server settings",
"server_stats_page_description": "Admin server statistics page",
"server_welcome_message": "Welcome message",
"server_welcome_message_description": "A message that is displayed on the login page.",
"settings_page_description": "Admin settings page",
"sidecar_job": "Sidecar metadata",
"sidecar_job_description": "Discover or synchronize sidecar metadata from the filesystem",
"slideshow_duration_description": "Number of seconds to display each image",
@@ -407,6 +415,8 @@
"user_restore_scheduled_removal": "Restore user - scheduled removal on {date, date, long}",
"user_settings": "User Settings",
"user_settings_description": "Manage user settings",
"user_successfully_removed": "User {email} has been successfully removed.",
"users_page_description": "Admin users page",
"version_check_enabled_description": "Enable version check",
"version_check_implications": "The version check feature relies on periodic communication with github.com",
"version_check_settings": "Version Check",
@@ -727,6 +737,7 @@
"collapse_all": "Collapse all",
"color": "Color",
"color_theme": "Color theme",
"command": "Command",
"comment_deleted": "Comment deleted",
"comment_options": "Comment options",
"comments_and_likes": "Comments & likes",
@@ -1511,6 +1522,7 @@
"other_variables": "Other variables",
"owned": "Owned",
"owner": "Owner",
"page": "Page",
"partner": "Partner",
"partner_can_access": "{partner} can access",
"partner_can_access_assets": "All your photos and videos except those in Archived and Deleted",
@@ -2071,6 +2083,7 @@
"to_select": "to select",
"to_trash": "Trash",
"toggle_settings": "Toggle settings",
"toggle_theme_description": "Toggle theme",
"total": "Total",
"total_usage": "Total usage",
"trash": "Trash",
@@ -2179,6 +2192,7 @@
"view_album": "View Album",
"view_all": "View All",
"view_all_users": "View all users",
"view_asset_owners": "View asset owners",
"view_details": "View Details",
"view_in_timeline": "View in timeline",
"view_link": "View link",

View File

@@ -1,6 +1,6 @@
ARG DEVICE=cpu
FROM python:3.11-bookworm@sha256:fc1f2e357c307c4044133952b203e66a47e7726821a664f603a180a0c5823844 AS builder-cpu
FROM python:3.11-bookworm@sha256:e39286476f84ffedf7c3564b0b74e32c9e1193ec9ca32ee8a11f8c09dbf6aafe AS builder-cpu
FROM builder-cpu AS builder-openvino
@@ -22,10 +22,10 @@ FROM builder-cpu AS builder-rknn
# Warning: 25GiB+ disk space required to pull this image
# TODO: find a way to reduce the image size
FROM rocm/dev-ubuntu-22.04:6.4.3-complete@sha256:1f7e92ca7e3a3785680473329ed1091fc99db3e90fcb3a1688f2933e870ed76b AS builder-rocm
FROM rocm/dev-ubuntu-22.04:6.4.3-complete@sha256:6cda50e312f3aac068cea9ec06c560ca1f522ad546bc8b3d2cf06da0fe8e8a76 AS builder-rocm
# renovate: datasource=github-releases depName=Microsoft/onnxruntime
ARG ONNXRUNTIME_VERSION="v1.20.1"
ARG ONNXRUNTIME_VERSION="v1.22.1"
WORKDIR /code
RUN apt-get update && apt-get install -y --no-install-recommends wget git python3.10-venv
@@ -68,12 +68,12 @@ RUN if [ "$DEVICE" = "rocm" ]; then \
uv pip install /opt/onnxruntime_rocm-*.whl; \
fi
FROM python:3.11-slim-bookworm@sha256:873f91540d53b36327ed4fb018c9669107a4e2a676719720edb4209c4b15d029 AS prod-cpu
FROM python:3.11-slim-bookworm@sha256:2c5bc243b1cc47985ee4fb768bb0bbd4490481c5d0897a62da31b7f30b7304a7 AS prod-cpu
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
MACHINE_LEARNING_MODEL_ARENA=false
FROM python:3.11-slim-bookworm@sha256:873f91540d53b36327ed4fb018c9669107a4e2a676719720edb4209c4b15d029 AS prod-openvino
FROM python:3.11-slim-bookworm@sha256:2c5bc243b1cc47985ee4fb768bb0bbd4490481c5d0897a62da31b7f30b7304a7 AS prod-openvino
RUN apt-get update && \
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
@@ -102,7 +102,7 @@ COPY --from=builder-cuda /usr/local/bin/python3 /usr/local/bin/python3
COPY --from=builder-cuda /usr/local/lib/python3.11 /usr/local/lib/python3.11
COPY --from=builder-cuda /usr/local/lib/libpython3.11.so /usr/local/lib/libpython3.11.so
FROM rocm/dev-ubuntu-22.04:6.4.3-complete@sha256:1f7e92ca7e3a3785680473329ed1091fc99db3e90fcb3a1688f2933e870ed76b AS prod-rocm
FROM rocm/dev-ubuntu-22.04:6.4.3-complete@sha256:6cda50e312f3aac068cea9ec06c560ca1f522ad546bc8b3d2cf06da0fe8e8a76 AS prod-rocm
FROM prod-cpu AS prod-armnn

View File

@@ -1,13 +1,13 @@
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
index d90a2a355..bb1a7de12 100644
index 2714e6f59..a69da76b4 100644
--- a/cmake/CMakeLists.txt
+++ b/cmake/CMakeLists.txt
@@ -295,7 +295,7 @@ if (onnxruntime_USE_ROCM)
@@ -338,7 +338,7 @@ if (onnxruntime_USE_ROCM)
if (ROCM_VERSION_DEV VERSION_LESS "6.2")
message(FATAL_ERROR "CMAKE_HIP_ARCHITECTURES is not set when ROCm version < 6.2")
else()
- set(CMAKE_HIP_ARCHITECTURES "gfx908;gfx90a;gfx1030;gfx1100;gfx1101;gfx940;gfx941;gfx942;gfx1200;gfx1201")
+ set(CMAKE_HIP_ARCHITECTURES "gfx900;gfx908;gfx90a;gfx1030;gfx1100;gfx1101;gfx1102;gfx940;gfx941;gfx942;gfx1200;gfx1201")
endif()
endif()
if (NOT CMAKE_HIP_ARCHITECTURES)
- set(CMAKE_HIP_ARCHITECTURES "gfx908;gfx90a;gfx1030;gfx1100;gfx1101;gfx940;gfx941;gfx942;gfx1200;gfx1201")
+ set(CMAKE_HIP_ARCHITECTURES "gfx900;gfx908;gfx90a;gfx1030;gfx1100;gfx1101;gfx1102;gfx940;gfx941;gfx942;gfx1200;gfx1201")
endif()
file(GLOB rocm_cmake_components ${onnxruntime_ROCM_HOME}/lib/cmake/*)

3598
machine-learning/uv.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,12 @@
experimental_monorepo_root = true
[tools]
node = "24.11.0"
node = "24.11.1"
flutter = "3.35.7"
pnpm = "10.20.0"
terragrunt = "0.91.2"
opentofu = "1.10.6"
pnpm = "10.22.0"
terragrunt = "0.93.10"
opentofu = "1.10.7"
java = "25.0.1"
[tools."github:CQLabs/homebrew-dcm"]
version = "1.30.0"

View File

@@ -0,0 +1,32 @@
import 'package:immich_mobile/domain/utils/event_stream.dart';
// Timeline Events
class TimelineReloadEvent extends Event {
const TimelineReloadEvent();
}
class ScrollToTopEvent extends Event {
const ScrollToTopEvent();
}
class ScrollToDateEvent extends Event {
final DateTime date;
const ScrollToDateEvent(this.date);
}
// Asset Viewer Events
class ViewerOpenBottomSheetEvent extends Event {
final bool activitiesMode;
const ViewerOpenBottomSheetEvent({this.activitiesMode = false});
}
class ViewerReloadAssetEvent extends Event {
const ViewerReloadAssetEvent();
}
// Multi-Select Events
class MultiSelectToggleEvent extends Event {
final bool isEnabled;
const MultiSelectToggleEvent(this.isEnabled);
}

View File

@@ -1,5 +1,3 @@
import 'package:immich_mobile/domain/utils/event_stream.dart';
enum GroupAssetsBy { day, month, auto, none }
enum HeaderType { none, month, day, monthAndDay }
@@ -31,17 +29,3 @@ class TimeBucket extends Bucket {
@override
int get hashCode => super.hashCode ^ date.hashCode;
}
class TimelineReloadEvent extends Event {
const TimelineReloadEvent();
}
class ScrollToTopEvent extends Event {
const ScrollToTopEvent();
}
class ScrollToDateEvent extends Event {
final DateTime date;
const ScrollToDateEvent(this.date);
}

View File

@@ -4,6 +4,7 @@ import 'dart:math' as math;
import 'package:collection/collection.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/models/setting.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/services/setting.service.dart';

View File

@@ -3,7 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';

View File

@@ -58,7 +58,7 @@ class SettingsPage extends StatelessWidget {
context.locale;
return Scaffold(
appBar: AppBar(centerTitle: false, title: const Text('settings').tr()),
body: context.isMobile ? const SafeArea(child: _MobileLayout()) : const SafeArea(child: _TabletLayout()),
body: context.isMobile ? const _MobileLayout() : const _TabletLayout(),
);
}
}
@@ -89,11 +89,7 @@ class _MobileLayout extends StatelessWidget {
],
)
.toList();
return ListView(
physics: const ClampingScrollPhysics(),
padding: const EdgeInsets.only(top: 10.0, bottom: 16),
children: [...settings],
);
return ListView(padding: const EdgeInsets.only(top: 10.0, bottom: 16), children: [...settings]);
}
}

View File

@@ -5,7 +5,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart';
@@ -16,7 +16,6 @@ import 'package:immich_mobile/providers/infrastructure/people.provider.dart';
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
import 'package:immich_mobile/providers/tab.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/routing/router.dart';
@RoutePage()

View File

@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';

View File

@@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';

View File

@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';

View File

@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';

View File

@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';

View File

@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';

View File

@@ -9,8 +9,8 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
// used to allow performing unarchive action from different sources (without duplicating code)
Future<void> performUnArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {

View File

@@ -7,7 +7,7 @@ import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';

View File

@@ -1,17 +1,7 @@
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
class ViewerOpenBottomSheetEvent extends Event {
final bool activitiesMode;
const ViewerOpenBottomSheetEvent({this.activitiesMode = false});
}
class ViewerReloadAssetEvent extends Event {
const ViewerReloadAssetEvent();
}
class AssetViewerState {
final int backgroundOpacity;
final bool showingBottomSheet;

View File

@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';

View File

@@ -143,11 +143,13 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"enable_backup".t(context: context),
style: context.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: context.primaryColor,
Flexible(
child: Text(
"enable_backup".t(context: context),
style: context.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: context.primaryColor,
),
),
),
],

View File

@@ -3,8 +3,8 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/models/memory.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/routing/router.dart';

View File

@@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/models/setting.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';

View File

@@ -2,7 +2,6 @@ import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
final multiSelectProvider = NotifierProvider<MultiSelectNotifier, MultiSelectState>(
@@ -10,11 +9,6 @@ final multiSelectProvider = NotifierProvider<MultiSelectNotifier, MultiSelectSta
dependencies: [timelineServiceProvider],
);
class MultiSelectToggleEvent extends Event {
final bool isEnabled;
const MultiSelectToggleEvent(this.isEnabled);
}
class MultiSelectState {
final Set<BaseAsset> selectedAssets;
final Set<BaseAsset> lockedSelectionAssets;

View File

@@ -245,23 +245,15 @@ class AppRouter extends RootStackRouter {
guards: [_authGuard, _duplicateGuard],
transitionsBuilder: TransitionsBuilders.slideLeft,
),
CustomRoute(page: FolderRoute.page, guards: [_authGuard], transitionsBuilder: TransitionsBuilders.fadeIn),
AutoRoute(page: FolderRoute.page, guards: [_authGuard]),
AutoRoute(page: PartnerDetailRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: PersonResultRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: AllPeopleRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: MemoryRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: MapRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: AlbumOptionsRoute.page, guards: [_authGuard, _duplicateGuard]),
CustomRoute(
page: TrashRoute.page,
guards: [_authGuard, _duplicateGuard],
transitionsBuilder: TransitionsBuilders.slideLeft,
),
CustomRoute(
page: SharedLinkRoute.page,
guards: [_authGuard, _duplicateGuard],
transitionsBuilder: TransitionsBuilders.slideLeft,
),
AutoRoute(page: TrashRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: SharedLinkRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: SharedLinkEditRoute.page, guards: [_authGuard, _duplicateGuard]),
CustomRoute(
page: ActivitiesRoute.page,

View File

@@ -193,7 +193,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
InkWell(
onTap: () {
context.pop();
launchUrl(Uri.parse('https://immich.app'), mode: LaunchMode.externalApplication);
launchUrl(Uri.parse('https://docs.immich.app'), mode: LaunchMode.externalApplication);
},
child: Text("documentation", style: context.textTheme.bodySmall).tr(),
),

View File

@@ -4,7 +4,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';

View File

@@ -6,8 +6,8 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';

View File

@@ -7,7 +7,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';

View File

@@ -108,82 +108,80 @@ class SyncStatusAndActions extends HookConsumerWidget {
);
}
return Padding(
padding: const EdgeInsets.only(top: 16, bottom: 32),
child: ListView(
children: [
const _SyncStatsCounts(),
const Divider(height: 1, indent: 16, endIndent: 16),
const SizedBox(height: 24),
_SectionHeaderText(text: "jobs".t(context: context)),
ListTile(
title: Text(
"sync_local".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Text("tap_to_run_job".t(context: context)),
leading: const Icon(Icons.sync),
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).localSyncStatus),
onTap: () {
ref.read(backgroundSyncProvider).syncLocal(full: true);
},
return ListView(
padding: const EdgeInsets.only(top: 16, bottom: 96),
children: [
const _SyncStatsCounts(),
const Divider(height: 1, indent: 16, endIndent: 16),
const SizedBox(height: 24),
_SectionHeaderText(text: "jobs".t(context: context)),
ListTile(
title: Text(
"sync_local".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
ListTile(
title: Text(
"sync_remote".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Text("tap_to_run_job".t(context: context)),
leading: const Icon(Icons.cloud_sync),
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).remoteSyncStatus),
onTap: () {
ref.read(backgroundSyncProvider).syncRemote();
},
subtitle: Text("tap_to_run_job".t(context: context)),
leading: const Icon(Icons.sync),
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).localSyncStatus),
onTap: () {
ref.read(backgroundSyncProvider).syncLocal(full: true);
},
),
ListTile(
title: Text(
"sync_remote".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
ListTile(
title: Text(
"hash_asset".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
leading: const Icon(Icons.tag),
subtitle: Text("tap_to_run_job".t(context: context)),
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).hashJobStatus),
onTap: () {
ref.read(backgroundSyncProvider).hashAssets();
},
subtitle: Text("tap_to_run_job".t(context: context)),
leading: const Icon(Icons.cloud_sync),
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).remoteSyncStatus),
onTap: () {
ref.read(backgroundSyncProvider).syncRemote();
},
),
ListTile(
title: Text(
"hash_asset".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
const Divider(height: 1, indent: 16, endIndent: 16),
const SizedBox(height: 24),
_SectionHeaderText(text: "actions".t(context: context)),
ListTile(
title: Text(
"clear_file_cache".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
leading: const Icon(Icons.playlist_remove_rounded),
onTap: clearFileCache,
leading: const Icon(Icons.tag),
subtitle: Text("tap_to_run_job".t(context: context)),
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).hashJobStatus),
onTap: () {
ref.read(backgroundSyncProvider).hashAssets();
},
),
const Divider(height: 1, indent: 16, endIndent: 16),
const SizedBox(height: 24),
_SectionHeaderText(text: "actions".t(context: context)),
ListTile(
title: Text(
"clear_file_cache".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
ListTile(
title: Text(
"export_database".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Text("export_database_description".t(context: context)),
leading: const Icon(Icons.download),
onTap: exportDatabase,
leading: const Icon(Icons.playlist_remove_rounded),
onTap: clearFileCache,
),
ListTile(
title: Text(
"export_database".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
ListTile(
title: Text(
"reset_sqlite".t(context: context),
style: TextStyle(color: context.colorScheme.error, fontWeight: FontWeight.w500),
),
leading: Icon(Icons.settings_backup_restore_rounded, color: context.colorScheme.error),
onTap: () async {
await resetSqliteDb(context);
},
subtitle: Text("export_database_description".t(context: context)),
leading: const Icon(Icons.download),
onTap: exportDatabase,
),
ListTile(
title: Text(
"reset_sqlite".t(context: context),
style: TextStyle(color: context.colorScheme.error, fontWeight: FontWeight.w500),
),
],
),
leading: Icon(Icons.settings_backup_restore_rounded, color: context.colorScheme.error),
onTap: () async {
await resetSqliteDb(context);
},
),
],
);
}
}

View File

@@ -86,7 +86,6 @@ class NetworkingSettings extends HookConsumerWidget {
return ListView(
padding: const EdgeInsets.only(bottom: 96),
physics: const ClampingScrollPhysics(),
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 8, left: 16, bottom: 8),

View File

@@ -137,8 +137,10 @@ Class | Method | HTTP request | Description
*DeprecatedApi* | [**getAllUserAssetsByDeviceId**](doc//DeprecatedApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Retrieve assets by device ID
*DeprecatedApi* | [**getDeltaSync**](doc//DeprecatedApi.md#getdeltasync) | **POST** /sync/delta-sync | Get delta sync for user
*DeprecatedApi* | [**getFullSyncForUser**](doc//DeprecatedApi.md#getfullsyncforuser) | **POST** /sync/full-sync | Get full sync for user
*DeprecatedApi* | [**getQueuesLegacy**](doc//DeprecatedApi.md#getqueueslegacy) | **GET** /jobs | Retrieve queue counts and status
*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random | Get random assets
*DeprecatedApi* | [**replaceAsset**](doc//DeprecatedApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace asset
*DeprecatedApi* | [**runQueueCommandLegacy**](doc//DeprecatedApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | Download asset archive
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | Retrieve download information
*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} | Delete a duplicate
@@ -198,6 +200,11 @@ Class | Method | HTTP request | Description
*PeopleApi* | [**updatePerson**](doc//PeopleApi.md#updateperson) | **PUT** /people/{id} | Update person
*PluginsApi* | [**getPlugin**](doc//PluginsApi.md#getplugin) | **GET** /plugins/{id} | Retrieve a plugin
*PluginsApi* | [**getPlugins**](doc//PluginsApi.md#getplugins) | **GET** /plugins | List all plugins
*QueuesApi* | [**emptyQueue**](doc//QueuesApi.md#emptyqueue) | **DELETE** /queues/{name}/jobs | Empty a queue
*QueuesApi* | [**getQueue**](doc//QueuesApi.md#getqueue) | **GET** /queues/{name} | Retrieve a queue
*QueuesApi* | [**getQueueJobs**](doc//QueuesApi.md#getqueuejobs) | **GET** /queues/{name}/jobs | Retrieve queue jobs
*QueuesApi* | [**getQueues**](doc//QueuesApi.md#getqueues) | **GET** /queues | List all queues
*QueuesApi* | [**updateQueue**](doc//QueuesApi.md#updatequeue) | **PUT** /queues/{name} | Update a queue
*SearchApi* | [**getAssetsByCity**](doc//SearchApi.md#getassetsbycity) | **GET** /search/cities | Retrieve assets by city
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore | Retrieve explore data
*SearchApi* | [**getSearchSuggestions**](doc//SearchApi.md#getsearchsuggestions) | **GET** /search/suggestions | Retrieve search suggestions
@@ -396,6 +403,7 @@ Class | Method | HTTP request | Description
- [FoldersUpdate](doc//FoldersUpdate.md)
- [ImageFormat](doc//ImageFormat.md)
- [JobCreateDto](doc//JobCreateDto.md)
- [JobName](doc//JobName.md)
- [JobSettingsDto](doc//JobSettingsDto.md)
- [LibraryResponseDto](doc//LibraryResponseDto.md)
- [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md)
@@ -465,11 +473,16 @@ Class | Method | HTTP request | Description
- [PurchaseUpdate](doc//PurchaseUpdate.md)
- [QueueCommand](doc//QueueCommand.md)
- [QueueCommandDto](doc//QueueCommandDto.md)
- [QueueDeleteDto](doc//QueueDeleteDto.md)
- [QueueJobResponseDto](doc//QueueJobResponseDto.md)
- [QueueJobStatus](doc//QueueJobStatus.md)
- [QueueName](doc//QueueName.md)
- [QueueResponseDto](doc//QueueResponseDto.md)
- [QueueResponseLegacyDto](doc//QueueResponseLegacyDto.md)
- [QueueStatisticsDto](doc//QueueStatisticsDto.md)
- [QueueStatusDto](doc//QueueStatusDto.md)
- [QueuesResponseDto](doc//QueuesResponseDto.md)
- [QueueStatusLegacyDto](doc//QueueStatusLegacyDto.md)
- [QueueUpdateDto](doc//QueueUpdateDto.md)
- [QueuesResponseLegacyDto](doc//QueuesResponseLegacyDto.md)
- [RandomSearchDto](doc//RandomSearchDto.md)
- [RatingsResponse](doc//RatingsResponse.md)
- [RatingsUpdate](doc//RatingsUpdate.md)

View File

@@ -50,6 +50,7 @@ part 'api/notifications_admin_api.dart';
part 'api/partners_api.dart';
part 'api/people_api.dart';
part 'api/plugins_api.dart';
part 'api/queues_api.dart';
part 'api/search_api.dart';
part 'api/server_api.dart';
part 'api/sessions_api.dart';
@@ -154,6 +155,7 @@ part 'model/folders_response.dart';
part 'model/folders_update.dart';
part 'model/image_format.dart';
part 'model/job_create_dto.dart';
part 'model/job_name.dart';
part 'model/job_settings_dto.dart';
part 'model/library_response_dto.dart';
part 'model/library_stats_response_dto.dart';
@@ -223,11 +225,16 @@ part 'model/purchase_response.dart';
part 'model/purchase_update.dart';
part 'model/queue_command.dart';
part 'model/queue_command_dto.dart';
part 'model/queue_delete_dto.dart';
part 'model/queue_job_response_dto.dart';
part 'model/queue_job_status.dart';
part 'model/queue_name.dart';
part 'model/queue_response_dto.dart';
part 'model/queue_response_legacy_dto.dart';
part 'model/queue_statistics_dto.dart';
part 'model/queue_status_dto.dart';
part 'model/queues_response_dto.dart';
part 'model/queue_status_legacy_dto.dart';
part 'model/queue_update_dto.dart';
part 'model/queues_response_legacy_dto.dart';
part 'model/random_search_dto.dart';
part 'model/ratings_response.dart';
part 'model/ratings_update.dart';

View File

@@ -248,6 +248,54 @@ class DeprecatedApi {
return null;
}
/// Retrieve queue counts and status
///
/// Retrieve the counts of the current queue, as well as the current status.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getQueuesLegacyWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/jobs';
// 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,
);
}
/// Retrieve queue counts and status
///
/// Retrieve the counts of the current queue, as well as the current status.
Future<QueuesResponseLegacyDto?> getQueuesLegacy() async {
final response = await getQueuesLegacyWithHttpInfo();
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), 'QueuesResponseLegacyDto',) as QueuesResponseLegacyDto;
}
return null;
}
/// Get random assets
///
/// Retrieve a specified number of random assets for the authenticated user.
@@ -444,4 +492,65 @@ class DeprecatedApi {
}
return null;
}
/// Run jobs
///
/// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueCommandDto] queueCommandDto (required):
Future<Response> runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/jobs/{name}'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody = queueCommandDto;
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,
);
}
/// Run jobs
///
/// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueCommandDto] queueCommandDto (required):
Future<QueueResponseLegacyDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto,);
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), 'QueueResponseLegacyDto',) as QueueResponseLegacyDto;
}
return null;
}
}

View File

@@ -97,7 +97,7 @@ class JobsApi {
/// Retrieve queue counts and status
///
/// Retrieve the counts of the current queue, as well as the current status.
Future<QueuesResponseDto?> getQueuesLegacy() async {
Future<QueuesResponseLegacyDto?> getQueuesLegacy() async {
final response = await getQueuesLegacyWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
@@ -106,7 +106,7 @@ class JobsApi {
// 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), 'QueuesResponseDto',) as QueuesResponseDto;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueuesResponseLegacyDto',) as QueuesResponseLegacyDto;
}
return null;
@@ -158,7 +158,7 @@ class JobsApi {
/// * [QueueName] name (required):
///
/// * [QueueCommandDto] queueCommandDto (required):
Future<QueueResponseDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
Future<QueueResponseLegacyDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
@@ -167,7 +167,7 @@ class JobsApi {
// 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), 'QueueResponseDto',) as QueueResponseDto;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueueResponseLegacyDto',) as QueueResponseLegacyDto;
}
return null;

308
mobile/openapi/lib/api/queues_api.dart generated Normal file
View File

@@ -0,0 +1,308 @@
//
// 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 QueuesApi {
QueuesApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
final ApiClient apiClient;
/// Empty a queue
///
/// Removes all jobs from the specified queue.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueDeleteDto] queueDeleteDto (required):
Future<Response> emptyQueueWithHttpInfo(QueueName name, QueueDeleteDto queueDeleteDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/queues/{name}/jobs'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody = queueDeleteDto;
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,
);
}
/// Empty a queue
///
/// Removes all jobs from the specified queue.
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueDeleteDto] queueDeleteDto (required):
Future<void> emptyQueue(QueueName name, QueueDeleteDto queueDeleteDto,) async {
final response = await emptyQueueWithHttpInfo(name, queueDeleteDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Retrieve a queue
///
/// Retrieves a specific queue by its name.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
Future<Response> getQueueWithHttpInfo(QueueName name,) async {
// ignore: prefer_const_declarations
final apiPath = r'/queues/{name}'
.replaceAll('{name}', name.toString());
// 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,
);
}
/// Retrieve a queue
///
/// Retrieves a specific queue by its name.
///
/// Parameters:
///
/// * [QueueName] name (required):
Future<QueueResponseDto?> getQueue(QueueName name,) async {
final response = await getQueueWithHttpInfo(name,);
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), 'QueueResponseDto',) as QueueResponseDto;
}
return null;
}
/// Retrieve queue jobs
///
/// Retrieves a list of queue jobs from the specified queue.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [List<QueueJobStatus>] status:
Future<Response> getQueueJobsWithHttpInfo(QueueName name, { List<QueueJobStatus>? status, }) async {
// ignore: prefer_const_declarations
final apiPath = r'/queues/{name}/jobs'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (status != null) {
queryParams.addAll(_queryParams('multi', 'status', status));
}
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Retrieve queue jobs
///
/// Retrieves a list of queue jobs from the specified queue.
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [List<QueueJobStatus>] status:
Future<List<QueueJobResponseDto>?> getQueueJobs(QueueName name, { List<QueueJobStatus>? status, }) async {
final response = await getQueueJobsWithHttpInfo(name, status: status, );
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<QueueJobResponseDto>') as List)
.cast<QueueJobResponseDto>()
.toList(growable: false);
}
return null;
}
/// List all queues
///
/// Retrieves a list of queues.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getQueuesWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/queues';
// 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,
);
}
/// List all queues
///
/// Retrieves a list of queues.
Future<List<QueueResponseDto>?> getQueues() async {
final response = await getQueuesWithHttpInfo();
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<QueueResponseDto>') as List)
.cast<QueueResponseDto>()
.toList(growable: false);
}
return null;
}
/// Update a queue
///
/// Change the paused status of a specific queue.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueUpdateDto] queueUpdateDto (required):
Future<Response> updateQueueWithHttpInfo(QueueName name, QueueUpdateDto queueUpdateDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/queues/{name}'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody = queueUpdateDto;
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,
);
}
/// Update a queue
///
/// Change the paused status of a specific queue.
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueUpdateDto] queueUpdateDto (required):
Future<QueueResponseDto?> updateQueue(QueueName name, QueueUpdateDto queueUpdateDto,) async {
final response = await updateQueueWithHttpInfo(name, queueUpdateDto,);
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), 'QueueResponseDto',) as QueueResponseDto;
}
return null;
}
}

View File

@@ -358,6 +358,8 @@ class ApiClient {
return ImageFormatTypeTransformer().decode(value);
case 'JobCreateDto':
return JobCreateDto.fromJson(value);
case 'JobName':
return JobNameTypeTransformer().decode(value);
case 'JobSettingsDto':
return JobSettingsDto.fromJson(value);
case 'LibraryResponseDto':
@@ -496,16 +498,26 @@ class ApiClient {
return QueueCommandTypeTransformer().decode(value);
case 'QueueCommandDto':
return QueueCommandDto.fromJson(value);
case 'QueueDeleteDto':
return QueueDeleteDto.fromJson(value);
case 'QueueJobResponseDto':
return QueueJobResponseDto.fromJson(value);
case 'QueueJobStatus':
return QueueJobStatusTypeTransformer().decode(value);
case 'QueueName':
return QueueNameTypeTransformer().decode(value);
case 'QueueResponseDto':
return QueueResponseDto.fromJson(value);
case 'QueueResponseLegacyDto':
return QueueResponseLegacyDto.fromJson(value);
case 'QueueStatisticsDto':
return QueueStatisticsDto.fromJson(value);
case 'QueueStatusDto':
return QueueStatusDto.fromJson(value);
case 'QueuesResponseDto':
return QueuesResponseDto.fromJson(value);
case 'QueueStatusLegacyDto':
return QueueStatusLegacyDto.fromJson(value);
case 'QueueUpdateDto':
return QueueUpdateDto.fromJson(value);
case 'QueuesResponseLegacyDto':
return QueuesResponseLegacyDto.fromJson(value);
case 'RandomSearchDto':
return RandomSearchDto.fromJson(value);
case 'RatingsResponse':

View File

@@ -94,6 +94,9 @@ String parameterToString(dynamic value) {
if (value is ImageFormat) {
return ImageFormatTypeTransformer().encode(value).toString();
}
if (value is JobName) {
return JobNameTypeTransformer().encode(value).toString();
}
if (value is LogLevel) {
return LogLevelTypeTransformer().encode(value).toString();
}
@@ -133,6 +136,9 @@ String parameterToString(dynamic value) {
if (value is QueueCommand) {
return QueueCommandTypeTransformer().encode(value).toString();
}
if (value is QueueJobStatus) {
return QueueJobStatusTypeTransformer().encode(value).toString();
}
if (value is QueueName) {
return QueueNameTypeTransformer().encode(value).toString();
}

244
mobile/openapi/lib/model/job_name.dart generated Normal file
View File

@@ -0,0 +1,244 @@
//
// 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 JobName {
/// Instantiate a new enum with the provided [value].
const JobName._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const assetDelete = JobName._(r'AssetDelete');
static const assetDeleteCheck = JobName._(r'AssetDeleteCheck');
static const assetDetectFacesQueueAll = JobName._(r'AssetDetectFacesQueueAll');
static const assetDetectFaces = JobName._(r'AssetDetectFaces');
static const assetDetectDuplicatesQueueAll = JobName._(r'AssetDetectDuplicatesQueueAll');
static const assetDetectDuplicates = JobName._(r'AssetDetectDuplicates');
static const assetEncodeVideoQueueAll = JobName._(r'AssetEncodeVideoQueueAll');
static const assetEncodeVideo = JobName._(r'AssetEncodeVideo');
static const assetEmptyTrash = JobName._(r'AssetEmptyTrash');
static const assetExtractMetadataQueueAll = JobName._(r'AssetExtractMetadataQueueAll');
static const assetExtractMetadata = JobName._(r'AssetExtractMetadata');
static const assetFileMigration = JobName._(r'AssetFileMigration');
static const assetGenerateThumbnailsQueueAll = JobName._(r'AssetGenerateThumbnailsQueueAll');
static const assetGenerateThumbnails = JobName._(r'AssetGenerateThumbnails');
static const auditLogCleanup = JobName._(r'AuditLogCleanup');
static const auditTableCleanup = JobName._(r'AuditTableCleanup');
static const databaseBackup = JobName._(r'DatabaseBackup');
static const facialRecognitionQueueAll = JobName._(r'FacialRecognitionQueueAll');
static const facialRecognition = JobName._(r'FacialRecognition');
static const fileDelete = JobName._(r'FileDelete');
static const fileMigrationQueueAll = JobName._(r'FileMigrationQueueAll');
static const libraryDeleteCheck = JobName._(r'LibraryDeleteCheck');
static const libraryDelete = JobName._(r'LibraryDelete');
static const libraryRemoveAsset = JobName._(r'LibraryRemoveAsset');
static const libraryScanAssetsQueueAll = JobName._(r'LibraryScanAssetsQueueAll');
static const librarySyncAssets = JobName._(r'LibrarySyncAssets');
static const librarySyncFilesQueueAll = JobName._(r'LibrarySyncFilesQueueAll');
static const librarySyncFiles = JobName._(r'LibrarySyncFiles');
static const libraryScanQueueAll = JobName._(r'LibraryScanQueueAll');
static const memoryCleanup = JobName._(r'MemoryCleanup');
static const memoryGenerate = JobName._(r'MemoryGenerate');
static const notificationsCleanup = JobName._(r'NotificationsCleanup');
static const notifyUserSignup = JobName._(r'NotifyUserSignup');
static const notifyAlbumInvite = JobName._(r'NotifyAlbumInvite');
static const notifyAlbumUpdate = JobName._(r'NotifyAlbumUpdate');
static const userDelete = JobName._(r'UserDelete');
static const userDeleteCheck = JobName._(r'UserDeleteCheck');
static const userSyncUsage = JobName._(r'UserSyncUsage');
static const personCleanup = JobName._(r'PersonCleanup');
static const personFileMigration = JobName._(r'PersonFileMigration');
static const personGenerateThumbnail = JobName._(r'PersonGenerateThumbnail');
static const sessionCleanup = JobName._(r'SessionCleanup');
static const sendMail = JobName._(r'SendMail');
static const sidecarQueueAll = JobName._(r'SidecarQueueAll');
static const sidecarCheck = JobName._(r'SidecarCheck');
static const sidecarWrite = JobName._(r'SidecarWrite');
static const smartSearchQueueAll = JobName._(r'SmartSearchQueueAll');
static const smartSearch = JobName._(r'SmartSearch');
static const storageTemplateMigration = JobName._(r'StorageTemplateMigration');
static const storageTemplateMigrationSingle = JobName._(r'StorageTemplateMigrationSingle');
static const tagCleanup = JobName._(r'TagCleanup');
static const versionCheck = JobName._(r'VersionCheck');
static const ocrQueueAll = JobName._(r'OcrQueueAll');
static const ocr = JobName._(r'Ocr');
static const workflowRun = JobName._(r'WorkflowRun');
/// List of all possible values in this [enum][JobName].
static const values = <JobName>[
assetDelete,
assetDeleteCheck,
assetDetectFacesQueueAll,
assetDetectFaces,
assetDetectDuplicatesQueueAll,
assetDetectDuplicates,
assetEncodeVideoQueueAll,
assetEncodeVideo,
assetEmptyTrash,
assetExtractMetadataQueueAll,
assetExtractMetadata,
assetFileMigration,
assetGenerateThumbnailsQueueAll,
assetGenerateThumbnails,
auditLogCleanup,
auditTableCleanup,
databaseBackup,
facialRecognitionQueueAll,
facialRecognition,
fileDelete,
fileMigrationQueueAll,
libraryDeleteCheck,
libraryDelete,
libraryRemoveAsset,
libraryScanAssetsQueueAll,
librarySyncAssets,
librarySyncFilesQueueAll,
librarySyncFiles,
libraryScanQueueAll,
memoryCleanup,
memoryGenerate,
notificationsCleanup,
notifyUserSignup,
notifyAlbumInvite,
notifyAlbumUpdate,
userDelete,
userDeleteCheck,
userSyncUsage,
personCleanup,
personFileMigration,
personGenerateThumbnail,
sessionCleanup,
sendMail,
sidecarQueueAll,
sidecarCheck,
sidecarWrite,
smartSearchQueueAll,
smartSearch,
storageTemplateMigration,
storageTemplateMigrationSingle,
tagCleanup,
versionCheck,
ocrQueueAll,
ocr,
workflowRun,
];
static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value);
static List<JobName> listFromJson(dynamic json, {bool growable = false,}) {
final result = <JobName>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = JobName.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [JobName] to String,
/// and [decode] dynamic data back to [JobName].
class JobNameTypeTransformer {
factory JobNameTypeTransformer() => _instance ??= const JobNameTypeTransformer._();
const JobNameTypeTransformer._();
String encode(JobName data) => data.value;
/// Decodes a [dynamic value][data] to a JobName.
///
/// 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.
JobName? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'AssetDelete': return JobName.assetDelete;
case r'AssetDeleteCheck': return JobName.assetDeleteCheck;
case r'AssetDetectFacesQueueAll': return JobName.assetDetectFacesQueueAll;
case r'AssetDetectFaces': return JobName.assetDetectFaces;
case r'AssetDetectDuplicatesQueueAll': return JobName.assetDetectDuplicatesQueueAll;
case r'AssetDetectDuplicates': return JobName.assetDetectDuplicates;
case r'AssetEncodeVideoQueueAll': return JobName.assetEncodeVideoQueueAll;
case r'AssetEncodeVideo': return JobName.assetEncodeVideo;
case r'AssetEmptyTrash': return JobName.assetEmptyTrash;
case r'AssetExtractMetadataQueueAll': return JobName.assetExtractMetadataQueueAll;
case r'AssetExtractMetadata': return JobName.assetExtractMetadata;
case r'AssetFileMigration': return JobName.assetFileMigration;
case r'AssetGenerateThumbnailsQueueAll': return JobName.assetGenerateThumbnailsQueueAll;
case r'AssetGenerateThumbnails': return JobName.assetGenerateThumbnails;
case r'AuditLogCleanup': return JobName.auditLogCleanup;
case r'AuditTableCleanup': return JobName.auditTableCleanup;
case r'DatabaseBackup': return JobName.databaseBackup;
case r'FacialRecognitionQueueAll': return JobName.facialRecognitionQueueAll;
case r'FacialRecognition': return JobName.facialRecognition;
case r'FileDelete': return JobName.fileDelete;
case r'FileMigrationQueueAll': return JobName.fileMigrationQueueAll;
case r'LibraryDeleteCheck': return JobName.libraryDeleteCheck;
case r'LibraryDelete': return JobName.libraryDelete;
case r'LibraryRemoveAsset': return JobName.libraryRemoveAsset;
case r'LibraryScanAssetsQueueAll': return JobName.libraryScanAssetsQueueAll;
case r'LibrarySyncAssets': return JobName.librarySyncAssets;
case r'LibrarySyncFilesQueueAll': return JobName.librarySyncFilesQueueAll;
case r'LibrarySyncFiles': return JobName.librarySyncFiles;
case r'LibraryScanQueueAll': return JobName.libraryScanQueueAll;
case r'MemoryCleanup': return JobName.memoryCleanup;
case r'MemoryGenerate': return JobName.memoryGenerate;
case r'NotificationsCleanup': return JobName.notificationsCleanup;
case r'NotifyUserSignup': return JobName.notifyUserSignup;
case r'NotifyAlbumInvite': return JobName.notifyAlbumInvite;
case r'NotifyAlbumUpdate': return JobName.notifyAlbumUpdate;
case r'UserDelete': return JobName.userDelete;
case r'UserDeleteCheck': return JobName.userDeleteCheck;
case r'UserSyncUsage': return JobName.userSyncUsage;
case r'PersonCleanup': return JobName.personCleanup;
case r'PersonFileMigration': return JobName.personFileMigration;
case r'PersonGenerateThumbnail': return JobName.personGenerateThumbnail;
case r'SessionCleanup': return JobName.sessionCleanup;
case r'SendMail': return JobName.sendMail;
case r'SidecarQueueAll': return JobName.sidecarQueueAll;
case r'SidecarCheck': return JobName.sidecarCheck;
case r'SidecarWrite': return JobName.sidecarWrite;
case r'SmartSearchQueueAll': return JobName.smartSearchQueueAll;
case r'SmartSearch': return JobName.smartSearch;
case r'StorageTemplateMigration': return JobName.storageTemplateMigration;
case r'StorageTemplateMigrationSingle': return JobName.storageTemplateMigrationSingle;
case r'TagCleanup': return JobName.tagCleanup;
case r'VersionCheck': return JobName.versionCheck;
case r'OcrQueueAll': return JobName.ocrQueueAll;
case r'Ocr': return JobName.ocr;
case r'WorkflowRun': return JobName.workflowRun;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [JobNameTypeTransformer] instance.
static JobNameTypeTransformer? _instance;
}

View File

@@ -152,6 +152,12 @@ class Permission {
static const userProfileImagePeriodRead = Permission._(r'userProfileImage.read');
static const userProfileImagePeriodUpdate = Permission._(r'userProfileImage.update');
static const userProfileImagePeriodDelete = Permission._(r'userProfileImage.delete');
static const queuePeriodRead = Permission._(r'queue.read');
static const queuePeriodUpdate = Permission._(r'queue.update');
static const queueJobPeriodCreate = Permission._(r'queueJob.create');
static const queueJobPeriodRead = Permission._(r'queueJob.read');
static const queueJobPeriodUpdate = Permission._(r'queueJob.update');
static const queueJobPeriodDelete = Permission._(r'queueJob.delete');
static const workflowPeriodCreate = Permission._(r'workflow.create');
static const workflowPeriodRead = Permission._(r'workflow.read');
static const workflowPeriodUpdate = Permission._(r'workflow.update');
@@ -294,6 +300,12 @@ class Permission {
userProfileImagePeriodRead,
userProfileImagePeriodUpdate,
userProfileImagePeriodDelete,
queuePeriodRead,
queuePeriodUpdate,
queueJobPeriodCreate,
queueJobPeriodRead,
queueJobPeriodUpdate,
queueJobPeriodDelete,
workflowPeriodCreate,
workflowPeriodRead,
workflowPeriodUpdate,
@@ -471,6 +483,12 @@ class PermissionTypeTransformer {
case r'userProfileImage.read': return Permission.userProfileImagePeriodRead;
case r'userProfileImage.update': return Permission.userProfileImagePeriodUpdate;
case r'userProfileImage.delete': return Permission.userProfileImagePeriodDelete;
case r'queue.read': return Permission.queuePeriodRead;
case r'queue.update': return Permission.queuePeriodUpdate;
case r'queueJob.create': return Permission.queueJobPeriodCreate;
case r'queueJob.read': return Permission.queueJobPeriodRead;
case r'queueJob.update': return Permission.queueJobPeriodUpdate;
case r'queueJob.delete': return Permission.queueJobPeriodDelete;
case r'workflow.create': return Permission.workflowPeriodCreate;
case r'workflow.read': return Permission.workflowPeriodRead;
case r'workflow.update': return Permission.workflowPeriodUpdate;

View File

@@ -0,0 +1,109 @@
//
// 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 QueueDeleteDto {
/// Returns a new [QueueDeleteDto] instance.
QueueDeleteDto({
this.failed,
});
/// If true, will also remove failed jobs from the queue.
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? failed;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueDeleteDto &&
other.failed == failed;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(failed == null ? 0 : failed!.hashCode);
@override
String toString() => 'QueueDeleteDto[failed=$failed]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.failed != null) {
json[r'failed'] = this.failed;
} else {
// json[r'failed'] = null;
}
return json;
}
/// Returns a new [QueueDeleteDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueueDeleteDto? fromJson(dynamic value) {
upgradeDto(value, "QueueDeleteDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueueDeleteDto(
failed: mapValueOfType<bool>(json, r'failed'),
);
}
return null;
}
static List<QueueDeleteDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueDeleteDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueDeleteDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, QueueDeleteDto> mapFromJson(dynamic json) {
final map = <String, QueueDeleteDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueueDeleteDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of QueueDeleteDto-objects as value to a dart map
static Map<String, List<QueueDeleteDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueDeleteDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueueDeleteDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}

View File

@@ -0,0 +1,132 @@
//
// 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 QueueJobResponseDto {
/// Returns a new [QueueJobResponseDto] instance.
QueueJobResponseDto({
required this.data,
this.id,
required this.name,
required this.timestamp,
});
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? id;
JobName name;
int timestamp;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueJobResponseDto &&
other.data == data &&
other.id == id &&
other.name == name &&
other.timestamp == timestamp;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(data.hashCode) +
(id == null ? 0 : id!.hashCode) +
(name.hashCode) +
(timestamp.hashCode);
@override
String toString() => 'QueueJobResponseDto[data=$data, id=$id, name=$name, timestamp=$timestamp]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'data'] = this.data;
if (this.id != null) {
json[r'id'] = this.id;
} else {
// json[r'id'] = null;
}
json[r'name'] = this.name;
json[r'timestamp'] = this.timestamp;
return json;
}
/// Returns a new [QueueJobResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueueJobResponseDto? fromJson(dynamic value) {
upgradeDto(value, "QueueJobResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueueJobResponseDto(
data: mapValueOfType<Object>(json, r'data')!,
id: mapValueOfType<String>(json, r'id'),
name: JobName.fromJson(json[r'name'])!,
timestamp: mapValueOfType<int>(json, r'timestamp')!,
);
}
return null;
}
static List<QueueJobResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueJobResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueJobResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, QueueJobResponseDto> mapFromJson(dynamic json) {
final map = <String, QueueJobResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueueJobResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of QueueJobResponseDto-objects as value to a dart map
static Map<String, List<QueueJobResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueJobResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueueJobResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'data',
'name',
'timestamp',
};
}

View File

@@ -0,0 +1,97 @@
//
// 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 QueueJobStatus {
/// Instantiate a new enum with the provided [value].
const QueueJobStatus._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const active = QueueJobStatus._(r'active');
static const failed = QueueJobStatus._(r'failed');
static const completed = QueueJobStatus._(r'completed');
static const delayed = QueueJobStatus._(r'delayed');
static const waiting = QueueJobStatus._(r'waiting');
static const paused = QueueJobStatus._(r'paused');
/// List of all possible values in this [enum][QueueJobStatus].
static const values = <QueueJobStatus>[
active,
failed,
completed,
delayed,
waiting,
paused,
];
static QueueJobStatus? fromJson(dynamic value) => QueueJobStatusTypeTransformer().decode(value);
static List<QueueJobStatus> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueJobStatus>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueJobStatus.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [QueueJobStatus] to String,
/// and [decode] dynamic data back to [QueueJobStatus].
class QueueJobStatusTypeTransformer {
factory QueueJobStatusTypeTransformer() => _instance ??= const QueueJobStatusTypeTransformer._();
const QueueJobStatusTypeTransformer._();
String encode(QueueJobStatus data) => data.value;
/// Decodes a [dynamic value][data] to a QueueJobStatus.
///
/// 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.
QueueJobStatus? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'active': return QueueJobStatus.active;
case r'failed': return QueueJobStatus.failed;
case r'completed': return QueueJobStatus.completed;
case r'delayed': return QueueJobStatus.delayed;
case r'waiting': return QueueJobStatus.waiting;
case r'paused': return QueueJobStatus.paused;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [QueueJobStatusTypeTransformer] instance.
static QueueJobStatusTypeTransformer? _instance;
}

View File

@@ -13,32 +13,38 @@ part of openapi.api;
class QueueResponseDto {
/// Returns a new [QueueResponseDto] instance.
QueueResponseDto({
required this.jobCounts,
required this.queueStatus,
required this.isPaused,
required this.name,
required this.statistics,
});
QueueStatisticsDto jobCounts;
bool isPaused;
QueueStatusDto queueStatus;
QueueName name;
QueueStatisticsDto statistics;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueResponseDto &&
other.jobCounts == jobCounts &&
other.queueStatus == queueStatus;
other.isPaused == isPaused &&
other.name == name &&
other.statistics == statistics;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(jobCounts.hashCode) +
(queueStatus.hashCode);
(isPaused.hashCode) +
(name.hashCode) +
(statistics.hashCode);
@override
String toString() => 'QueueResponseDto[jobCounts=$jobCounts, queueStatus=$queueStatus]';
String toString() => 'QueueResponseDto[isPaused=$isPaused, name=$name, statistics=$statistics]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'jobCounts'] = this.jobCounts;
json[r'queueStatus'] = this.queueStatus;
json[r'isPaused'] = this.isPaused;
json[r'name'] = this.name;
json[r'statistics'] = this.statistics;
return json;
}
@@ -51,8 +57,9 @@ class QueueResponseDto {
final json = value.cast<String, dynamic>();
return QueueResponseDto(
jobCounts: QueueStatisticsDto.fromJson(json[r'jobCounts'])!,
queueStatus: QueueStatusDto.fromJson(json[r'queueStatus'])!,
isPaused: mapValueOfType<bool>(json, r'isPaused')!,
name: QueueName.fromJson(json[r'name'])!,
statistics: QueueStatisticsDto.fromJson(json[r'statistics'])!,
);
}
return null;
@@ -100,8 +107,9 @@ class QueueResponseDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'jobCounts',
'queueStatus',
'isPaused',
'name',
'statistics',
};
}

View File

@@ -0,0 +1,107 @@
//
// 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 QueueResponseLegacyDto {
/// Returns a new [QueueResponseLegacyDto] instance.
QueueResponseLegacyDto({
required this.jobCounts,
required this.queueStatus,
});
QueueStatisticsDto jobCounts;
QueueStatusLegacyDto queueStatus;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueResponseLegacyDto &&
other.jobCounts == jobCounts &&
other.queueStatus == queueStatus;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(jobCounts.hashCode) +
(queueStatus.hashCode);
@override
String toString() => 'QueueResponseLegacyDto[jobCounts=$jobCounts, queueStatus=$queueStatus]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'jobCounts'] = this.jobCounts;
json[r'queueStatus'] = this.queueStatus;
return json;
}
/// Returns a new [QueueResponseLegacyDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueueResponseLegacyDto? fromJson(dynamic value) {
upgradeDto(value, "QueueResponseLegacyDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueueResponseLegacyDto(
jobCounts: QueueStatisticsDto.fromJson(json[r'jobCounts'])!,
queueStatus: QueueStatusLegacyDto.fromJson(json[r'queueStatus'])!,
);
}
return null;
}
static List<QueueResponseLegacyDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueResponseLegacyDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueResponseLegacyDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, QueueResponseLegacyDto> mapFromJson(dynamic json) {
final map = <String, QueueResponseLegacyDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueueResponseLegacyDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of QueueResponseLegacyDto-objects as value to a dart map
static Map<String, List<QueueResponseLegacyDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueResponseLegacyDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueueResponseLegacyDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'jobCounts',
'queueStatus',
};
}

View File

@@ -10,9 +10,9 @@
part of openapi.api;
class QueueStatusDto {
/// Returns a new [QueueStatusDto] instance.
QueueStatusDto({
class QueueStatusLegacyDto {
/// Returns a new [QueueStatusLegacyDto] instance.
QueueStatusLegacyDto({
required this.isActive,
required this.isPaused,
});
@@ -22,7 +22,7 @@ class QueueStatusDto {
bool isPaused;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueStatusDto &&
bool operator ==(Object other) => identical(this, other) || other is QueueStatusLegacyDto &&
other.isActive == isActive &&
other.isPaused == isPaused;
@@ -33,7 +33,7 @@ class QueueStatusDto {
(isPaused.hashCode);
@override
String toString() => 'QueueStatusDto[isActive=$isActive, isPaused=$isPaused]';
String toString() => 'QueueStatusLegacyDto[isActive=$isActive, isPaused=$isPaused]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -42,15 +42,15 @@ class QueueStatusDto {
return json;
}
/// Returns a new [QueueStatusDto] instance and imports its values from
/// Returns a new [QueueStatusLegacyDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueueStatusDto? fromJson(dynamic value) {
upgradeDto(value, "QueueStatusDto");
static QueueStatusLegacyDto? fromJson(dynamic value) {
upgradeDto(value, "QueueStatusLegacyDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueueStatusDto(
return QueueStatusLegacyDto(
isActive: mapValueOfType<bool>(json, r'isActive')!,
isPaused: mapValueOfType<bool>(json, r'isPaused')!,
);
@@ -58,11 +58,11 @@ class QueueStatusDto {
return null;
}
static List<QueueStatusDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueStatusDto>[];
static List<QueueStatusLegacyDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueStatusLegacyDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueStatusDto.fromJson(row);
final value = QueueStatusLegacyDto.fromJson(row);
if (value != null) {
result.add(value);
}
@@ -71,12 +71,12 @@ class QueueStatusDto {
return result.toList(growable: growable);
}
static Map<String, QueueStatusDto> mapFromJson(dynamic json) {
final map = <String, QueueStatusDto>{};
static Map<String, QueueStatusLegacyDto> mapFromJson(dynamic json) {
final map = <String, QueueStatusLegacyDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueueStatusDto.fromJson(entry.value);
final value = QueueStatusLegacyDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
@@ -85,14 +85,14 @@ class QueueStatusDto {
return map;
}
// maps a json object with a list of QueueStatusDto-objects as value to a dart map
static Map<String, List<QueueStatusDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueStatusDto>>{};
// maps a json object with a list of QueueStatusLegacyDto-objects as value to a dart map
static Map<String, List<QueueStatusLegacyDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueStatusLegacyDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueueStatusDto.listFromJson(entry.value, growable: growable,);
map[entry.key] = QueueStatusLegacyDto.listFromJson(entry.value, growable: growable,);
}
}
return map;

View File

@@ -0,0 +1,108 @@
//
// 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 QueueUpdateDto {
/// Returns a new [QueueUpdateDto] instance.
QueueUpdateDto({
this.isPaused,
});
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isPaused;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueUpdateDto &&
other.isPaused == isPaused;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(isPaused == null ? 0 : isPaused!.hashCode);
@override
String toString() => 'QueueUpdateDto[isPaused=$isPaused]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.isPaused != null) {
json[r'isPaused'] = this.isPaused;
} else {
// json[r'isPaused'] = null;
}
return json;
}
/// Returns a new [QueueUpdateDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueueUpdateDto? fromJson(dynamic value) {
upgradeDto(value, "QueueUpdateDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueueUpdateDto(
isPaused: mapValueOfType<bool>(json, r'isPaused'),
);
}
return null;
}
static List<QueueUpdateDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueUpdateDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueUpdateDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, QueueUpdateDto> mapFromJson(dynamic json) {
final map = <String, QueueUpdateDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueueUpdateDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of QueueUpdateDto-objects as value to a dart map
static Map<String, List<QueueUpdateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueUpdateDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueueUpdateDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}

View File

@@ -10,9 +10,9 @@
part of openapi.api;
class QueuesResponseDto {
/// Returns a new [QueuesResponseDto] instance.
QueuesResponseDto({
class QueuesResponseLegacyDto {
/// Returns a new [QueuesResponseLegacyDto] instance.
QueuesResponseLegacyDto({
required this.backgroundTask,
required this.backupDatabase,
required this.duplicateDetection,
@@ -32,42 +32,42 @@ class QueuesResponseDto {
required this.workflow,
});
QueueResponseDto backgroundTask;
QueueResponseLegacyDto backgroundTask;
QueueResponseDto backupDatabase;
QueueResponseLegacyDto backupDatabase;
QueueResponseDto duplicateDetection;
QueueResponseLegacyDto duplicateDetection;
QueueResponseDto faceDetection;
QueueResponseLegacyDto faceDetection;
QueueResponseDto facialRecognition;
QueueResponseLegacyDto facialRecognition;
QueueResponseDto library_;
QueueResponseLegacyDto library_;
QueueResponseDto metadataExtraction;
QueueResponseLegacyDto metadataExtraction;
QueueResponseDto migration;
QueueResponseLegacyDto migration;
QueueResponseDto notifications;
QueueResponseLegacyDto notifications;
QueueResponseDto ocr;
QueueResponseLegacyDto ocr;
QueueResponseDto search;
QueueResponseLegacyDto search;
QueueResponseDto sidecar;
QueueResponseLegacyDto sidecar;
QueueResponseDto smartSearch;
QueueResponseLegacyDto smartSearch;
QueueResponseDto storageTemplateMigration;
QueueResponseLegacyDto storageTemplateMigration;
QueueResponseDto thumbnailGeneration;
QueueResponseLegacyDto thumbnailGeneration;
QueueResponseDto videoConversion;
QueueResponseLegacyDto videoConversion;
QueueResponseDto workflow;
QueueResponseLegacyDto workflow;
@override
bool operator ==(Object other) => identical(this, other) || other is QueuesResponseDto &&
bool operator ==(Object other) => identical(this, other) || other is QueuesResponseLegacyDto &&
other.backgroundTask == backgroundTask &&
other.backupDatabase == backupDatabase &&
other.duplicateDetection == duplicateDetection &&
@@ -108,7 +108,7 @@ class QueuesResponseDto {
(workflow.hashCode);
@override
String toString() => 'QueuesResponseDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]';
String toString() => 'QueuesResponseLegacyDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -132,42 +132,42 @@ class QueuesResponseDto {
return json;
}
/// Returns a new [QueuesResponseDto] instance and imports its values from
/// Returns a new [QueuesResponseLegacyDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueuesResponseDto? fromJson(dynamic value) {
upgradeDto(value, "QueuesResponseDto");
static QueuesResponseLegacyDto? fromJson(dynamic value) {
upgradeDto(value, "QueuesResponseLegacyDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueuesResponseDto(
backgroundTask: QueueResponseDto.fromJson(json[r'backgroundTask'])!,
backupDatabase: QueueResponseDto.fromJson(json[r'backupDatabase'])!,
duplicateDetection: QueueResponseDto.fromJson(json[r'duplicateDetection'])!,
faceDetection: QueueResponseDto.fromJson(json[r'faceDetection'])!,
facialRecognition: QueueResponseDto.fromJson(json[r'facialRecognition'])!,
library_: QueueResponseDto.fromJson(json[r'library'])!,
metadataExtraction: QueueResponseDto.fromJson(json[r'metadataExtraction'])!,
migration: QueueResponseDto.fromJson(json[r'migration'])!,
notifications: QueueResponseDto.fromJson(json[r'notifications'])!,
ocr: QueueResponseDto.fromJson(json[r'ocr'])!,
search: QueueResponseDto.fromJson(json[r'search'])!,
sidecar: QueueResponseDto.fromJson(json[r'sidecar'])!,
smartSearch: QueueResponseDto.fromJson(json[r'smartSearch'])!,
storageTemplateMigration: QueueResponseDto.fromJson(json[r'storageTemplateMigration'])!,
thumbnailGeneration: QueueResponseDto.fromJson(json[r'thumbnailGeneration'])!,
videoConversion: QueueResponseDto.fromJson(json[r'videoConversion'])!,
workflow: QueueResponseDto.fromJson(json[r'workflow'])!,
return QueuesResponseLegacyDto(
backgroundTask: QueueResponseLegacyDto.fromJson(json[r'backgroundTask'])!,
backupDatabase: QueueResponseLegacyDto.fromJson(json[r'backupDatabase'])!,
duplicateDetection: QueueResponseLegacyDto.fromJson(json[r'duplicateDetection'])!,
faceDetection: QueueResponseLegacyDto.fromJson(json[r'faceDetection'])!,
facialRecognition: QueueResponseLegacyDto.fromJson(json[r'facialRecognition'])!,
library_: QueueResponseLegacyDto.fromJson(json[r'library'])!,
metadataExtraction: QueueResponseLegacyDto.fromJson(json[r'metadataExtraction'])!,
migration: QueueResponseLegacyDto.fromJson(json[r'migration'])!,
notifications: QueueResponseLegacyDto.fromJson(json[r'notifications'])!,
ocr: QueueResponseLegacyDto.fromJson(json[r'ocr'])!,
search: QueueResponseLegacyDto.fromJson(json[r'search'])!,
sidecar: QueueResponseLegacyDto.fromJson(json[r'sidecar'])!,
smartSearch: QueueResponseLegacyDto.fromJson(json[r'smartSearch'])!,
storageTemplateMigration: QueueResponseLegacyDto.fromJson(json[r'storageTemplateMigration'])!,
thumbnailGeneration: QueueResponseLegacyDto.fromJson(json[r'thumbnailGeneration'])!,
videoConversion: QueueResponseLegacyDto.fromJson(json[r'videoConversion'])!,
workflow: QueueResponseLegacyDto.fromJson(json[r'workflow'])!,
);
}
return null;
}
static List<QueuesResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueuesResponseDto>[];
static List<QueuesResponseLegacyDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueuesResponseLegacyDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueuesResponseDto.fromJson(row);
final value = QueuesResponseLegacyDto.fromJson(row);
if (value != null) {
result.add(value);
}
@@ -176,12 +176,12 @@ class QueuesResponseDto {
return result.toList(growable: growable);
}
static Map<String, QueuesResponseDto> mapFromJson(dynamic json) {
final map = <String, QueuesResponseDto>{};
static Map<String, QueuesResponseLegacyDto> mapFromJson(dynamic json) {
final map = <String, QueuesResponseLegacyDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueuesResponseDto.fromJson(entry.value);
final value = QueuesResponseLegacyDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
@@ -190,14 +190,14 @@ class QueuesResponseDto {
return map;
}
// maps a json object with a list of QueuesResponseDto-objects as value to a dart map
static Map<String, List<QueuesResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueuesResponseDto>>{};
// maps a json object with a list of QueuesResponseLegacyDto-objects as value to a dart map
static Map<String, List<QueuesResponseLegacyDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueuesResponseLegacyDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueuesResponseDto.listFromJson(entry.value, growable: growable,);
map[entry.key] = QueuesResponseLegacyDto.listFromJson(entry.value, growable: growable,);
}
}
return map;

View File

@@ -4929,6 +4929,7 @@
},
"/jobs": {
"get": {
"deprecated": true,
"description": "Retrieve the counts of the current queue, as well as the current status.",
"operationId": "getQueuesLegacy",
"parameters": [],
@@ -4937,7 +4938,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QueuesResponseDto"
"$ref": "#/components/schemas/QueuesResponseLegacyDto"
}
}
},
@@ -4957,7 +4958,8 @@
],
"summary": "Retrieve queue counts and status",
"tags": [
"Jobs"
"Jobs",
"Deprecated"
],
"x-immich-admin-only": true,
"x-immich-history": [
@@ -4972,10 +4974,14 @@
{
"version": "v2",
"state": "Stable"
},
{
"version": "v2.4.0",
"state": "Deprecated"
}
],
"x-immich-permission": "job.read",
"x-immich-state": "Stable"
"x-immich-state": "Deprecated"
},
"post": {
"description": "Run a specific job. Most jobs are queued automatically, but this endpoint allows for manual creation of a handful of jobs, including various cleanup tasks, as well as creating a new database backup.",
@@ -5032,6 +5038,7 @@
},
"/jobs/{name}": {
"put": {
"deprecated": true,
"description": "Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.",
"operationId": "runQueueCommandLegacy",
"parameters": [
@@ -5059,7 +5066,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
}
}
},
@@ -5079,7 +5086,8 @@
],
"summary": "Run jobs",
"tags": [
"Jobs"
"Jobs",
"Deprecated"
],
"x-immich-admin-only": true,
"x-immich-history": [
@@ -5094,10 +5102,14 @@
{
"version": "v2",
"state": "Stable"
},
{
"version": "v2.4.0",
"state": "Deprecated"
}
],
"x-immich-permission": "job.create",
"x-immich-state": "Stable"
"x-immich-state": "Deprecated"
}
},
"/libraries": {
@@ -8064,6 +8076,303 @@
"x-immich-state": "Alpha"
}
},
"/queues": {
"get": {
"description": "Retrieves a list of queues.",
"operationId": "getQueues",
"parameters": [],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/QueueResponseDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"summary": "List all queues",
"tags": [
"Queues"
],
"x-immich-admin-only": true,
"x-immich-history": [
{
"version": "v2.4.0",
"state": "Added"
},
{
"version": "v2.4.0",
"state": "Alpha"
}
],
"x-immich-permission": "queue.read",
"x-immich-state": "Alpha"
}
},
"/queues/{name}": {
"get": {
"description": "Retrieves a specific queue by its name.",
"operationId": "getQueue",
"parameters": [
{
"name": "name",
"required": true,
"in": "path",
"schema": {
"$ref": "#/components/schemas/QueueName"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QueueResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"summary": "Retrieve a queue",
"tags": [
"Queues"
],
"x-immich-admin-only": true,
"x-immich-history": [
{
"version": "v2.4.0",
"state": "Added"
},
{
"version": "v2.4.0",
"state": "Alpha"
}
],
"x-immich-permission": "queue.read",
"x-immich-state": "Alpha"
},
"put": {
"description": "Change the paused status of a specific queue.",
"operationId": "updateQueue",
"parameters": [
{
"name": "name",
"required": true,
"in": "path",
"schema": {
"$ref": "#/components/schemas/QueueName"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QueueUpdateDto"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QueueResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"summary": "Update a queue",
"tags": [
"Queues"
],
"x-immich-admin-only": true,
"x-immich-history": [
{
"version": "v2.4.0",
"state": "Added"
},
{
"version": "v2.4.0",
"state": "Alpha"
}
],
"x-immich-permission": "queue.update",
"x-immich-state": "Alpha"
}
},
"/queues/{name}/jobs": {
"delete": {
"description": "Removes all jobs from the specified queue.",
"operationId": "emptyQueue",
"parameters": [
{
"name": "name",
"required": true,
"in": "path",
"schema": {
"$ref": "#/components/schemas/QueueName"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QueueDeleteDto"
}
}
},
"required": true
},
"responses": {
"204": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"summary": "Empty a queue",
"tags": [
"Queues"
],
"x-immich-admin-only": true,
"x-immich-history": [
{
"version": "v2.4.0",
"state": "Added"
},
{
"version": "v2.4.0",
"state": "Alpha"
}
],
"x-immich-permission": "queueJob.delete",
"x-immich-state": "Alpha"
},
"get": {
"description": "Retrieves a list of queue jobs from the specified queue.",
"operationId": "getQueueJobs",
"parameters": [
{
"name": "name",
"required": true,
"in": "path",
"schema": {
"$ref": "#/components/schemas/QueueName"
}
},
{
"name": "status",
"required": false,
"in": "query",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/QueueJobStatus"
}
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/QueueJobResponseDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"summary": "Retrieve queue jobs",
"tags": [
"Queues"
],
"x-immich-admin-only": true,
"x-immich-history": [
{
"version": "v2.4.0",
"state": "Added"
},
{
"version": "v2.4.0",
"state": "Alpha"
}
],
"x-immich-permission": "queueJob.read",
"x-immich-state": "Alpha"
}
},
"/search/cities": {
"get": {
"description": "Retrieve a list of assets with each asset belonging to a different city. This endpoint is used on the places pages to show a single thumbnail for each city the user has assets in.",
@@ -14043,6 +14352,10 @@
"name": "Plugins",
"description": "A plugin is an installed module that makes filters and actions available for the workflow feature."
},
{
"name": "Queues",
"description": "Queues and background jobs are used for processing tasks asynchronously. Queues can be paused and resumed as needed."
},
{
"name": "Search",
"description": "Endpoints related to searching assets via text, smart search, optical character recognition (OCR), and other filters like person, album, and other metadata. Search endpoints usually support pagination and sorting."
@@ -16291,6 +16604,66 @@
],
"type": "object"
},
"JobName": {
"enum": [
"AssetDelete",
"AssetDeleteCheck",
"AssetDetectFacesQueueAll",
"AssetDetectFaces",
"AssetDetectDuplicatesQueueAll",
"AssetDetectDuplicates",
"AssetEncodeVideoQueueAll",
"AssetEncodeVideo",
"AssetEmptyTrash",
"AssetExtractMetadataQueueAll",
"AssetExtractMetadata",
"AssetFileMigration",
"AssetGenerateThumbnailsQueueAll",
"AssetGenerateThumbnails",
"AuditLogCleanup",
"AuditTableCleanup",
"DatabaseBackup",
"FacialRecognitionQueueAll",
"FacialRecognition",
"FileDelete",
"FileMigrationQueueAll",
"LibraryDeleteCheck",
"LibraryDelete",
"LibraryRemoveAsset",
"LibraryScanAssetsQueueAll",
"LibrarySyncAssets",
"LibrarySyncFilesQueueAll",
"LibrarySyncFiles",
"LibraryScanQueueAll",
"MemoryCleanup",
"MemoryGenerate",
"NotificationsCleanup",
"NotifyUserSignup",
"NotifyAlbumInvite",
"NotifyAlbumUpdate",
"UserDelete",
"UserDeleteCheck",
"UserSyncUsage",
"PersonCleanup",
"PersonFileMigration",
"PersonGenerateThumbnail",
"SessionCleanup",
"SendMail",
"SidecarQueueAll",
"SidecarCheck",
"SidecarWrite",
"SmartSearchQueueAll",
"SmartSearch",
"StorageTemplateMigration",
"StorageTemplateMigrationSingle",
"TagCleanup",
"VersionCheck",
"OcrQueueAll",
"Ocr",
"WorkflowRun"
],
"type": "string"
},
"JobSettingsDto": {
"properties": {
"concurrency": {
@@ -17583,6 +17956,12 @@
"userProfileImage.read",
"userProfileImage.update",
"userProfileImage.delete",
"queue.read",
"queue.update",
"queueJob.create",
"queueJob.read",
"queueJob.update",
"queueJob.delete",
"workflow.create",
"workflow.read",
"workflow.update",
@@ -18083,6 +18462,63 @@
],
"type": "object"
},
"QueueDeleteDto": {
"properties": {
"failed": {
"description": "If true, will also remove failed jobs from the queue.",
"type": "boolean",
"x-immich-history": [
{
"version": "v2.4.0",
"state": "Added"
},
{
"version": "v2.4.0",
"state": "Alpha"
}
],
"x-immich-state": "Alpha"
}
},
"type": "object"
},
"QueueJobResponseDto": {
"properties": {
"data": {
"type": "object"
},
"id": {
"type": "string"
},
"name": {
"allOf": [
{
"$ref": "#/components/schemas/JobName"
}
]
},
"timestamp": {
"type": "integer"
}
},
"required": [
"data",
"name",
"timestamp"
],
"type": "object"
},
"QueueJobStatus": {
"enum": [
"active",
"failed",
"completed",
"delayed",
"waiting",
"paused"
],
"type": "string"
},
"QueueName": {
"enum": [
"thumbnailGeneration",
@@ -18106,12 +18542,35 @@
"type": "string"
},
"QueueResponseDto": {
"properties": {
"isPaused": {
"type": "boolean"
},
"name": {
"allOf": [
{
"$ref": "#/components/schemas/QueueName"
}
]
},
"statistics": {
"$ref": "#/components/schemas/QueueStatisticsDto"
}
},
"required": [
"isPaused",
"name",
"statistics"
],
"type": "object"
},
"QueueResponseLegacyDto": {
"properties": {
"jobCounts": {
"$ref": "#/components/schemas/QueueStatisticsDto"
},
"queueStatus": {
"$ref": "#/components/schemas/QueueStatusDto"
"$ref": "#/components/schemas/QueueStatusLegacyDto"
}
},
"required": [
@@ -18151,7 +18610,7 @@
],
"type": "object"
},
"QueueStatusDto": {
"QueueStatusLegacyDto": {
"properties": {
"isActive": {
"type": "boolean"
@@ -18166,58 +18625,66 @@
],
"type": "object"
},
"QueuesResponseDto": {
"QueueUpdateDto": {
"properties": {
"isPaused": {
"type": "boolean"
}
},
"type": "object"
},
"QueuesResponseLegacyDto": {
"properties": {
"backgroundTask": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"backupDatabase": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"duplicateDetection": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"faceDetection": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"facialRecognition": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"library": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"metadataExtraction": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"migration": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"notifications": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"ocr": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"search": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"sidecar": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"smartSearch": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"storageTemplateMigration": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"thumbnailGeneration": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"videoConversion": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"workflow": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
}
},
"required": [

View File

@@ -1 +1 @@
24.11.0
24.11.1

View File

@@ -19,7 +19,7 @@
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
"@types/node": "^22.19.1",
"@types/node": "^24.10.1",
"typescript": "^5.3.3"
},
"repository": {
@@ -28,6 +28,6 @@
"directory": "open-api/typescript-sdk"
},
"volta": {
"node": "24.11.0"
"node": "24.11.1"
}
}

View File

@@ -716,32 +716,32 @@ export type QueueStatisticsDto = {
paused: number;
waiting: number;
};
export type QueueStatusDto = {
export type QueueStatusLegacyDto = {
isActive: boolean;
isPaused: boolean;
};
export type QueueResponseDto = {
export type QueueResponseLegacyDto = {
jobCounts: QueueStatisticsDto;
queueStatus: QueueStatusDto;
queueStatus: QueueStatusLegacyDto;
};
export type QueuesResponseDto = {
backgroundTask: QueueResponseDto;
backupDatabase: QueueResponseDto;
duplicateDetection: QueueResponseDto;
faceDetection: QueueResponseDto;
facialRecognition: QueueResponseDto;
library: QueueResponseDto;
metadataExtraction: QueueResponseDto;
migration: QueueResponseDto;
notifications: QueueResponseDto;
ocr: QueueResponseDto;
search: QueueResponseDto;
sidecar: QueueResponseDto;
smartSearch: QueueResponseDto;
storageTemplateMigration: QueueResponseDto;
thumbnailGeneration: QueueResponseDto;
videoConversion: QueueResponseDto;
workflow: QueueResponseDto;
export type QueuesResponseLegacyDto = {
backgroundTask: QueueResponseLegacyDto;
backupDatabase: QueueResponseLegacyDto;
duplicateDetection: QueueResponseLegacyDto;
faceDetection: QueueResponseLegacyDto;
facialRecognition: QueueResponseLegacyDto;
library: QueueResponseLegacyDto;
metadataExtraction: QueueResponseLegacyDto;
migration: QueueResponseLegacyDto;
notifications: QueueResponseLegacyDto;
ocr: QueueResponseLegacyDto;
search: QueueResponseLegacyDto;
sidecar: QueueResponseLegacyDto;
smartSearch: QueueResponseLegacyDto;
storageTemplateMigration: QueueResponseLegacyDto;
thumbnailGeneration: QueueResponseLegacyDto;
videoConversion: QueueResponseLegacyDto;
workflow: QueueResponseLegacyDto;
};
export type JobCreateDto = {
name: ManualJobName;
@@ -966,6 +966,24 @@ export type PluginResponseDto = {
updatedAt: string;
version: string;
};
export type QueueResponseDto = {
isPaused: boolean;
name: QueueName;
statistics: QueueStatisticsDto;
};
export type QueueUpdateDto = {
isPaused?: boolean;
};
export type QueueDeleteDto = {
/** If true, will also remove failed jobs from the queue. */
failed?: boolean;
};
export type QueueJobResponseDto = {
data: object;
id?: string;
name: JobName;
timestamp: number;
};
export type SearchExploreItem = {
data: AssetResponseDto;
value: string;
@@ -2925,7 +2943,7 @@ export function reassignFacesById({ id, faceDto }: {
export function getQueuesLegacy(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueuesResponseDto;
data: QueuesResponseLegacyDto;
}>("/jobs", {
...opts
}));
@@ -2951,7 +2969,7 @@ export function runQueueCommandLegacy({ name, queueCommandDto }: {
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueResponseDto;
data: QueueResponseLegacyDto;
}>(`/jobs/${encodeURIComponent(name)}`, oazapfts.json({
...opts,
method: "PUT",
@@ -3651,6 +3669,75 @@ export function getPlugin({ id }: {
...opts
}));
}
/**
* List all queues
*/
export function getQueues(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueResponseDto[];
}>("/queues", {
...opts
}));
}
/**
* Retrieve a queue
*/
export function getQueue({ name }: {
name: QueueName;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueResponseDto;
}>(`/queues/${encodeURIComponent(name)}`, {
...opts
}));
}
/**
* Update a queue
*/
export function updateQueue({ name, queueUpdateDto }: {
name: QueueName;
queueUpdateDto: QueueUpdateDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueResponseDto;
}>(`/queues/${encodeURIComponent(name)}`, oazapfts.json({
...opts,
method: "PUT",
body: queueUpdateDto
})));
}
/**
* Empty a queue
*/
export function emptyQueue({ name, queueDeleteDto }: {
name: QueueName;
queueDeleteDto: QueueDeleteDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/queues/${encodeURIComponent(name)}/jobs`, oazapfts.json({
...opts,
method: "DELETE",
body: queueDeleteDto
})));
}
/**
* Retrieve queue jobs
*/
export function getQueueJobs({ name, status }: {
name: QueueName;
status?: QueueJobStatus[];
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueJobResponseDto[];
}>(`/queues/${encodeURIComponent(name)}/jobs${QS.query(QS.explode({
status
}))}`, {
...opts
}));
}
/**
* Retrieve assets by city
*/
@@ -5241,6 +5328,12 @@ export enum Permission {
UserProfileImageRead = "userProfileImage.read",
UserProfileImageUpdate = "userProfileImage.update",
UserProfileImageDelete = "userProfileImage.delete",
QueueRead = "queue.read",
QueueUpdate = "queue.update",
QueueJobCreate = "queueJob.create",
QueueJobRead = "queueJob.read",
QueueJobUpdate = "queueJob.update",
QueueJobDelete = "queueJob.delete",
WorkflowCreate = "workflow.create",
WorkflowRead = "workflow.read",
WorkflowUpdate = "workflow.update",
@@ -5330,6 +5423,71 @@ export enum PluginContext {
Album = "album",
Person = "person"
}
export enum QueueJobStatus {
Active = "active",
Failed = "failed",
Completed = "completed",
Delayed = "delayed",
Waiting = "waiting",
Paused = "paused"
}
export enum JobName {
AssetDelete = "AssetDelete",
AssetDeleteCheck = "AssetDeleteCheck",
AssetDetectFacesQueueAll = "AssetDetectFacesQueueAll",
AssetDetectFaces = "AssetDetectFaces",
AssetDetectDuplicatesQueueAll = "AssetDetectDuplicatesQueueAll",
AssetDetectDuplicates = "AssetDetectDuplicates",
AssetEncodeVideoQueueAll = "AssetEncodeVideoQueueAll",
AssetEncodeVideo = "AssetEncodeVideo",
AssetEmptyTrash = "AssetEmptyTrash",
AssetExtractMetadataQueueAll = "AssetExtractMetadataQueueAll",
AssetExtractMetadata = "AssetExtractMetadata",
AssetFileMigration = "AssetFileMigration",
AssetGenerateThumbnailsQueueAll = "AssetGenerateThumbnailsQueueAll",
AssetGenerateThumbnails = "AssetGenerateThumbnails",
AuditLogCleanup = "AuditLogCleanup",
AuditTableCleanup = "AuditTableCleanup",
DatabaseBackup = "DatabaseBackup",
FacialRecognitionQueueAll = "FacialRecognitionQueueAll",
FacialRecognition = "FacialRecognition",
FileDelete = "FileDelete",
FileMigrationQueueAll = "FileMigrationQueueAll",
LibraryDeleteCheck = "LibraryDeleteCheck",
LibraryDelete = "LibraryDelete",
LibraryRemoveAsset = "LibraryRemoveAsset",
LibraryScanAssetsQueueAll = "LibraryScanAssetsQueueAll",
LibrarySyncAssets = "LibrarySyncAssets",
LibrarySyncFilesQueueAll = "LibrarySyncFilesQueueAll",
LibrarySyncFiles = "LibrarySyncFiles",
LibraryScanQueueAll = "LibraryScanQueueAll",
MemoryCleanup = "MemoryCleanup",
MemoryGenerate = "MemoryGenerate",
NotificationsCleanup = "NotificationsCleanup",
NotifyUserSignup = "NotifyUserSignup",
NotifyAlbumInvite = "NotifyAlbumInvite",
NotifyAlbumUpdate = "NotifyAlbumUpdate",
UserDelete = "UserDelete",
UserDeleteCheck = "UserDeleteCheck",
UserSyncUsage = "UserSyncUsage",
PersonCleanup = "PersonCleanup",
PersonFileMigration = "PersonFileMigration",
PersonGenerateThumbnail = "PersonGenerateThumbnail",
SessionCleanup = "SessionCleanup",
SendMail = "SendMail",
SidecarQueueAll = "SidecarQueueAll",
SidecarCheck = "SidecarCheck",
SidecarWrite = "SidecarWrite",
SmartSearchQueueAll = "SmartSearchQueueAll",
SmartSearch = "SmartSearch",
StorageTemplateMigration = "StorageTemplateMigration",
StorageTemplateMigrationSingle = "StorageTemplateMigrationSingle",
TagCleanup = "TagCleanup",
VersionCheck = "VersionCheck",
OcrQueueAll = "OcrQueueAll",
Ocr = "Ocr",
WorkflowRun = "WorkflowRun"
}
export enum SearchSuggestionType {
Country = "country",
State = "state",

View File

@@ -3,7 +3,7 @@
"version": "0.0.1",
"description": "Monorepo for Immich",
"private": true,
"packageManager": "pnpm@10.20.0+sha512.cf9998222162dd85864d0a8102e7892e7ba4ceadebbf5a31f9c2fce48dfce317a9c53b9f6464d1ef9042cba2e02ae02a9f7c143a2b438cd93c91840f0192b9dd",
"packageManager": "pnpm@10.22.0+sha512.bf049efe995b28f527fd2b41ae0474ce29186f7edcb3bf545087bd61fbbebb2bf75362d1307fda09c2d288e1e499787ac12d4fcb617a974718a6051f2eee741c",
"engines": {
"pnpm": ">=10.0.0"
}

View File

@@ -10,14 +10,14 @@
"license": "AGPL-3.0",
"devDependencies": {
"@extism/js-pdk": "^1.0.1",
"esbuild": "^0.25.0",
"esbuild": "^0.27.0",
"typescript": "^5.3.2"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz",
"integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==",
"cpu": [
"ppc64"
],
@@ -32,9 +32,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz",
"integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==",
"cpu": [
"arm"
],
@@ -49,9 +49,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz",
"integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==",
"cpu": [
"arm64"
],
@@ -66,9 +66,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz",
"integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==",
"cpu": [
"x64"
],
@@ -83,9 +83,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz",
"integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==",
"cpu": [
"arm64"
],
@@ -100,9 +100,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz",
"integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==",
"cpu": [
"x64"
],
@@ -117,9 +117,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz",
"integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==",
"cpu": [
"arm64"
],
@@ -134,9 +134,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz",
"integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==",
"cpu": [
"x64"
],
@@ -151,9 +151,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz",
"integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==",
"cpu": [
"arm"
],
@@ -168,9 +168,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz",
"integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==",
"cpu": [
"arm64"
],
@@ -185,9 +185,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz",
"integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==",
"cpu": [
"ia32"
],
@@ -202,9 +202,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz",
"integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==",
"cpu": [
"loong64"
],
@@ -219,9 +219,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz",
"integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==",
"cpu": [
"mips64el"
],
@@ -236,9 +236,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz",
"integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==",
"cpu": [
"ppc64"
],
@@ -253,9 +253,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz",
"integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==",
"cpu": [
"riscv64"
],
@@ -270,9 +270,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz",
"integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==",
"cpu": [
"s390x"
],
@@ -287,9 +287,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz",
"integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==",
"cpu": [
"x64"
],
@@ -304,9 +304,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz",
"integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==",
"cpu": [
"arm64"
],
@@ -321,9 +321,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz",
"integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==",
"cpu": [
"x64"
],
@@ -338,9 +338,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz",
"integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==",
"cpu": [
"arm64"
],
@@ -355,9 +355,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz",
"integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==",
"cpu": [
"x64"
],
@@ -372,9 +372,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz",
"integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==",
"cpu": [
"arm64"
],
@@ -389,9 +389,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz",
"integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==",
"cpu": [
"x64"
],
@@ -406,9 +406,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz",
"integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==",
"cpu": [
"arm64"
],
@@ -423,9 +423,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz",
"integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==",
"cpu": [
"ia32"
],
@@ -440,9 +440,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz",
"integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==",
"cpu": [
"x64"
],
@@ -457,15 +457,19 @@
}
},
"node_modules/@extism/js-pdk": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@extism/js-pdk/-/js-pdk-1.0.1.tgz",
"integrity": "sha512-YJWfHGeOuJnQw4V8NPNHvbSr6S8iDd2Ga6VEukwlRP7tu62ozTxIgokYw8i+rajD/16zz/gK0KYARBpm2qPAmQ==",
"dev": true
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@extism/js-pdk/-/js-pdk-1.1.1.tgz",
"integrity": "sha512-VZLn/dX0ttA1uKk2PZeR/FL3N+nA1S5Vc7E5gdjkR60LuUIwCZT9cYON245V4HowHlBA7YOegh0TLjkx+wNbrA==",
"dev": true,
"license": "BSD-Clause-3",
"dependencies": {
"urlpattern-polyfill": "^8.0.2"
}
},
"node_modules/esbuild": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz",
"integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -476,39 +480,40 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.12",
"@esbuild/android-arm": "0.25.12",
"@esbuild/android-arm64": "0.25.12",
"@esbuild/android-x64": "0.25.12",
"@esbuild/darwin-arm64": "0.25.12",
"@esbuild/darwin-x64": "0.25.12",
"@esbuild/freebsd-arm64": "0.25.12",
"@esbuild/freebsd-x64": "0.25.12",
"@esbuild/linux-arm": "0.25.12",
"@esbuild/linux-arm64": "0.25.12",
"@esbuild/linux-ia32": "0.25.12",
"@esbuild/linux-loong64": "0.25.12",
"@esbuild/linux-mips64el": "0.25.12",
"@esbuild/linux-ppc64": "0.25.12",
"@esbuild/linux-riscv64": "0.25.12",
"@esbuild/linux-s390x": "0.25.12",
"@esbuild/linux-x64": "0.25.12",
"@esbuild/netbsd-arm64": "0.25.12",
"@esbuild/netbsd-x64": "0.25.12",
"@esbuild/openbsd-arm64": "0.25.12",
"@esbuild/openbsd-x64": "0.25.12",
"@esbuild/openharmony-arm64": "0.25.12",
"@esbuild/sunos-x64": "0.25.12",
"@esbuild/win32-arm64": "0.25.12",
"@esbuild/win32-ia32": "0.25.12",
"@esbuild/win32-x64": "0.25.12"
"@esbuild/aix-ppc64": "0.27.0",
"@esbuild/android-arm": "0.27.0",
"@esbuild/android-arm64": "0.27.0",
"@esbuild/android-x64": "0.27.0",
"@esbuild/darwin-arm64": "0.27.0",
"@esbuild/darwin-x64": "0.27.0",
"@esbuild/freebsd-arm64": "0.27.0",
"@esbuild/freebsd-x64": "0.27.0",
"@esbuild/linux-arm": "0.27.0",
"@esbuild/linux-arm64": "0.27.0",
"@esbuild/linux-ia32": "0.27.0",
"@esbuild/linux-loong64": "0.27.0",
"@esbuild/linux-mips64el": "0.27.0",
"@esbuild/linux-ppc64": "0.27.0",
"@esbuild/linux-riscv64": "0.27.0",
"@esbuild/linux-s390x": "0.27.0",
"@esbuild/linux-x64": "0.27.0",
"@esbuild/netbsd-arm64": "0.27.0",
"@esbuild/netbsd-x64": "0.27.0",
"@esbuild/openbsd-arm64": "0.27.0",
"@esbuild/openbsd-x64": "0.27.0",
"@esbuild/openharmony-arm64": "0.27.0",
"@esbuild/sunos-x64": "0.27.0",
"@esbuild/win32-arm64": "0.27.0",
"@esbuild/win32-ia32": "0.27.0",
"@esbuild/win32-x64": "0.27.0"
}
},
"node_modules/typescript": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -516,6 +521,13 @@
"engines": {
"node": ">=14.17"
}
},
"node_modules/urlpattern-polyfill": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz",
"integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==",
"dev": true,
"license": "MIT"
}
}
}

View File

@@ -13,7 +13,7 @@
"license": "AGPL-3.0",
"devDependencies": {
"@extism/js-pdk": "^1.0.1",
"esbuild": "^0.25.0",
"esbuild": "^0.27.0",
"typescript": "^5.3.2"
}
}

4274
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,7 @@ onlyBuiltDependencies:
- bcrypt
overrides:
canvas: 2.11.2
sharp: ^0.34.4
sharp: ^0.34.5
packageExtensions:
nestjs-kysely:
dependencies:

View File

@@ -1 +1 @@
24.11.0
24.11.1

View File

@@ -1,4 +1,4 @@
FROM ghcr.io/immich-app/base-server-dev:202511181104@sha256:fd445b91d4db131aae71b143b647d2262818dac80946078ce231c79cb9acecba AS builder
FROM ghcr.io/immich-app/base-server-dev:202510092146@sha256:124ec9659cba4a206924de5e3691f84acde16d75fa2b10b7007542424b696b96 AS builder
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
CI=1 \
COREPACK_HOME=/tmp \
@@ -69,7 +69,7 @@ RUN --mount=type=cache,id=pnpm-plugins,target=/buildcache/pnpm-store \
--mount=type=cache,id=mise-tools,target=/buildcache/mise \
cd plugins && mise run build
FROM ghcr.io/immich-app/base-server-prod:202511181104@sha256:1bc2b7cebc4fd3296dc33a5779411e1c7d854ea713066c1e024d54f45f176f89
FROM ghcr.io/immich-app/base-server-prod:202510092146@sha256:c39b9ad949e7777bce415e6931334aeff7331e04cb7f9df93f9ae44f6ff36b9e
WORKDIR /usr/src/app
ENV NODE_ENV=production \

View File

@@ -1,5 +1,5 @@
# dev build
FROM ghcr.io/immich-app/base-server-dev:202511181104@sha256:fd445b91d4db131aae71b143b647d2262818dac80946078ce231c79cb9acecba AS dev
FROM ghcr.io/immich-app/base-server-dev:202511261514@sha256:cbcca5851fd11042463f09797e6d6068d94adbb108749e62aa69159df59c0591 AS dev
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
CI=1 \
@@ -44,18 +44,18 @@ FROM dev-container-server AS dev-container-mobile
USER root
# Enable multiarch for arm64 if necessary
RUN if [ "$(dpkg --print-architecture)" = "arm64" ]; then \
dpkg --add-architecture amd64 && \
apt-get update && \
apt-get install -y --no-install-recommends \
gnupg \
qemu-user-static \
libc6:amd64 \
libstdc++6:amd64 \
libgcc1:amd64; \
dpkg --add-architecture amd64 && \
apt-get update && \
apt-get install -y --no-install-recommends \
gnupg \
qemu-user-static \
libc6:amd64 \
libstdc++6:amd64 \
libgcc1:amd64; \
else \
apt-get update && \
apt-get install -y --no-install-recommends \
gnupg; \
apt-get update && \
apt-get install -y --no-install-recommends \
gnupg; \
fi
# Flutter SDK

View File

@@ -70,7 +70,7 @@
"cookie": "^1.0.2",
"cookie-parser": "^1.4.7",
"cron": "4.3.3",
"exiftool-vendored": "^31.1.0",
"exiftool-vendored": "^28.8.0",
"express": "^5.1.0",
"fast-glob": "^3.3.2",
"fluent-ffmpeg": "^2.1.2",
@@ -120,7 +120,7 @@
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.4",
"@swc/core": "^1.4.14",
"@types/archiver": "^6.0.0",
"@types/archiver": "^7.0.0",
"@types/async-lock": "^1.4.2",
"@types/bcrypt": "^6.0.0",
"@types/body-parser": "^1.19.6",
@@ -134,7 +134,7 @@
"@types/luxon": "^3.6.2",
"@types/mock-fs": "^4.13.1",
"@types/multer": "^2.0.0",
"@types/node": "^22.19.1",
"@types/node": "^24.10.1",
"@types/nodemailer": "^7.0.0",
"@types/picomatch": "^4.0.0",
"@types/pngjs": "^6.0.5",
@@ -148,10 +148,10 @@
"eslint": "^9.14.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^60.0.0",
"eslint-plugin-unicorn": "^62.0.0",
"globals": "^16.0.0",
"mock-fs": "^5.2.0",
"node-gyp": "^11.2.0",
"node-gyp": "^12.0.0",
"pngjs": "^7.0.0",
"prettier": "^3.0.2",
"prettier-plugin-organize-imports": "^4.0.0",
@@ -166,9 +166,9 @@
"vitest": "^3.0.0"
},
"volta": {
"node": "24.11.0"
"node": "24.11.1"
},
"overrides": {
"sharp": "^0.34.4"
"sharp": "^0.34.5"
}
}

View File

@@ -163,6 +163,8 @@ export const endpointTags: Record<ApiTag, string> = {
'A person is a collection of faces, which can be favorited and named. A person can also be merged into another person. People are automatically created via the face recognition job.',
[ApiTag.Plugins]:
'A plugin is an installed module that makes filters and actions available for the workflow feature.',
[ApiTag.Queues]:
'Queues and background jobs are used for processing tasks asynchronously. Queues can be paused and resumed as needed.',
[ApiTag.Search]:
'Endpoints related to searching assets via text, smart search, optical character recognition (OCR), and other filters like person, album, and other metadata. Search endpoints usually support pagination and sorting.',
[ApiTag.Server]:

View File

@@ -20,6 +20,7 @@ import { OAuthController } from 'src/controllers/oauth.controller';
import { PartnerController } from 'src/controllers/partner.controller';
import { PersonController } from 'src/controllers/person.controller';
import { PluginController } from 'src/controllers/plugin.controller';
import { QueueController } from 'src/controllers/queue.controller';
import { SearchController } from 'src/controllers/search.controller';
import { ServerController } from 'src/controllers/server.controller';
import { SessionController } from 'src/controllers/session.controller';
@@ -59,6 +60,7 @@ export const controllers = [
PartnerController,
PersonController,
PluginController,
QueueController,
SearchController,
ServerController,
SessionController,

View File

@@ -1,10 +1,12 @@
import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import { AuthDto } from 'src/dtos/auth.dto';
import { JobCreateDto } from 'src/dtos/job.dto';
import { QueueCommandDto, QueueNameParamDto, QueueResponseDto, QueuesResponseDto } from 'src/dtos/queue.dto';
import { QueueResponseLegacyDto, QueuesResponseLegacyDto } from 'src/dtos/queue-legacy.dto';
import { QueueCommandDto, QueueNameParamDto } from 'src/dtos/queue.dto';
import { ApiTag, Permission } from 'src/enum';
import { Authenticated } from 'src/middleware/auth.guard';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { JobService } from 'src/services/job.service';
import { QueueService } from 'src/services/queue.service';
@@ -21,10 +23,10 @@ export class JobController {
@Endpoint({
summary: 'Retrieve queue counts and status',
description: 'Retrieve the counts of the current queue, as well as the current status.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
history: new HistoryBuilder().added('v1').beta('v1').stable('v2').deprecated('v2.4.0'),
})
getQueuesLegacy(): Promise<QueuesResponseDto> {
return this.queueService.getAll();
getQueuesLegacy(@Auth() auth: AuthDto): Promise<QueuesResponseLegacyDto> {
return this.queueService.getAllLegacy(auth);
}
@Post()
@@ -46,9 +48,12 @@ export class JobController {
summary: 'Run jobs',
description:
'Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
history: new HistoryBuilder().added('v1').beta('v1').stable('v2').deprecated('v2.4.0'),
})
runQueueCommandLegacy(@Param() { name }: QueueNameParamDto, @Body() dto: QueueCommandDto): Promise<QueueResponseDto> {
return this.queueService.runCommand(name, dto);
runQueueCommandLegacy(
@Param() { name }: QueueNameParamDto,
@Body() dto: QueueCommandDto,
): Promise<QueueResponseLegacyDto> {
return this.queueService.runCommandLegacy(name, dto);
}
}

View File

@@ -0,0 +1,85 @@
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Put, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import { AuthDto } from 'src/dtos/auth.dto';
import {
QueueDeleteDto,
QueueJobResponseDto,
QueueJobSearchDto,
QueueNameParamDto,
QueueResponseDto,
QueueUpdateDto,
} from 'src/dtos/queue.dto';
import { ApiTag, Permission } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { QueueService } from 'src/services/queue.service';
@ApiTags(ApiTag.Queues)
@Controller('queues')
export class QueueController {
constructor(private service: QueueService) {}
@Get()
@Authenticated({ permission: Permission.QueueRead, admin: true })
@Endpoint({
summary: 'List all queues',
description: 'Retrieves a list of queues.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
getQueues(@Auth() auth: AuthDto): Promise<QueueResponseDto[]> {
return this.service.getAll(auth);
}
@Get(':name')
@Authenticated({ permission: Permission.QueueRead, admin: true })
@Endpoint({
summary: 'Retrieve a queue',
description: 'Retrieves a specific queue by its name.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
getQueue(@Auth() auth: AuthDto, @Param() { name }: QueueNameParamDto): Promise<QueueResponseDto> {
return this.service.get(auth, name);
}
@Put(':name')
@Authenticated({ permission: Permission.QueueUpdate, admin: true })
@Endpoint({
summary: 'Update a queue',
description: 'Change the paused status of a specific queue.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
updateQueue(
@Auth() auth: AuthDto,
@Param() { name }: QueueNameParamDto,
@Body() dto: QueueUpdateDto,
): Promise<QueueResponseDto> {
return this.service.update(auth, name, dto);
}
@Get(':name/jobs')
@Authenticated({ permission: Permission.QueueJobRead, admin: true })
@Endpoint({
summary: 'Retrieve queue jobs',
description: 'Retrieves a list of queue jobs from the specified queue.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
getQueueJobs(
@Auth() auth: AuthDto,
@Param() { name }: QueueNameParamDto,
@Query() dto: QueueJobSearchDto,
): Promise<QueueJobResponseDto[]> {
return this.service.searchJobs(auth, name, dto);
}
@Delete(':name/jobs')
@Authenticated({ permission: Permission.QueueJobDelete, admin: true })
@HttpCode(HttpStatus.NO_CONTENT)
@Endpoint({
summary: 'Empty a queue',
description: 'Removes all jobs from the specified queue.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
emptyQueue(@Auth() auth: AuthDto, @Param() { name }: QueueNameParamDto, @Body() dto: QueueDeleteDto): Promise<void> {
return this.service.emptyQueue(auth, name, dto);
}
}

View File

@@ -0,0 +1,89 @@
import { ApiProperty } from '@nestjs/swagger';
import { QueueResponseDto, QueueStatisticsDto } from 'src/dtos/queue.dto';
import { QueueName } from 'src/enum';
export class QueueStatusLegacyDto {
isActive!: boolean;
isPaused!: boolean;
}
export class QueueResponseLegacyDto {
@ApiProperty({ type: QueueStatusLegacyDto })
queueStatus!: QueueStatusLegacyDto;
@ApiProperty({ type: QueueStatisticsDto })
jobCounts!: QueueStatisticsDto;
}
export class QueuesResponseLegacyDto implements Record<QueueName, QueueResponseLegacyDto> {
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.ThumbnailGeneration]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.MetadataExtraction]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.VideoConversion]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.SmartSearch]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.StorageTemplateMigration]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Migration]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.BackgroundTask]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Search]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.DuplicateDetection]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.FaceDetection]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.FacialRecognition]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Sidecar]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Library]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Notification]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.BackupDatabase]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Ocr]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Workflow]!: QueueResponseLegacyDto;
}
export const mapQueueLegacy = (response: QueueResponseDto): QueueResponseLegacyDto => {
return {
queueStatus: {
isPaused: response.isPaused,
isActive: response.statistics.active > 0,
},
jobCounts: response.statistics,
};
};
export const mapQueuesLegacy = (responses: QueueResponseDto[]): QueuesResponseLegacyDto => {
const legacy = new QueuesResponseLegacyDto();
for (const response of responses) {
legacy[response.name] = mapQueueLegacy(response);
}
return legacy;
};

View File

@@ -1,5 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { QueueCommand, QueueName } from 'src/enum';
import { HistoryBuilder, Property } from 'src/decorators';
import { JobName, QueueCommand, QueueJobStatus, QueueName } from 'src/enum';
import { ValidateBoolean, ValidateEnum } from 'src/validation';
export class QueueNameParamDto {
@@ -15,6 +16,46 @@ export class QueueCommandDto {
force?: boolean; // TODO: this uses undefined as a third state, which should be refactored to be more explicit
}
export class QueueUpdateDto {
@ValidateBoolean({ optional: true })
isPaused?: boolean;
}
export class QueueDeleteDto {
@ValidateBoolean({ optional: true })
@Property({
description: 'If true, will also remove failed jobs from the queue.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
failed?: boolean;
}
export class QueueJobSearchDto {
@ValidateEnum({ enum: QueueJobStatus, name: 'QueueJobStatus', optional: true, each: true })
status?: QueueJobStatus[];
}
export class QueueJobResponseDto {
id?: string;
@ValidateEnum({ enum: JobName, name: 'JobName' })
name!: JobName;
data!: object;
@ApiProperty({ type: 'integer' })
timestamp!: number;
}
export class QueueResponseDto {
@ValidateEnum({ enum: QueueName, name: 'QueueName' })
name!: QueueName;
@ValidateBoolean()
isPaused!: boolean;
statistics!: QueueStatisticsDto;
}
export class QueueStatisticsDto {
@ApiProperty({ type: 'integer' })
active!: number;
@@ -29,69 +70,3 @@ export class QueueStatisticsDto {
@ApiProperty({ type: 'integer' })
paused!: number;
}
export class QueueStatusDto {
isActive!: boolean;
isPaused!: boolean;
}
export class QueueResponseDto {
@ApiProperty({ type: QueueStatisticsDto })
jobCounts!: QueueStatisticsDto;
@ApiProperty({ type: QueueStatusDto })
queueStatus!: QueueStatusDto;
}
export class QueuesResponseDto implements Record<QueueName, QueueResponseDto> {
@ApiProperty({ type: QueueResponseDto })
[QueueName.ThumbnailGeneration]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.MetadataExtraction]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.VideoConversion]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.SmartSearch]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.StorageTemplateMigration]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Migration]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.BackgroundTask]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Search]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.DuplicateDetection]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.FaceDetection]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.FacialRecognition]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Sidecar]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Library]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Notification]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.BackupDatabase]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Ocr]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Workflow]!: QueueResponseDto;
}

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