Compare commits
171 Commits
feat/serve
...
v1.113.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc88cbb456 | ||
|
|
860ba78650 | ||
|
|
9b1a985d29 | ||
|
|
b9e5e40ced | ||
|
|
3316acb71f | ||
|
|
1736887f96 | ||
|
|
c40262f3ff | ||
|
|
b3b599e071 | ||
|
|
b1e780561d | ||
|
|
aa04ded311 | ||
|
|
fa9b2219f8 | ||
|
|
7d0c64b73e | ||
|
|
48fb0f309d | ||
|
|
8c54312c87 | ||
|
|
eb4a291c81 | ||
|
|
c63f63cc15 | ||
|
|
715ac4c599 | ||
|
|
6fe011e2d7 | ||
|
|
ebecb60f39 | ||
|
|
9bfaa525db | ||
|
|
74f18a4523 | ||
|
|
d08a20bd57 | ||
|
|
682adaa334 | ||
|
|
c008feca63 | ||
|
|
f3e176e192 | ||
|
|
9f5a3f1e84 | ||
|
|
bab5ad7ebd | ||
|
|
c6c7c54fa5 | ||
|
|
f0c86846e0 | ||
|
|
562fec6e2b | ||
|
|
363c558db7 | ||
|
|
5811025ebd | ||
|
|
7506eefee3 | ||
|
|
2297d86569 | ||
|
|
cc4e5298ff | ||
|
|
e705831e67 | ||
|
|
6867bae770 | ||
|
|
c44280a50b | ||
|
|
cf272fc7fd | ||
|
|
365facfc51 | ||
|
|
d8aec81ae0 | ||
|
|
1239066ada | ||
|
|
1fd00d8262 | ||
|
|
d4cdd590bd | ||
|
|
be476d7982 | ||
|
|
028be6738e | ||
|
|
72ab664936 | ||
|
|
98b3441cb1 | ||
|
|
0be3c4472f | ||
|
|
aac6a4b052 | ||
|
|
16d5996f77 | ||
|
|
3e970bc2d3 | ||
|
|
f70dcaa6cc | ||
|
|
b051b29eca | ||
|
|
9894b9513b | ||
|
|
6b6d2a6621 | ||
|
|
f4371578f5 | ||
|
|
edf47dbbd0 | ||
|
|
3ac42edc74 | ||
|
|
129e5eae66 | ||
|
|
fe672d4f35 | ||
|
|
4f02412493 | ||
|
|
96056208fc | ||
|
|
b2dd5a3152 | ||
|
|
b653a20d15 | ||
|
|
868aedd212 | ||
|
|
e457d8d62e | ||
|
|
b41af65997 | ||
|
|
7a4fccb1b2 | ||
|
|
843345df4f | ||
|
|
00a7b80184 | ||
|
|
da12d5f567 | ||
|
|
c14e2914f8 | ||
|
|
7fbf50a75e | ||
|
|
f69ce6ad8a | ||
|
|
296bbeb2fc | ||
|
|
c24cc8a33b | ||
|
|
837b1e4929 | ||
|
|
07538299cf | ||
|
|
6cf5906813 | ||
|
|
817bd2ee94 | ||
|
|
29d229c5ba | ||
|
|
fd225e7462 | ||
|
|
817f42aef7 | ||
|
|
3be1aaaaa4 | ||
|
|
ef9a06be5c | ||
|
|
cde0458dc8 | ||
|
|
8285803c95 | ||
|
|
c7801eae7e | ||
|
|
b60fa77846 | ||
|
|
8d89eba3a9 | ||
|
|
2fba9f9547 | ||
|
|
1d559431ba | ||
|
|
7af6733665 | ||
|
|
af3a793fe8 | ||
|
|
2237b7a399 | ||
|
|
d9698884bd | ||
|
|
8338657eaa | ||
|
|
ca52cbace1 | ||
|
|
bc31b7c06c | ||
|
|
fa7f1e656f | ||
|
|
036676d501 | ||
|
|
5ab92f346a | ||
|
|
bd42e05152 | ||
|
|
c9f1304bce | ||
|
|
5ef9a8ff8d | ||
|
|
0261f79c72 | ||
|
|
d61828598b | ||
|
|
e9bfe5418a | ||
|
|
f230b3aa42 | ||
|
|
a372b56d44 | ||
|
|
1c754b60dc | ||
|
|
c582a841ba | ||
|
|
433c7ab01d | ||
|
|
32c05ea950 | ||
|
|
ed6971222c | ||
|
|
00023e387f | ||
|
|
e51b581f6e | ||
|
|
f40a4fc1c8 | ||
|
|
3ab7438036 | ||
|
|
49610de4b3 | ||
|
|
a4506758aa | ||
|
|
b288241a5c | ||
|
|
fa64277476 | ||
|
|
f7bfde6a32 | ||
|
|
7d5f07d1c7 | ||
|
|
a38dd53afd | ||
|
|
44c26c20b6 | ||
|
|
fcec5f867c | ||
|
|
7d888106ed | ||
|
|
9e21f254cd | ||
|
|
da6f269008 | ||
|
|
228a7710e6 | ||
|
|
8014b0f86d | ||
|
|
fb962f49ea | ||
|
|
7f7fec2cea | ||
|
|
593f036c0d | ||
|
|
e934e368b3 | ||
|
|
f331a974ed | ||
|
|
9d09b95618 | ||
|
|
a8a63b24d0 | ||
|
|
ab0ed11778 | ||
|
|
5ec407b57c | ||
|
|
fdf0b16fe3 | ||
|
|
c924f6c27c | ||
|
|
df45ef0e35 | ||
|
|
81c813a882 | ||
|
|
b014162088 | ||
|
|
276101ee82 | ||
|
|
9837d60074 | ||
|
|
28b7443b92 | ||
|
|
5acdc958b6 | ||
|
|
e384692025 | ||
|
|
54b276c984 | ||
|
|
7eb004bd00 | ||
|
|
c2965c4408 | ||
|
|
b749a68349 | ||
|
|
30aa2c9b82 | ||
|
|
9ed04588b8 | ||
|
|
7d320217b9 | ||
|
|
efdf8bbca9 | ||
|
|
34c4fbf730 | ||
|
|
ca775ab3e9 | ||
|
|
2dd5514043 | ||
|
|
f33dbdfe9a | ||
|
|
b1587a5dee | ||
|
|
501485d0b1 | ||
|
|
ed7f857975 | ||
|
|
d346985457 | ||
|
|
a144a1bec3 | ||
|
|
9f318a9338 |
@@ -22,6 +22,7 @@ open-api/typescript-sdk/node_modules/
|
|||||||
server/coverage/
|
server/coverage/
|
||||||
server/node_modules/
|
server/node_modules/
|
||||||
server/upload/
|
server/upload/
|
||||||
|
server/src/queries
|
||||||
server/dist/
|
server/dist/
|
||||||
server/www/
|
server/www/
|
||||||
|
|
||||||
|
|||||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
custom: ['https://buy.immich.app']
|
||||||
1
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -83,7 +83,6 @@ body:
|
|||||||
2.
|
2.
|
||||||
3.
|
3.
|
||||||
...
|
...
|
||||||
render: bash
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
40
.github/release.yml
vendored
40
.github/release.yml
vendored
@@ -1,41 +1,29 @@
|
|||||||
changelog:
|
changelog:
|
||||||
categories:
|
categories:
|
||||||
- title: ⚠️ Breaking Changes
|
- title: 🚨 Breaking Changes
|
||||||
labels:
|
labels:
|
||||||
- breaking-change
|
- changelog:breaking-change
|
||||||
|
|
||||||
- title: 🗄️ Server
|
- title: 🔒 Security
|
||||||
labels:
|
labels:
|
||||||
- 🗄️server
|
- changelog:security
|
||||||
|
|
||||||
- title: 📱 Mobile
|
- title: 🚀 Features
|
||||||
labels:
|
labels:
|
||||||
- 📱mobile
|
- changelog:feature
|
||||||
|
|
||||||
- title: 🖥️ Web
|
- title: 🌟 Enhancements
|
||||||
labels:
|
labels:
|
||||||
- 🖥️web
|
- changelog:enhancement
|
||||||
|
|
||||||
- title: 🧠 Machine Learning
|
- title: 🐛 Bug fixes
|
||||||
labels:
|
labels:
|
||||||
- 🧠machine-learning
|
- changelog:bugfix
|
||||||
|
|
||||||
- title: ⚡ CLI
|
- title: 📚 Documentation
|
||||||
labels:
|
labels:
|
||||||
- cli
|
- changelog:documentation
|
||||||
|
|
||||||
- title: 📓 Documentation
|
- title: 🌐 Translations
|
||||||
labels:
|
labels:
|
||||||
- documentation
|
- changelog:translation
|
||||||
|
|
||||||
- title: 🔨 Maintenance
|
|
||||||
labels:
|
|
||||||
- deployment
|
|
||||||
- dependencies
|
|
||||||
- renovate
|
|
||||||
- maintenance
|
|
||||||
- tech-debt
|
|
||||||
|
|
||||||
- title: Other changes
|
|
||||||
labels:
|
|
||||||
- "*"
|
|
||||||
|
|||||||
2
.github/workflows/cli.yml
vendored
2
.github/workflows/cli.yml
vendored
@@ -88,7 +88,7 @@ jobs:
|
|||||||
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
|
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@v6.6.1
|
uses: docker/build-push-action@v6.7.0
|
||||||
with:
|
with:
|
||||||
file: cli/Dockerfile
|
file: cli/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|||||||
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -115,7 +115,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@v6.6.1
|
uses: docker/build-push-action@v6.7.0
|
||||||
with:
|
with:
|
||||||
context: ${{ matrix.context }}
|
context: ${{ matrix.context }}
|
||||||
file: ${{ matrix.file }}
|
file: ${{ matrix.file }}
|
||||||
|
|||||||
21
.github/workflows/pr-label-validation.yml
vendored
Normal file
21
.github/workflows/pr-label-validation.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: PR Label Validation
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, labeled, unlabeled, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-release-label:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: read
|
||||||
|
steps:
|
||||||
|
- name: Require PR to have a changelog label
|
||||||
|
uses: mheap/github-action-required-labels@v5
|
||||||
|
with:
|
||||||
|
mode: exactly
|
||||||
|
count: 1
|
||||||
|
use_regex: true
|
||||||
|
labels: "changelog:.*"
|
||||||
|
add_comment: true
|
||||||
15
.github/workflows/prepare-release.yml
vendored
15
.github/workflows/prepare-release.yml
vendored
@@ -29,10 +29,17 @@ jobs:
|
|||||||
ref: ${{ steps.push-tag.outputs.commit_long_sha }}
|
ref: ${{ steps.push-tag.outputs.commit_long_sha }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Generate a token
|
||||||
|
id: generate-token
|
||||||
|
uses: actions/create-github-app-token@v1
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.ORG_RELEASE_TOKEN }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Install Poetry
|
- name: Install Poetry
|
||||||
run: pipx install poetry
|
run: pipx install poetry
|
||||||
@@ -44,10 +51,8 @@ jobs:
|
|||||||
id: push-tag
|
id: push-tag
|
||||||
uses: EndBug/add-and-commit@v9
|
uses: EndBug/add-and-commit@v9
|
||||||
with:
|
with:
|
||||||
author_name: Alex The Bot
|
default_author: github_actions
|
||||||
author_email: alex.tran1502@gmail.com
|
message: 'chore: version ${{ env.IMMICH_VERSION }}'
|
||||||
default_author: user_info
|
|
||||||
message: 'Version ${{ env.IMMICH_VERSION }}'
|
|
||||||
tag: ${{ env.IMMICH_VERSION }}
|
tag: ${{ env.IMMICH_VERSION }}
|
||||||
push: true
|
push: true
|
||||||
|
|
||||||
|
|||||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,6 +1,6 @@
|
|||||||
[submodule "mobile/.isar"]
|
[submodule "mobile/.isar"]
|
||||||
path = mobile/.isar
|
path = mobile/.isar
|
||||||
url = https://github.com/isar/isar
|
url = https://github.com/isar/isar
|
||||||
[submodule "server/test/assets"]
|
[submodule "e2e/test-assets"]
|
||||||
path = e2e/test-assets
|
path = e2e/test-assets
|
||||||
url = https://github.com/immich-app/test-assets
|
url = https://github.com/immich-app/test-assets
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
20.16.0
|
20.17.0
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:20.16.0-alpine3.20@sha256:eb8101caae9ac02229bd64c024919fe3d4504ff7f329da79ca60a04db08cef52 AS core
|
FROM node:20.17.0-alpine3.20@sha256:1a526b97cace6b4006256570efa1a29cd1fe4b96a5301f8d48e87c5139438a45 AS core
|
||||||
|
|
||||||
WORKDIR /usr/src/open-api/typescript-sdk
|
WORKDIR /usr/src/open-api/typescript-sdk
|
||||||
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
|
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
|
||||||
|
|||||||
@@ -4,8 +4,18 @@ Please see the [Immich CLI documentation](https://immich.app/docs/features/comma
|
|||||||
|
|
||||||
# For developers
|
# For developers
|
||||||
|
|
||||||
|
Before building the CLI, you must build the immich server and the open-api client. To build the server run the following in the server folder:
|
||||||
|
|
||||||
|
$ npm install
|
||||||
|
$ npm run build
|
||||||
|
|
||||||
|
Then, to build the open-api client run the following in the open-api folder:
|
||||||
|
|
||||||
|
$ ./bin/generate-open-api.sh
|
||||||
|
|
||||||
To run the Immich CLI from source, run the following in the cli folder:
|
To run the Immich CLI from source, run the following in the cli folder:
|
||||||
|
|
||||||
|
$ npm install
|
||||||
$ npm run build
|
$ npm run build
|
||||||
$ ts-node .
|
$ ts-node .
|
||||||
|
|
||||||
@@ -17,3 +27,4 @@ You can also build and install the CLI using
|
|||||||
|
|
||||||
$ npm run build
|
$ npm run build
|
||||||
$ npm install -g .
|
$ npm install -g .
|
||||||
|
****
|
||||||
|
|||||||
331
cli/package-lock.json
generated
331
cli/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.13",
|
"version": "2.2.16",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.13",
|
"version": "2.2.16",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"@types/cli-progress": "^3.11.0",
|
"@types/cli-progress": "^3.11.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^20.14.13",
|
"@types/node": "^20.16.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@vitest/coverage-v8": "^2.0.5",
|
"@vitest/coverage-v8": "^2.0.5",
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
"prettier-plugin-organize-imports": "^4.0.0",
|
"prettier-plugin-organize-imports": "^4.0.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.0.12",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^5.0.0",
|
||||||
"vitest": "^2.0.5",
|
"vitest": "^2.0.5",
|
||||||
"vitest-fetch-mock": "^0.3.0",
|
"vitest-fetch-mock": "^0.3.0",
|
||||||
"yaml": "^2.3.1"
|
"yaml": "^2.3.1"
|
||||||
@@ -52,14 +52,14 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.111.0",
|
"version": "1.113.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.14.13",
|
"@types/node": "^20.16.1",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -825,9 +825,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "9.8.0",
|
"version": "9.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.0.tgz",
|
||||||
"integrity": "sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==",
|
"integrity": "sha512-hhetes6ZHP3BlXLxmd8K2SNgkhNSi+UcecbnwWKwpP7kyi/uC75DJ1lOOBO3xrC4jyojtGE3YxKZPHfk4yrgug==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1054,169 +1054,224 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.1.tgz",
|
||||||
"integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==",
|
"integrity": "sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.1.tgz",
|
||||||
"integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==",
|
"integrity": "sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.1.tgz",
|
||||||
"integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==",
|
"integrity": "sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.1.tgz",
|
||||||
"integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==",
|
"integrity": "sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.1.tgz",
|
||||||
"integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==",
|
"integrity": "sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
|
"version": "4.21.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.1.tgz",
|
||||||
|
"integrity": "sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.1.tgz",
|
||||||
"integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==",
|
"integrity": "sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.1.tgz",
|
||||||
"integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==",
|
"integrity": "sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||||
|
"version": "4.21.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.1.tgz",
|
||||||
|
"integrity": "sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.1.tgz",
|
||||||
"integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==",
|
"integrity": "sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
|
"version": "4.21.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.1.tgz",
|
||||||
|
"integrity": "sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.1.tgz",
|
||||||
"integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==",
|
"integrity": "sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.1.tgz",
|
||||||
"integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==",
|
"integrity": "sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.1.tgz",
|
||||||
"integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==",
|
"integrity": "sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.1.tgz",
|
||||||
"integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==",
|
"integrity": "sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.1.tgz",
|
||||||
"integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==",
|
"integrity": "sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
@@ -1269,13 +1324,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.14.14",
|
"version": "20.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.1.tgz",
|
||||||
"integrity": "sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==",
|
"integrity": "sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~6.19.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/normalize-package-data": {
|
"node_modules/@types/normalize-package-data": {
|
||||||
@@ -1285,17 +1340,17 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.2.0.tgz",
|
||||||
"integrity": "sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==",
|
"integrity": "sha512-02tJIs655em7fvt9gps/+4k4OsKULYGtLBPJfOsmOq1+3cdClYiF0+d6mHu6qDnTcg88wJBkcPLpQhq7FyDz0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.0.1",
|
"@typescript-eslint/scope-manager": "8.2.0",
|
||||||
"@typescript-eslint/type-utils": "8.0.1",
|
"@typescript-eslint/type-utils": "8.2.0",
|
||||||
"@typescript-eslint/utils": "8.0.1",
|
"@typescript-eslint/utils": "8.2.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.0.1",
|
"@typescript-eslint/visitor-keys": "8.2.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@@ -1319,16 +1374,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.2.0.tgz",
|
||||||
"integrity": "sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==",
|
"integrity": "sha512-j3Di+o0lHgPrb7FxL3fdEy6LJ/j2NE8u+AP/5cQ9SKb+JLH6V6UHDqJ+e0hXBkHP1wn1YDFjYCS9LBQsZDlDEg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.0.1",
|
"@typescript-eslint/scope-manager": "8.2.0",
|
||||||
"@typescript-eslint/types": "8.0.1",
|
"@typescript-eslint/types": "8.2.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.0.1",
|
"@typescript-eslint/typescript-estree": "8.2.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.0.1",
|
"@typescript-eslint/visitor-keys": "8.2.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1348,14 +1403,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.2.0.tgz",
|
||||||
"integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==",
|
"integrity": "sha512-OFn80B38yD6WwpoHU2Tz/fTz7CgFqInllBoC3WP+/jLbTb4gGPTy9HBSTsbDWkMdN55XlVU0mMDYAtgvlUspGw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.0.1",
|
"@typescript-eslint/types": "8.2.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.0.1"
|
"@typescript-eslint/visitor-keys": "8.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -1366,14 +1421,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.2.0.tgz",
|
||||||
"integrity": "sha512-+/UT25MWvXeDX9YaHv1IS6KI1fiuTto43WprE7pgSMswHbn1Jm9GEM4Txp+X74ifOWV8emu2AWcbLhpJAvD5Ng==",
|
"integrity": "sha512-g1CfXGFMQdT5S+0PSO0fvGXUaiSkl73U1n9LTK5aRAFnPlJ8dLKkXr4AaLFvPedW8lVDoMgLLE3JN98ZZfsj0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "8.0.1",
|
"@typescript-eslint/typescript-estree": "8.2.0",
|
||||||
"@typescript-eslint/utils": "8.0.1",
|
"@typescript-eslint/utils": "8.2.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.3.0"
|
"ts-api-utils": "^1.3.0"
|
||||||
},
|
},
|
||||||
@@ -1391,9 +1446,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.2.0.tgz",
|
||||||
"integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==",
|
"integrity": "sha512-6a9QSK396YqmiBKPkJtxsgZZZVjYQ6wQ/TlI0C65z7vInaETuC6HAHD98AGLC8DyIPqHytvNuS8bBVvNLKyqvQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1405,14 +1460,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.2.0.tgz",
|
||||||
"integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==",
|
"integrity": "sha512-kiG4EDUT4dImplOsbh47B1QnNmXSoUqOjWDvCJw/o8LgfD0yr7k2uy54D5Wm0j4t71Ge1NkynGhpWdS0dEIAUA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.0.1",
|
"@typescript-eslint/types": "8.2.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.0.1",
|
"@typescript-eslint/visitor-keys": "8.2.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"globby": "^11.1.0",
|
"globby": "^11.1.0",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@@ -1434,16 +1489,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.2.0.tgz",
|
||||||
"integrity": "sha512-CBFR0G0sCt0+fzfnKaciu9IBsKvEKYwN9UZ+eeogK1fYHg4Qxk1yf/wLQkLXlq8wbU2dFlgAesxt8Gi76E8RTA==",
|
"integrity": "sha512-O46eaYKDlV3TvAVDNcoDzd5N550ckSe8G4phko++OCSC1dYIb9LTc3HDGYdWqWIAT5qDUKphO6sd9RrpIJJPfg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@typescript-eslint/scope-manager": "8.0.1",
|
"@typescript-eslint/scope-manager": "8.2.0",
|
||||||
"@typescript-eslint/types": "8.0.1",
|
"@typescript-eslint/types": "8.2.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.0.1"
|
"@typescript-eslint/typescript-estree": "8.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -1457,13 +1512,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.2.0.tgz",
|
||||||
"integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==",
|
"integrity": "sha512-sbgsPMW9yLvS7IhCi8IpuK1oBmtbWUNP+hBdwl/I9nzqVsszGnNGti5r9dUtF5RLivHUFFIdRvLiTsPhzSyJ3Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.0.1",
|
"@typescript-eslint/types": "8.2.0",
|
||||||
"eslint-visitor-keys": "^3.4.3"
|
"eslint-visitor-keys": "^3.4.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2080,9 +2135,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "9.8.0",
|
"version": "9.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.0.tgz",
|
||||||
"integrity": "sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==",
|
"integrity": "sha512-JfiKJrbx0506OEerjK2Y1QlldtBxkAlLxT5OEcRF8uaQ86noDe2k31Vw9rnSWv+MXZHj7OOUV/dA0AhdLFcyvA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2090,7 +2145,7 @@
|
|||||||
"@eslint-community/regexpp": "^4.11.0",
|
"@eslint-community/regexpp": "^4.11.0",
|
||||||
"@eslint/config-array": "^0.17.1",
|
"@eslint/config-array": "^0.17.1",
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
"@eslint/js": "9.8.0",
|
"@eslint/js": "9.9.0",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@humanwhocodes/retry": "^0.3.0",
|
"@humanwhocodes/retry": "^0.3.0",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
@@ -2129,6 +2184,14 @@
|
|||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://eslint.org/donate"
|
"url": "https://eslint.org/donate"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"jiti": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"jiti": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-config-prettier": {
|
"node_modules/eslint-config-prettier": {
|
||||||
@@ -3427,9 +3490,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.39",
|
"version": "8.4.41",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
|
||||||
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
|
"integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3709,10 +3772,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.13.0",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.1.tgz",
|
||||||
"integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==",
|
"integrity": "sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.5"
|
"@types/estree": "1.0.5"
|
||||||
},
|
},
|
||||||
@@ -3724,19 +3788,22 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.13.0",
|
"@rollup/rollup-android-arm-eabi": "4.21.1",
|
||||||
"@rollup/rollup-android-arm64": "4.13.0",
|
"@rollup/rollup-android-arm64": "4.21.1",
|
||||||
"@rollup/rollup-darwin-arm64": "4.13.0",
|
"@rollup/rollup-darwin-arm64": "4.21.1",
|
||||||
"@rollup/rollup-darwin-x64": "4.13.0",
|
"@rollup/rollup-darwin-x64": "4.21.1",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.13.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.21.1",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.13.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.21.1",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.13.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.21.1",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.13.0",
|
"@rollup/rollup-linux-arm64-musl": "4.21.1",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.13.0",
|
"@rollup/rollup-linux-powerpc64le-gnu": "4.21.1",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.13.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.21.1",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.13.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.21.1",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.13.0",
|
"@rollup/rollup-linux-x64-gnu": "4.21.1",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.13.0",
|
"@rollup/rollup-linux-x64-musl": "4.21.1",
|
||||||
|
"@rollup/rollup-win32-arm64-msvc": "4.21.1",
|
||||||
|
"@rollup/rollup-win32-ia32-msvc": "4.21.1",
|
||||||
|
"@rollup/rollup-win32-x64-msvc": "4.21.1",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4151,10 +4218,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "5.26.5",
|
"version": "6.19.8",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.0.13",
|
"version": "1.0.13",
|
||||||
@@ -4206,15 +4274,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.3.5",
|
"version": "5.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz",
|
||||||
"integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==",
|
"integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.39",
|
"postcss": "^8.4.41",
|
||||||
"rollup": "^4.13.0"
|
"rollup": "^4.20.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"vite": "bin/vite.js"
|
"vite": "bin/vite.js"
|
||||||
@@ -4233,6 +4301,7 @@
|
|||||||
"less": "*",
|
"less": "*",
|
||||||
"lightningcss": "^1.21.0",
|
"lightningcss": "^1.21.0",
|
||||||
"sass": "*",
|
"sass": "*",
|
||||||
|
"sass-embedded": "*",
|
||||||
"stylus": "*",
|
"stylus": "*",
|
||||||
"sugarss": "*",
|
"sugarss": "*",
|
||||||
"terser": "^5.4.0"
|
"terser": "^5.4.0"
|
||||||
@@ -4250,6 +4319,9 @@
|
|||||||
"sass": {
|
"sass": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"sass-embedded": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"stylus": {
|
"stylus": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
@@ -4284,10 +4356,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite-tsconfig-paths": {
|
"node_modules/vite-tsconfig-paths": {
|
||||||
"version": "4.3.2",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.0.1.tgz",
|
||||||
"integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==",
|
"integrity": "sha512-yqwv+LstU7NwPeNqajZzLEBVpUFU6Dugtb2P84FXuvaoYA+/70l9MHE+GYfYAycVyPSDYZ7mjOFuYBRqlEpTig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"globrex": "^0.1.2",
|
"globrex": "^0.1.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.13",
|
"version": "2.2.16",
|
||||||
"description": "Command Line Interface (CLI) for Immich",
|
"description": "Command Line Interface (CLI) for Immich",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": "./dist/index.js",
|
"exports": "./dist/index.js",
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"@types/cli-progress": "^3.11.0",
|
"@types/cli-progress": "^3.11.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^20.14.13",
|
"@types/node": "^20.16.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@vitest/coverage-v8": "^2.0.5",
|
"@vitest/coverage-v8": "^2.0.5",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
"prettier-plugin-organize-imports": "^4.0.0",
|
"prettier-plugin-organize-imports": "^4.0.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.0.12",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^5.0.0",
|
||||||
"vitest": "^2.0.5",
|
"vitest": "^2.0.5",
|
||||||
"vitest-fetch-mock": "^0.3.0",
|
"vitest-fetch-mock": "^0.3.0",
|
||||||
"yaml": "^2.3.1"
|
"yaml": "^2.3.1"
|
||||||
@@ -67,6 +67,6 @@
|
|||||||
"lodash-es": "^4.17.21"
|
"lodash-es": "^4.17.21"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.16.0"
|
"node": "20.17.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,37 +2,37 @@
|
|||||||
# Manual edits may be lost in future updates.
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||||
version = "4.38.0"
|
version = "4.40.0"
|
||||||
constraints = "4.38.0"
|
constraints = "4.40.0"
|
||||||
hashes = [
|
hashes = [
|
||||||
"h1:+27KAHKHBDvv3dqyJv5vhtdKQZJzoZXoMqIyronlHNw=",
|
"h1:GP2N1tXrmpxu+qEDvFAmkfv9aeZNhag3bchyJpGpYbU=",
|
||||||
"h1:/uV9RgOUhkxElkHhWs8fs5ZbX9vj6RCBfP0oJO0JF30=",
|
"h1:HDJKZBQkVU0kQl4gViQ5L7EcFLn9hB0iuvO+ORJiDS4=",
|
||||||
"h1:1DNAdMugJJOAWD/XYiZenYYZLy7fw2ctjT4YZmkRCVQ=",
|
"h1:KrbeEsZoCJOnnX68yNI5h3QhMjc5bBCQW4yvYaEFq3s=",
|
||||||
"h1:1wn4PmCLdT7mvd74JkCGmJDJxTQDkcxc+1jNbmwnMHA=",
|
"h1:LelwnzU0OVn6g2+T9Ub9XdpC+vbheraIL/qgXhWBs/k=",
|
||||||
"h1:BIHB4fBxHg2bA9KbL92njhyctxKC8b6hNDp60y5QBss=",
|
"h1:TIq9CynfWrKgCxKL97Akj89cYlvJKn/AL4UXogd8/FM=",
|
||||||
"h1:HCQpvKPsMsR4HO5eDqt+Kao7T7CYeEH7KZIO7xMcC6M=",
|
"h1:Uoy5oPdm1ipDG7yIMCUN1IXMpsTGXahPw3I0rVA/6wA=",
|
||||||
"h1:HTomuzocukpNLwtWzeSF3yteCVsyVKbwKmN66u9iPac=",
|
"h1:Wunfpm+IZhENdoimrh4iXiakVnCsfKOHo80yJUjMQXM=",
|
||||||
"h1:YDxsUBhBAwHSXLzVwrSlSBOwv1NvLyry7s5SfCV7VqQ=",
|
"h1:cRdCuahMOFrNyldnCInqGQRBT1DTkRPSfPnaf5r05iw=",
|
||||||
"h1:dchVhxo+Acd1l2RuZ88tW9lWj4422QMfgtxKvKCjYrw=",
|
"h1:k+zpXg8BO7gdbTIfSGyQisHhs5aVWQVbPLa5uUdr2UA=",
|
||||||
"h1:eypa+P4ZpsEGMPFuCE+6VkRefu0TZRFmVBOpK+PDOPY=",
|
"h1:kWNrzZ8Rh0OpHikexkmwJIIucD6SMZPi4oGyDsKJitw=",
|
||||||
"h1:f3yjse2OsRZj7ZhR7BLintJMlI4fpyt8HyDP/zcEavw=",
|
"h1:lomfTTjK78BdSEVTFcJUBQRy7IQHuGQImMaPWaYpfgQ=",
|
||||||
"h1:mSJ7xj8K+xcnEmGg7lH0jjzyQb157wH94ULTAlIV+HQ=",
|
"h1:oWcWlZe52ZRyLQciNe94RaWzhHifSTu03nlK0uL7rlM=",
|
||||||
"h1:tt+2J2Ze8VIdDq2Hr6uHlTJzAMBRpErBwTYx0uD5ilE=",
|
"h1:p3JJrhGEPlPQP7Uwy9FNMdvqCyD8tuT4lnXuJ+pSF/M=",
|
||||||
"h1:uQW8SKxmulqrAisO+365mIf2FueINAp5PY28bqCPCug=",
|
"h1:wtB0sKxG2K/H41hWJI4uJdImWquuaP34Sip5LmfE410=",
|
||||||
"zh:171ab67cccceead4514fafb2d39e4e708a90cce79000aaf3c29aab7ed4457071",
|
"zh:01742e5946f936548f8e42120287ffc757abf97e7cbbe34e25c266a438fb54fd",
|
||||||
"zh:18aa7228447baaaefc49a43e8eff970817a7491a63d8937e796357a3829dd979",
|
"zh:08d81f5a5aab4cc269f983b8c6b5be0e278105136aca9681740802619577371f",
|
||||||
"zh:2cbaab6092e81ba6f41fa60a50f14e980c8ec327ee11d0b21f16a478be4b7567",
|
"zh:0d75131ba70902cfc94a7a5900369bdde56528b2aad6e10b164449cc97d57396",
|
||||||
"zh:53b8e49c06f5b31a8c681f8c0669cf43e78abe71657b8182a221d096bb514965",
|
"zh:3890a715a012e197541daacdacb8cceec6d364814daa4640ddfe98a8ba9036cb",
|
||||||
"zh:6037cfc60b4b647aabae155fcb46d649ed7c650e0287f05db52b2068f1e27c8a",
|
"zh:58254ce5ebe1faed4664df86210c39d660bcdc60280f17b25fe4d4dbea21ea8c",
|
||||||
"zh:62460982ce1a869eebfca675603fbbd50416cf6b69459fb855bfbe5ae2b97607",
|
"zh:6b0abc1adbc2edee79368ce9f7338ebcb5d0bf941e8d7d9ac505b750f20f80a2",
|
||||||
"zh:65f6f3a8470917b6398baa5eb4f74b3932b213eac7c0202798bfad6fd1ee17df",
|
"zh:81cc415d1477174a1ca288d25fdb57e5ee488c2d7f61f265ef995b255a53b0ce",
|
||||||
|
"zh:8680140c7fe5beaefe61c5cfa471bf88422dc0c0f05dad6d3cb482d4ffd22be4",
|
||||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||||
"zh:8b5cebe64bf04105a49178a165b6a8800a9a33bae6767143a47fe4977755f805",
|
"zh:a491d26236122ccb83dac8cb490d2c0aa1f4d3a0b4abe99300fd49b1a624f42f",
|
||||||
"zh:a5596635db0993ee3c3060fbc2227d91b239466e96d2d82642625a5aa2486988",
|
"zh:a70d9c469dc8d55715ba77c9d1a4ede1fdebf79e60ee18438a0844868db54e0d",
|
||||||
"zh:b3a9c63038441f13c311fd4b2c7e69e571445e5a7365a20c7cc9046b7e6c8aba",
|
"zh:a7fcb7d5c4222e14ec6d9a15adf8b9a083d84b102c3d0e4a0d102df5a1360b62",
|
||||||
"zh:b585e7e4d7648a540b14b9182819214896ca9337729eeb1f2034833b17db754d",
|
"zh:b4f9677174fabd199c8ebd2e9e5eb3528cf887e700569a4fb61eef4e070cec5e",
|
||||||
"zh:d2c3c545318ac8542369e9fc8228e29ee585febdf203a450fad3e0eded71ce02",
|
"zh:c27f0f7519221d75dae4a3787a59e05acd5cc9a0d30a390eff349a77d20d52e6",
|
||||||
"zh:e95dd2d6c3525073af47d47b763cb81b6a51b20cabf76f789c69328922da9ecf",
|
"zh:db00d8605dbf43ca42fe1481a6c67fdcaa73debb7d2a0f613cb95ae5c5e7150e",
|
||||||
"zh:eee6e590b36d6c6168a7daae8afa74a8721fd7aa9f62a710f04a311975100722",
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
cloudflare = {
|
cloudflare = {
|
||||||
source = "cloudflare/cloudflare"
|
source = "cloudflare/cloudflare"
|
||||||
version = "4.38.0"
|
version = "4.40.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,37 +2,37 @@
|
|||||||
# Manual edits may be lost in future updates.
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||||
version = "4.38.0"
|
version = "4.40.0"
|
||||||
constraints = "4.38.0"
|
constraints = "4.40.0"
|
||||||
hashes = [
|
hashes = [
|
||||||
"h1:+27KAHKHBDvv3dqyJv5vhtdKQZJzoZXoMqIyronlHNw=",
|
"h1:GP2N1tXrmpxu+qEDvFAmkfv9aeZNhag3bchyJpGpYbU=",
|
||||||
"h1:/uV9RgOUhkxElkHhWs8fs5ZbX9vj6RCBfP0oJO0JF30=",
|
"h1:HDJKZBQkVU0kQl4gViQ5L7EcFLn9hB0iuvO+ORJiDS4=",
|
||||||
"h1:1DNAdMugJJOAWD/XYiZenYYZLy7fw2ctjT4YZmkRCVQ=",
|
"h1:KrbeEsZoCJOnnX68yNI5h3QhMjc5bBCQW4yvYaEFq3s=",
|
||||||
"h1:1wn4PmCLdT7mvd74JkCGmJDJxTQDkcxc+1jNbmwnMHA=",
|
"h1:LelwnzU0OVn6g2+T9Ub9XdpC+vbheraIL/qgXhWBs/k=",
|
||||||
"h1:BIHB4fBxHg2bA9KbL92njhyctxKC8b6hNDp60y5QBss=",
|
"h1:TIq9CynfWrKgCxKL97Akj89cYlvJKn/AL4UXogd8/FM=",
|
||||||
"h1:HCQpvKPsMsR4HO5eDqt+Kao7T7CYeEH7KZIO7xMcC6M=",
|
"h1:Uoy5oPdm1ipDG7yIMCUN1IXMpsTGXahPw3I0rVA/6wA=",
|
||||||
"h1:HTomuzocukpNLwtWzeSF3yteCVsyVKbwKmN66u9iPac=",
|
"h1:Wunfpm+IZhENdoimrh4iXiakVnCsfKOHo80yJUjMQXM=",
|
||||||
"h1:YDxsUBhBAwHSXLzVwrSlSBOwv1NvLyry7s5SfCV7VqQ=",
|
"h1:cRdCuahMOFrNyldnCInqGQRBT1DTkRPSfPnaf5r05iw=",
|
||||||
"h1:dchVhxo+Acd1l2RuZ88tW9lWj4422QMfgtxKvKCjYrw=",
|
"h1:k+zpXg8BO7gdbTIfSGyQisHhs5aVWQVbPLa5uUdr2UA=",
|
||||||
"h1:eypa+P4ZpsEGMPFuCE+6VkRefu0TZRFmVBOpK+PDOPY=",
|
"h1:kWNrzZ8Rh0OpHikexkmwJIIucD6SMZPi4oGyDsKJitw=",
|
||||||
"h1:f3yjse2OsRZj7ZhR7BLintJMlI4fpyt8HyDP/zcEavw=",
|
"h1:lomfTTjK78BdSEVTFcJUBQRy7IQHuGQImMaPWaYpfgQ=",
|
||||||
"h1:mSJ7xj8K+xcnEmGg7lH0jjzyQb157wH94ULTAlIV+HQ=",
|
"h1:oWcWlZe52ZRyLQciNe94RaWzhHifSTu03nlK0uL7rlM=",
|
||||||
"h1:tt+2J2Ze8VIdDq2Hr6uHlTJzAMBRpErBwTYx0uD5ilE=",
|
"h1:p3JJrhGEPlPQP7Uwy9FNMdvqCyD8tuT4lnXuJ+pSF/M=",
|
||||||
"h1:uQW8SKxmulqrAisO+365mIf2FueINAp5PY28bqCPCug=",
|
"h1:wtB0sKxG2K/H41hWJI4uJdImWquuaP34Sip5LmfE410=",
|
||||||
"zh:171ab67cccceead4514fafb2d39e4e708a90cce79000aaf3c29aab7ed4457071",
|
"zh:01742e5946f936548f8e42120287ffc757abf97e7cbbe34e25c266a438fb54fd",
|
||||||
"zh:18aa7228447baaaefc49a43e8eff970817a7491a63d8937e796357a3829dd979",
|
"zh:08d81f5a5aab4cc269f983b8c6b5be0e278105136aca9681740802619577371f",
|
||||||
"zh:2cbaab6092e81ba6f41fa60a50f14e980c8ec327ee11d0b21f16a478be4b7567",
|
"zh:0d75131ba70902cfc94a7a5900369bdde56528b2aad6e10b164449cc97d57396",
|
||||||
"zh:53b8e49c06f5b31a8c681f8c0669cf43e78abe71657b8182a221d096bb514965",
|
"zh:3890a715a012e197541daacdacb8cceec6d364814daa4640ddfe98a8ba9036cb",
|
||||||
"zh:6037cfc60b4b647aabae155fcb46d649ed7c650e0287f05db52b2068f1e27c8a",
|
"zh:58254ce5ebe1faed4664df86210c39d660bcdc60280f17b25fe4d4dbea21ea8c",
|
||||||
"zh:62460982ce1a869eebfca675603fbbd50416cf6b69459fb855bfbe5ae2b97607",
|
"zh:6b0abc1adbc2edee79368ce9f7338ebcb5d0bf941e8d7d9ac505b750f20f80a2",
|
||||||
"zh:65f6f3a8470917b6398baa5eb4f74b3932b213eac7c0202798bfad6fd1ee17df",
|
"zh:81cc415d1477174a1ca288d25fdb57e5ee488c2d7f61f265ef995b255a53b0ce",
|
||||||
|
"zh:8680140c7fe5beaefe61c5cfa471bf88422dc0c0f05dad6d3cb482d4ffd22be4",
|
||||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||||
"zh:8b5cebe64bf04105a49178a165b6a8800a9a33bae6767143a47fe4977755f805",
|
"zh:a491d26236122ccb83dac8cb490d2c0aa1f4d3a0b4abe99300fd49b1a624f42f",
|
||||||
"zh:a5596635db0993ee3c3060fbc2227d91b239466e96d2d82642625a5aa2486988",
|
"zh:a70d9c469dc8d55715ba77c9d1a4ede1fdebf79e60ee18438a0844868db54e0d",
|
||||||
"zh:b3a9c63038441f13c311fd4b2c7e69e571445e5a7365a20c7cc9046b7e6c8aba",
|
"zh:a7fcb7d5c4222e14ec6d9a15adf8b9a083d84b102c3d0e4a0d102df5a1360b62",
|
||||||
"zh:b585e7e4d7648a540b14b9182819214896ca9337729eeb1f2034833b17db754d",
|
"zh:b4f9677174fabd199c8ebd2e9e5eb3528cf887e700569a4fb61eef4e070cec5e",
|
||||||
"zh:d2c3c545318ac8542369e9fc8228e29ee585febdf203a450fad3e0eded71ce02",
|
"zh:c27f0f7519221d75dae4a3787a59e05acd5cc9a0d30a390eff349a77d20d52e6",
|
||||||
"zh:e95dd2d6c3525073af47d47b763cb81b6a51b20cabf76f789c69328922da9ecf",
|
"zh:db00d8605dbf43ca42fe1481a6c67fdcaa73debb7d2a0f613cb95ae5c5e7150e",
|
||||||
"zh:eee6e590b36d6c6168a7daae8afa74a8721fd7aa9f62a710f04a311975100722",
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
cloudflare = {
|
cloudflare = {
|
||||||
source = "cloudflare/cloudflare"
|
source = "cloudflare/cloudflare"
|
||||||
version = "4.38.0"
|
version = "4.40.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ services:
|
|||||||
container_name: immich_prometheus
|
container_name: immich_prometheus
|
||||||
ports:
|
ports:
|
||||||
- 9090:9090
|
- 9090:9090
|
||||||
image: prom/prometheus@sha256:f20d3127bf2876f4a1df76246fca576b41ddf1125ed1c546fbd8b16ea55117e6
|
image: prom/prometheus@sha256:f6639335d34a77d9d9db382b92eeb7fc00934be8eae81dbc03b31cfe90411a94
|
||||||
volumes:
|
volumes:
|
||||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
- prometheus-data:/prometheus
|
- prometheus-data:/prometheus
|
||||||
@@ -91,7 +91,7 @@ services:
|
|||||||
command: ['./run.sh', '-disable-reporting']
|
command: ['./run.sh', '-disable-reporting']
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
image: grafana/grafana:11.1.3-ubuntu@sha256:e10453733015f31103cb530425f32c994816b50102886fa885dafea2c50a711c
|
image: grafana/grafana:11.1.4-ubuntu@sha256:8e74fb7eed4d59fb5595acd0576c21411167f6b6401426ae29f2e8f9f71b68f6
|
||||||
volumes:
|
volumes:
|
||||||
- grafana-data:/var/lib/grafana
|
- grafana-data:/var/lib/grafana
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ DB_DATA_LOCATION=./postgres
|
|||||||
IMMICH_VERSION=release
|
IMMICH_VERSION=release
|
||||||
|
|
||||||
# Connection secret for postgres. You should change it to a random password
|
# Connection secret for postgres. You should change it to a random password
|
||||||
|
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
||||||
DB_PASSWORD=postgres
|
DB_PASSWORD=postgres
|
||||||
|
|
||||||
# The values below this line do not need to be changed
|
# The values below this line do not need to be changed
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
20.16.0
|
20.17.0
|
||||||
|
|||||||
@@ -52,14 +52,25 @@ On iOS (iPhone and iPad), the operating system determines if a particular app ca
|
|||||||
- Disable Background App Refresh for apps that don't need background tasks to run. This will reduce the competition for background task invocation for Immich.
|
- Disable Background App Refresh for apps that don't need background tasks to run. This will reduce the competition for background task invocation for Immich.
|
||||||
- Use the Immich app more often.
|
- Use the Immich app more often.
|
||||||
|
|
||||||
|
### Why are features not working with a self-signed cert or mTLS?
|
||||||
|
|
||||||
|
Due to limitations in the upstream app/video library, using a self-signed TLS certificate or mutual TLS may break video playback or asset upload (both foreground and/or background).
|
||||||
|
We recommend using a real SSL certificate from a free provider, for example [Let's Encrypt](https://letsencrypt.org/).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Assets
|
## Assets
|
||||||
|
|
||||||
### Does Immich change the file?
|
### Does Immich change the file?
|
||||||
|
|
||||||
No, Immich does not touch the original file under any circumstances,
|
No, Immich does not modify the original files.
|
||||||
all edited metadata are saved in the companion sidecar file and the database.
|
All edited metadata is saved in companion `.xmp` sidecar files and the database.
|
||||||
|
However, Immich will delete original files that have been trashed when the trash is emptied in the Immich UI.
|
||||||
|
|
||||||
|
### Why do my file names appear as a random string in the file manager?
|
||||||
|
|
||||||
|
When Storage Template is off (default) Immich saves the file names in a random string (also known as random UUIDs) to prevent duplicate file names. To retrieve the original file names, you must enable the Storage Template and then run the STORAGE TEMPLATE MIGRATION job.
|
||||||
|
It is recommended to read about [Storage Template](https://immich.app/docs/administration/storage-template) before activation.
|
||||||
|
|
||||||
### Can I add my existing photo library?
|
### Can I add my existing photo library?
|
||||||
|
|
||||||
@@ -157,6 +168,19 @@ We haven't implemented an official mechanism for creating albums from external l
|
|||||||
|
|
||||||
Duplicate checking only exists for upload libraries, using the file hash. Furthermore, duplicate checking is not global, but _per library_. Therefore, a situation where the same file appears twice in the timeline is possible, especially for external libraries.
|
Duplicate checking only exists for upload libraries, using the file hash. Furthermore, duplicate checking is not global, but _per library_. Therefore, a situation where the same file appears twice in the timeline is possible, especially for external libraries.
|
||||||
|
|
||||||
|
### Why are my edits to files not being saved in read-only external libraries?
|
||||||
|
|
||||||
|
Images in read-write external libraries (the default) can be edited as normal.
|
||||||
|
In read-only libraries (`:ro` in the `docker-compose.yml`), Immich is unable to create the `.xmp` sidecar files to store edited file metadata.
|
||||||
|
For this reason, the metadata (timestamp, location, description, star rating, etc.) cannot be edited for files in read-only external libraries.
|
||||||
|
|
||||||
|
### How are deletions of files handled in external libraries?
|
||||||
|
|
||||||
|
Immich will attempt to delete original files that have been trashed when the trash is emptied.
|
||||||
|
In read-write external libraries (the default), Immich will delete the original file.
|
||||||
|
In read-only libraries (`:ro` in the `docker-compose.yml`), files can still be trashed in the UI.
|
||||||
|
However, when the trash is emptied, the files will re-appear in the main timeline since Immich is unable to delete the original file.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Machine Learning
|
## Machine Learning
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ docker compose up -d # Start remainder of Immich apps
|
|||||||
<TabItem value="Windows system (PowerShell)" label="Windows system (PowerShell)">
|
<TabItem value="Windows system (PowerShell)" label="Windows system (PowerShell)">
|
||||||
|
|
||||||
```powershell title='Backup'
|
```powershell title='Backup'
|
||||||
docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=postgres > "\path\to\backup\dump.sql"
|
docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=postgres | Set-Content -Encoding utf8 "C:\path\to\backup\dump.sql"
|
||||||
```
|
```
|
||||||
|
|
||||||
```powershell title='Restore'
|
```powershell title='Restore'
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
BIN
docs/docs/administration/img/admin-jobs.webp
Normal file
BIN
docs/docs/administration/img/admin-jobs.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
@@ -52,4 +52,4 @@ Additionally, some jobs run on a schedule, which is every night at midnight. Thi
|
|||||||
Storage Migration job can be run after changing the [Storage Template](/docs/administration/storage-template.mdx), in order to apply the change to the existing library.
|
Storage Migration job can be run after changing the [Storage Template](/docs/administration/storage-template.mdx), in order to apply the change to the existing library.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
<img src={require('./img/admin-jobs.png').default} width="80%" title="Admin jobs" />
|
<img src={require('./img/admin-jobs.webp').default} width="60%" title="Admin jobs" />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
This page contains details about using OAuth in Immich.
|
This page contains details about using OAuth in Immich.
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
Unable to set `app.immich:/` as a valid redirect URI? See [Mobile Redirect URI](#mobile-redirect-uri) for an alternative solution.
|
Unable to set `app.immich:///oauth-callback` as a valid redirect URI? See [Mobile Redirect URI](#mobile-redirect-uri) for an alternative solution.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
@@ -30,7 +30,7 @@ Before enabling OAuth in Immich, a new client application needs to be configured
|
|||||||
|
|
||||||
The **Sign-in redirect URIs** should include:
|
The **Sign-in redirect URIs** should include:
|
||||||
|
|
||||||
- `app.immich:/` - for logging in with OAuth from the [Mobile App](/docs/features/mobile-app.mdx)
|
- `app.immich:///oauth-callback` - for logging in with OAuth from the [Mobile App](/docs/features/mobile-app.mdx)
|
||||||
- `http://DOMAIN:PORT/auth/login` - for logging in with OAuth from the Web Client
|
- `http://DOMAIN:PORT/auth/login` - for logging in with OAuth from the Web Client
|
||||||
- `http://DOMAIN:PORT/user-settings` - for manually linking OAuth in the Web Client
|
- `http://DOMAIN:PORT/user-settings` - for manually linking OAuth in the Web Client
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ Before enabling OAuth in Immich, a new client application needs to be configured
|
|||||||
|
|
||||||
Mobile
|
Mobile
|
||||||
|
|
||||||
- `app.immich:/` (You **MUST** include this for iOS and Android mobile apps to work properly)
|
- `app.immich:///oauth-callback` (You **MUST** include this for iOS and Android mobile apps to work properly)
|
||||||
|
|
||||||
Localhost
|
Localhost
|
||||||
|
|
||||||
@@ -96,16 +96,16 @@ When Auto Launch is enabled, the login page will automatically redirect the user
|
|||||||
|
|
||||||
## Mobile Redirect URI
|
## Mobile Redirect URI
|
||||||
|
|
||||||
The redirect URI for the mobile app is `app.immich:/`, which is a [Custom Scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app). If this custom scheme is an invalid redirect URI for your OAuth Provider, you can work around this by doing the following:
|
The redirect URI for the mobile app is `app.immich:///oauth-callback`, which is a [Custom Scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app). If this custom scheme is an invalid redirect URI for your OAuth Provider, you can work around this by doing the following:
|
||||||
|
|
||||||
1. Configure an http(s) endpoint to forwards requests to `app.immich:/`
|
1. Configure an http(s) endpoint to forwards requests to `app.immich:///oauth-callback`
|
||||||
2. Whitelist the new endpoint as a valid redirect URI with your provider.
|
2. Whitelist the new endpoint as a valid redirect URI with your provider.
|
||||||
3. Specify the new endpoint as the `Mobile Redirect URI Override`, in the OAuth settings.
|
3. Specify the new endpoint as the `Mobile Redirect URI Override`, in the OAuth settings.
|
||||||
|
|
||||||
With these steps in place, you should be able to use OAuth from the [Mobile App](/docs/features/mobile-app.mdx) without a custom scheme redirect URI.
|
With these steps in place, you should be able to use OAuth from the [Mobile App](/docs/features/mobile-app.mdx) without a custom scheme redirect URI.
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
Immich has a route (`/api/oauth/mobile-redirect`) that is already configured to forward requests to `app.immich:/`, and can be used for step 1.
|
Immich has a route (`/api/oauth/mobile-redirect`) that is already configured to forward requests to `app.immich:///oauth-callback`, and can be used for step 1.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Example Configuration
|
## Example Configuration
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ You can choose to disable a certain type of machine learning, for example smart
|
|||||||
|
|
||||||
### Smart Search
|
### Smart Search
|
||||||
|
|
||||||
The smart search settings are designed to allow the search tool to be used using [CLIP](https://openai.com/research/clip) models that [can be changed](/docs/FAQ#can-i-use-a-custom-clip-model), different models will necessarily give better results but may consume more processing power, when changing a model it is mandatory to re-run the
|
The [smart search](/docs/features/smart-search) settings are designed to allow the search tool to be used using [CLIP](https://openai.com/research/clip) models that [can be changed](/docs/FAQ#can-i-use-a-custom-clip-model), different models will necessarily give better results but may consume more processing power, when changing a model it is mandatory to re-run the
|
||||||
Smart Search job on all images to fully apply the change.
|
Smart Search job on all images to fully apply the change.
|
||||||
|
|
||||||
:::info Internet connection
|
:::info Internet connection
|
||||||
@@ -113,15 +113,23 @@ After downloading, there is no need for Immich to connect to the network
|
|||||||
Unless version checking has been enabled in the settings.
|
Unless version checking has been enabled in the settings.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
### Duplicate Detection
|
||||||
|
|
||||||
|
Use CLIP embeddings to find likely duplicates. The maximum detection distance can be configured in order to improve / reduce the level of accuracy.
|
||||||
|
|
||||||
|
- **Maximum detection distance -** Maximum distance between two images to consider them duplicates, ranging from 0.001-0.1. Higher values will detect more duplicates, but may result in false positives.
|
||||||
|
|
||||||
### Facial Recognition
|
### Facial Recognition
|
||||||
|
|
||||||
Under these settings, you can change the facial recognition settings
|
Under these settings, you can change the facial recognition settings
|
||||||
Editable settings:
|
Editable settings:
|
||||||
|
|
||||||
- **Facial Recognition Model -** Models are listed in descending order of size. Larger models are slower and use more memory, but produce better results. Note that you must re-run the Face Detection job for all images upon changing a model.
|
- **Facial Recognition Model**
|
||||||
- **Min Detection Score -** Minimum confidence score for a face to be detected from 0-1. Lower values will detect more faces but may result in false positives.
|
- **Min Detection Score**
|
||||||
- **Max Recognition Distance -** Maximum distance between two faces to be considered the same person, ranging from 0-2. Lowering this can prevent labeling two people as the same person, while raising it can prevent labeling the same person as two different people. Note that it is easier to merge two people than to split one person in two, so err on the side of a lower threshold when possible.
|
- **Max Recognition Distance**
|
||||||
- **Min Recognized Faces -** The minimum number of recognized faces for a person to be created (AKA: Core face). Increasing this makes Facial Recognition more precise at the cost of increasing the chance that a face is not assigned to a person.
|
- **Min Recognized Faces**
|
||||||
|
|
||||||
|
You can learn more about these options on the [Facial Recognition page](/docs/features/facial-recognition#how-face-detection-works)
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
When changing the values in Min Detection Score, Max Recognition Distance, and Min Recognized Faces.
|
When changing the values in Min Detection Score, Max Recognition Distance, and Min Recognized Faces.
|
||||||
@@ -153,7 +161,7 @@ SMTP server setup, for user creation notifications, new albums, etc. More inform
|
|||||||
|
|
||||||
### External Domain
|
### External Domain
|
||||||
|
|
||||||
When set, will override the domain name used when viewing and copying a shared link.
|
Overrides the domain name in shared links and email notifications. The URL should not include a trailing slash.
|
||||||
|
|
||||||
### Welcome Message
|
### Welcome Message
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
### Unit tests
|
### Unit tests
|
||||||
|
|
||||||
Unit are run by calling `npm run test` from the `server` directory.
|
Unit are run by calling `npm run test` from the `server/` directory.
|
||||||
|
You need to run `npm install` (in `server/`) before _once_.
|
||||||
|
|
||||||
### End to end tests
|
### End to end tests
|
||||||
|
|
||||||
@@ -14,6 +15,11 @@ The e2e tests can be run by first starting up a test production environment via:
|
|||||||
make e2e
|
make e2e
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Before you can run the tests, you need to run the following commands _once_:
|
||||||
|
|
||||||
|
- `npm install` (in `e2e/`)
|
||||||
|
- `make open-api` (in the project root `/`)
|
||||||
|
|
||||||
Once the test environment is running, the e2e tests can be run via:
|
Once the test environment is running, the e2e tests can be run via:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -112,7 +112,8 @@ The `immich-server` container will need access to the gallery. Modify your docke
|
|||||||
```
|
```
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
The `ro` flag at the end only gives read-only access to the volumes. This will disallow the images from being deleted in the web UI.
|
The `ro` flag at the end only gives read-only access to the volumes.
|
||||||
|
This will disallow the images from being deleted in the web UI, or adding metadata to the library ([XMP sidecars](/docs/features/xmp-sidecars)).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ When sharing shared albums, whats shared is:
|
|||||||
|
|
||||||
- Download all assets as zip file (Web only).
|
- Download all assets as zip file (Web only).
|
||||||
:::info Archive size limited.
|
:::info Archive size limited.
|
||||||
If the size of the album exceeds 4GB, the archive files will be divided into 4GB each.
|
If the size of the album exceeds 4GB, the archive files will by default be divided into 4GB each. This can be changed on the user settings page.
|
||||||
:::
|
:::
|
||||||
- Add a description to the album (Web only).
|
- Add a description to the album (Web only).
|
||||||
- Slideshow view (Web only).
|
- Slideshow view (Web only).
|
||||||
@@ -73,14 +73,14 @@ You can edit the link properties, options include;
|
|||||||
- **Allow public user to download -** whether to allow whoever has the link to download all the images or a certain image (optional).
|
- **Allow public user to download -** whether to allow whoever has the link to download all the images or a certain image (optional).
|
||||||
- **Allow public user to upload -** whether to allow whoever has the link to upload assets to the album (optional).
|
- **Allow public user to upload -** whether to allow whoever has the link to upload assets to the album (optional).
|
||||||
:::info
|
:::info
|
||||||
whoever has the link and have uploaded files cannot delete them.
|
Whoever has the link and have uploaded files cannot delete them.
|
||||||
:::
|
:::
|
||||||
- **Expire after -** adding an expiration date to the link (optional).
|
- **Expire after -** adding an expiration date to the link (optional).
|
||||||
|
|
||||||
## Share Specific Assets
|
## Share Specific Assets
|
||||||
|
|
||||||
A user can share specific assets without linking them to a specific album.
|
A user can share specific assets without linking them to a specific album.
|
||||||
in order to do so;
|
In order to do this:
|
||||||
|
|
||||||
1. Go to the timeline
|
1. Go to the timeline
|
||||||
2. Select the assets (Shift can be used for multiple selection)
|
2. Select the assets (Shift can be used for multiple selection)
|
||||||
@@ -152,7 +152,7 @@ Some of the features are not available on mobile, to understand what the full fe
|
|||||||
|
|
||||||
## Sharing Between Users
|
## Sharing Between Users
|
||||||
|
|
||||||
#### Add or remove users from the album.
|
#### Add or remove users from the album
|
||||||
|
|
||||||
:::info remove user(s)
|
:::info remove user(s)
|
||||||
When a user is removed from the album, the photos he uploaded will still appear in the album.
|
When a user is removed from the album, the photos he uploaded will still appear in the album.
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
# Create Custom Map Styles for Immich Using Maptiler
|
# Custom Map Styles
|
||||||
|
|
||||||
You may decide that you'd like to modify the style document which is used to draw the maps in Immich. This can be done easily using Maptiler, if you do not want to write an entire JSON document by hand.
|
You may decide that you'd like to modify the style document which is used to
|
||||||
|
draw the maps in Immich. In addition to visual customization, this also allows
|
||||||
|
you to pick your own map tile provider instead of the default one. The default
|
||||||
|
`style.json` for [light theme](https://github.com/immich-app/immich/tree/main/server/resources/style-light.json)
|
||||||
|
and [dark theme](https://github.com/immich-app/immich/blob/main/server/resources/style-dark.json)
|
||||||
|
can be used as a basis for creating your own style.
|
||||||
|
|
||||||
## Steps
|
There are several sources for already-made `style.json` map themes, as well as
|
||||||
|
online generators you can use.
|
||||||
|
|
||||||
|
1. In **Immich**, navigate to **Administration --> Settings --> Map & GPS Settings** and expand the **Map Settings** subsection.
|
||||||
|
2. Paste the link to your JSON style in either the **Light Style** or **Dark Style**. (You can add different styles which will help make the map style more appropriate depending on whether you set **Immich** to Light or Dark mode.)
|
||||||
|
3. Save your selections. Reload the map, and enjoy your custom map style!
|
||||||
|
|
||||||
|
## Use Maptiler to build a custom style
|
||||||
|
|
||||||
|
Customizing the map style can be done easily using Maptiler, if you do not want to write an entire JSON document by hand.
|
||||||
|
|
||||||
1. Create a free account at https://cloud.maptiler.com
|
1. Create a free account at https://cloud.maptiler.com
|
||||||
2. Once logged in, you can either create a brand new map by clicking on **New Map**, selecting a starter map, and then clicking **Customize**, OR by selecting a **Standard Map** and customizing it from there.
|
2. Once logged in, you can either create a brand new map by clicking on **New Map**, selecting a starter map, and then clicking **Customize**, OR by selecting a **Standard Map** and customizing it from there.
|
||||||
@@ -11,6 +25,3 @@ You may decide that you'd like to modify the style document which is used to dra
|
|||||||
5. Next, **Publish** your style using the **Publish** button at the top right. This will deploy it to production, which means it is able to be exposed over the Internet. Maptiler will present an interactive side-by-side map with the original and your changes prior to publication.<br/>
|
5. Next, **Publish** your style using the **Publish** button at the top right. This will deploy it to production, which means it is able to be exposed over the Internet. Maptiler will present an interactive side-by-side map with the original and your changes prior to publication.<br/>
|
||||||
6. Maptiler will warn you that changing the map will change it across all apps using the map. Since no apps are using the map yet, this is okay.
|
6. Maptiler will warn you that changing the map will change it across all apps using the map. Since no apps are using the map yet, this is okay.
|
||||||
7. Clicking on the name of your new map at the top left will bring you to the item's **details** page. From here, copy the link to the JSON style under **Use vector style**. This link will automatically contain your personal API key to Maptiler.
|
7. Clicking on the name of your new map at the top left will bring you to the item's **details** page. From here, copy the link to the JSON style under **Use vector style**. This link will automatically contain your personal API key to Maptiler.
|
||||||
8. In **Immich**, navigate to **Administration --> Settings --> Map & GPS Settings** and expand the **Map Settings** subsection.
|
|
||||||
9. Paste the link to your JSON style in either the **Light Style** or **Dark Style**. (You can add different styles which will help make the map style more appropriate depending on whether you set **Immich** to Light or Dark mode.
|
|
||||||
10. Save your selections. Reload the map, and enjoy your custom map style!
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ SELECT * FROM "assets" WHERE "originalFileName" LIKE '%_2023_%'; -- all files wi
|
|||||||
```
|
```
|
||||||
|
|
||||||
```sql title="Find by path"
|
```sql title="Find by path"
|
||||||
SELECT * FROM "assets" WHERE "originalPath" = 'upload/library/admin/2023/2023-09-03/PXL_20230903_232542848.jpg';
|
SELECT * FROM "assets" WHERE "originalPath" = 'upload/library/admin/2023/2023-09-03/PXL_2023.jpg';
|
||||||
SELECT * FROM "assets" WHERE "originalPath" LIKE 'upload/library/admin/2023/%';
|
SELECT * FROM "assets" WHERE "originalPath" LIKE 'upload/library/admin/2023/%';
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -37,6 +37,12 @@ SELECT * FROM "assets" WHERE "checksum" = decode('69de19c87658c4c15d9cacb9967b8e
|
|||||||
SELECT * FROM "assets" WHERE "checksum" = '\x69de19c87658c4c15d9cacb9967b8e033bf74dd1'; -- alternate notation
|
SELECT * FROM "assets" WHERE "checksum" = '\x69de19c87658c4c15d9cacb9967b8e033bf74dd1'; -- alternate notation
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sql title="Find duplicate assets with identical checksum (SHA-1) (excluding trashed files)"
|
||||||
|
SELECT T1."checksum", array_agg(T2."id") ids FROM "assets" T1
|
||||||
|
INNER JOIN "assets" T2 ON T1."checksum" = T2."checksum" AND T1."id" != T2."id" AND T2."deletedAt" IS NULL
|
||||||
|
WHERE T1."deletedAt" IS NULL GROUP BY T1."checksum";
|
||||||
|
```
|
||||||
|
|
||||||
```sql title="Live photos"
|
```sql title="Live photos"
|
||||||
SELECT * FROM "assets" WHERE "livePhotoVideoId" IS NOT NULL;
|
SELECT * FROM "assets" WHERE "livePhotoVideoId" IS NOT NULL;
|
||||||
```
|
```
|
||||||
@@ -79,8 +85,7 @@ SELECT "assets"."type", COUNT(*) FROM "assets" GROUP BY "assets"."type";
|
|||||||
```sql title="Count by type (per user)"
|
```sql title="Count by type (per user)"
|
||||||
SELECT "users"."email", "assets"."type", COUNT(*) FROM "assets"
|
SELECT "users"."email", "assets"."type", COUNT(*) FROM "assets"
|
||||||
JOIN "users" ON "assets"."ownerId" = "users"."id"
|
JOIN "users" ON "assets"."ownerId" = "users"."id"
|
||||||
GROUP BY "assets"."type", "users"."email"
|
GROUP BY "assets"."type", "users"."email" ORDER BY "users"."email";
|
||||||
ORDER BY "users"."email";
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```sql title="Failed file movements"
|
```sql title="Failed file movements"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ in a directory on the same machine.
|
|||||||
# Mount the directory into the containers.
|
# Mount the directory into the containers.
|
||||||
|
|
||||||
Edit `docker-compose.yml` to add one or more new mount points in the section `immich-server:` under `volumes:`.
|
Edit `docker-compose.yml` to add one or more new mount points in the section `immich-server:` under `volumes:`.
|
||||||
If you want Immich to be able to delete the images in the external library, remove `:ro` from the end of the mount point.
|
If you want Immich to be able to delete the images in the external library or add metadata ([XMP sidecars](/docs/features/xmp-sidecars)), remove `:ro` from the end of the mount point.
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
immich-server:
|
immich-server:
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ Never forward port 2283 directly to the internet without additional configuratio
|
|||||||
|
|
||||||
You may use a VPN service to open an encrypted connection to your Immich instance. OpenVPN and Wireguard are two popular VPN solutions. Here is a guide on setting up VPN access to your server - [Pihole documentation](https://docs.pi-hole.net/guides/vpn/wireguard/overview/)
|
You may use a VPN service to open an encrypted connection to your Immich instance. OpenVPN and Wireguard are two popular VPN solutions. Here is a guide on setting up VPN access to your server - [Pihole documentation](https://docs.pi-hole.net/guides/vpn/wireguard/overview/)
|
||||||
|
|
||||||
### Pros:
|
### Pros
|
||||||
|
|
||||||
- Simple to set up and very secure.
|
- Simple to set up and very secure.
|
||||||
- Single point of potential failure, i.e., the VPN software itself. Even if there is a zero-day vulnerability on Immich, you will not be at risk.
|
- Single point of potential failure, i.e., the VPN software itself. Even if there is a zero-day vulnerability on Immich, you will not be at risk.
|
||||||
- Both Wireguard and OpenVPN are independently security-audited, so the risk of serious zero-day exploits are minimal.
|
- Both Wireguard and OpenVPN are independently security-audited, so the risk of serious zero-day exploits are minimal.
|
||||||
|
|
||||||
### Cons:
|
### Cons
|
||||||
|
|
||||||
- If you don't have a static IP address, you would need to set up a [Dynamic DNS](https://www.cloudflare.com/learning/dns/glossary/dynamic-dns/). [DuckDNS](https://www.duckdns.org/) is a free DDNS provider.
|
- If you don't have a static IP address, you would need to set up a [Dynamic DNS](https://www.cloudflare.com/learning/dns/glossary/dynamic-dns/). [DuckDNS](https://www.duckdns.org/) is a free DDNS provider.
|
||||||
- VPN software needs to be installed and active on both server-side and client-side.
|
- VPN software needs to be installed and active on both server-side and client-side.
|
||||||
@@ -27,6 +27,10 @@ You may use a VPN service to open an encrypted connection to your Immich instanc
|
|||||||
|
|
||||||
If you are unable to open a port on your router for Wireguard or OpenVPN to your server, [Tailscale](https://tailscale.com/) is a good option. Tailscale mediates a peer-to-peer wireguard tunnel between your server and remote device, even if one or both of them are behind a [NAT firewall](https://en.wikipedia.org/wiki/Network_address_translation).
|
If you are unable to open a port on your router for Wireguard or OpenVPN to your server, [Tailscale](https://tailscale.com/) is a good option. Tailscale mediates a peer-to-peer wireguard tunnel between your server and remote device, even if one or both of them are behind a [NAT firewall](https://en.wikipedia.org/wiki/Network_address_translation).
|
||||||
|
|
||||||
|
:::tip Video toturial
|
||||||
|
You can learn how to set up Tailscale together with Immich with the [tutorial video](https://www.youtube.com/watch?v=Vt4PDUXB_fg) they created.
|
||||||
|
:::
|
||||||
|
|
||||||
### Pros
|
### Pros
|
||||||
|
|
||||||
- Minimal configuration needed on server and client sides.
|
- Minimal configuration needed on server and client sides.
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ To alleviate [performance issues on low-memory systems](/docs/FAQ.mdx#why-is-imm
|
|||||||
Smart Search and Face Detection will use this feature, but Facial Recognition is handled in the server.
|
Smart Search and Face Detection will use this feature, but Facial Recognition is handled in the server.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::danger
|
||||||
|
When using remote machine learning, the thumbnails are sent to the remote machine learning container. Use this option carefully when running this on a public computer or a paid processing cloud.
|
||||||
|
:::
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: immich_remote_ml
|
name: immich_remote_ml
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ Optionally, you can enable hardware acceleration for machine learning and transc
|
|||||||
|
|
||||||
- Populate custom database information if necessary.
|
- Populate custom database information if necessary.
|
||||||
- Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets.
|
- Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets.
|
||||||
- Consider changing `DB_PASSWORD` to something randomly generated
|
- Consider changing `DB_PASSWORD` to a custom value. Postgres is not publically exposed, so this password is only used for local authentication.
|
||||||
|
To avoid issues with Docker parsing this value, it is best to use only the characters `A-Za-z0-9`.
|
||||||
|
|
||||||
### Step 3 - Start the containers
|
### Step 3 - Start the containers
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ Immich is currently under heavy development, which means you can expect [breakin
|
|||||||
[compose-file]: https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
|
[compose-file]: https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
|
||||||
[env-file]: https://github.com/immich-app/immich/releases/latest/download/example.env
|
[env-file]: https://github.com/immich-app/immich/releases/latest/download/example.env
|
||||||
[watchtower]: https://containrrr.dev/watchtower/
|
[watchtower]: https://containrrr.dev/watchtower/
|
||||||
[breaking]: https://github.com/immich-app/immich/discussions?discussions_q=label%3Abreaking-change+sort%3Adate_created
|
[breaking]: https://github.com/immich-app/immich/discussions?discussions_q=label%3Achangelog%3Abreaking-change+sort%3Adate_created
|
||||||
[container-auth]: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry
|
[container-auth]: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry
|
||||||
[releases]: https://github.com/immich-app/immich/releases
|
[releases]: https://github.com/immich-app/immich/releases
|
||||||
[docker-repo]: https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
|
[docker-repo]: https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
|
||||||
|
|||||||
@@ -38,21 +38,23 @@ Regardless of filesystem, it is not recommended to use a network share for your
|
|||||||
|
|
||||||
## General
|
## General
|
||||||
|
|
||||||
| Variable | Description | Default | Containers | Workers |
|
| Variable | Description | Default | Containers | Workers |
|
||||||
| :---------------------------------- | :-------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
|
| :---------------------------------- | :---------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
|
||||||
| `TZ` | Timezone | | server | microservices |
|
| `TZ` | Timezone | | server | microservices |
|
||||||
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
|
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
|
||||||
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
|
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
|
||||||
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload`<sup>\*1</sup> | server | api, microservices |
|
| `IMMICH_MEDIA_LOCATION` | Media Location inside the container ⚠️**You probably shouldn't set this**<sup>\*1</sup>⚠️ | `./upload`<sup>\*2</sup> | server | api, microservices |
|
||||||
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
|
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
|
||||||
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
||||||
| `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | |
|
| `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | |
|
||||||
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
|
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
|
||||||
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
|
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
|
||||||
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
|
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
|
||||||
| `IMMICH_TRUSTED_PROXIES` | List of comma separated IPs set as trusted proxies | | server | api |
|
| `IMMICH_TRUSTED_PROXIES` | List of comma separated IPs set as trusted proxies | | server | api |
|
||||||
|
|
||||||
\*1: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
|
\*1: This path is where the Immich code looks for the files, which is internal to the docker container. Setting it to a path on your host will certainly break things, you should use the `UPLOAD_LOCATION` variable instead.
|
||||||
|
|
||||||
|
\*2: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
|
||||||
It only need to be set if the Immich deployment method is changing.
|
It only need to be set if the Immich deployment method is changing.
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
@@ -157,26 +159,29 @@ Redis (Sentinel) URL example JSON before encoding:
|
|||||||
|
|
||||||
## Machine Learning
|
## Machine Learning
|
||||||
|
|
||||||
| Variable | Description | Default | Containers |
|
| Variable | Description | Default | Containers |
|
||||||
| :----------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :-----------------------------------: | :--------------- |
|
| :-------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :-----------------------------------: | :--------------- |
|
||||||
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
|
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
|
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
|
||||||
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
|
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
|
||||||
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning |
|
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
|
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO image) | machine learning |
|
| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`<sup>\*3</sup> | HTTP Keep-alive time in seconds | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__CLIP` | Name of a CLIP model to be preloaded and kept in cache | | machine learning |
|
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO image) | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION` | Name of a facial recognition model to be preloaded and kept in cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__CLIP` | Name of a CLIP model to be preloaded and kept in cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION` | Name of a facial recognition model to be preloaded and kept in cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning |
|
| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning |
|
||||||
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
|
| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning |
|
||||||
|
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
|
||||||
|
|
||||||
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
||||||
|
|
||||||
\*2: Since each process duplicates models in memory, changing this is not recommended unless you have abundant memory to go around.
|
\*2: Since each process duplicates models in memory, changing this is not recommended unless you have abundant memory to go around.
|
||||||
|
|
||||||
|
\*3: For scenarios like HPA in K8S. https://github.com/immich-app/immich/discussions/12064
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|
||||||
Other machine learning parameters can be tuned from the admin UI.
|
Other machine learning parameters can be tuned from the admin UI.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ sidebar_position: 10
|
|||||||
|
|
||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
Hardware and software requirements for Immich
|
Hardware and software requirements for Immich:
|
||||||
|
|
||||||
## Software
|
## Software
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ width="70%"
|
|||||||
alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
|
alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
3. Select the cog ⚙️ next to Immich then click "**Edit Stack**"
|
3. Select the cogwheel ⚙️ next to Immich and click "**Edit Stack**"
|
||||||
4. Click "**Compose File**" and then paste the entire contents of the [Immich Docker Compose](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) file into the Unraid editor. Remove any text that may be in the text area by default. Note that Unraid v6.12.10 uses version 24.0.9 of the Docker Engine, which does not support healthcheck `start_interval` as defined in the `database` service of the Docker compose file (version 25 or higher is needed). This parameter defines an initial waiting period before starting health checks, to give the container time to start up. Commenting out the `start_interval` and `start_period` parameters will allow the containers to start up normally. The only downside to this is that the database container will not receive an initial health check until `interval` time has passed.
|
4. Click "**Compose File**" and then paste the entire contents of the [Immich Docker Compose](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) file into the Unraid editor. Remove any text that may be in the text area by default. Note that Unraid v6.12.10 uses version 24.0.9 of the Docker Engine, which does not support healthcheck `start_interval` as defined in the `database` service of the Docker compose file (version 25 or higher is needed). This parameter defines an initial waiting period before starting health checks, to give the container time to start up. Commenting out the `start_interval` and `start_period` parameters will allow the containers to start up normally. The only downside to this is that the database container will not receive an initial health check until `interval` time has passed.
|
||||||
|
|
||||||
<details >
|
<details >
|
||||||
@@ -130,7 +130,7 @@ For more information on how to use the application once installed, please refer
|
|||||||
|
|
||||||
## Updating Steps
|
## Updating Steps
|
||||||
|
|
||||||
Updating is extremely easy however it's important to be aware that containers managed via the Docker Compose Manager plugin do not integrate with Unraid's native dockerman ui, the label "_update ready_" will always be present on containers installed via the Docker Compose Manager.
|
Updating is extremely easy however it's important to be aware that containers managed via the Docker Compose Manager plugin do not integrate with Unraid's native dockerman UI, the label "_update ready_" will always be present on containers installed via the Docker Compose Manager.
|
||||||
|
|
||||||
<img
|
<img
|
||||||
src={require('./img/unraid09.png').default}
|
src={require('./img/unraid09.png').default}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 404 KiB After Width: | Height: | Size: 1.5 MiB |
528
docs/package-lock.json
generated
528
docs/package-lock.json
generated
@@ -2155,9 +2155,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/core": {
|
"node_modules/@docusaurus/core": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.5.2.tgz",
|
||||||
"integrity": "sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w==",
|
"integrity": "sha512-4Z1WkhCSkX4KO0Fw5m/Vuc7Q3NxBG53NE5u59Rs96fWkMPZVSrzEPP16/Nk6cWb/shK7xXPndTmalJtw7twL/w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.23.3",
|
"@babel/core": "^7.23.3",
|
||||||
@@ -2170,12 +2170,12 @@
|
|||||||
"@babel/runtime": "^7.22.6",
|
"@babel/runtime": "^7.22.6",
|
||||||
"@babel/runtime-corejs3": "^7.22.6",
|
"@babel/runtime-corejs3": "^7.22.6",
|
||||||
"@babel/traverse": "^7.22.8",
|
"@babel/traverse": "^7.22.8",
|
||||||
"@docusaurus/cssnano-preset": "3.4.0",
|
"@docusaurus/cssnano-preset": "3.5.2",
|
||||||
"@docusaurus/logger": "3.4.0",
|
"@docusaurus/logger": "3.5.2",
|
||||||
"@docusaurus/mdx-loader": "3.4.0",
|
"@docusaurus/mdx-loader": "3.5.2",
|
||||||
"@docusaurus/utils": "3.4.0",
|
"@docusaurus/utils": "3.5.2",
|
||||||
"@docusaurus/utils-common": "3.4.0",
|
"@docusaurus/utils-common": "3.5.2",
|
||||||
"@docusaurus/utils-validation": "3.4.0",
|
"@docusaurus/utils-validation": "3.5.2",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"babel-loader": "^9.1.3",
|
"babel-loader": "^9.1.3",
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
@@ -2236,14 +2236,15 @@
|
|||||||
"node": ">=18.0"
|
"node": ">=18.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"@mdx-js/react": "^3.0.0",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^18.0.0"
|
"react-dom": "^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/cssnano-preset": {
|
"node_modules/@docusaurus/cssnano-preset": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.5.2.tgz",
|
||||||
"integrity": "sha512-qwLFSz6v/pZHy/UP32IrprmH5ORce86BGtN0eBtG75PpzQJAzp9gefspox+s8IEOr0oZKuQ/nhzZ3xwyc3jYJQ==",
|
"integrity": "sha512-D3KiQXOMA8+O0tqORBrTOEQyQxNIfPm9jEaJoALjjSjc2M/ZAWcUfPQEnwr2JB2TadHw2gqWgpZckQmrVWkytA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssnano-preset-advanced": "^6.1.2",
|
"cssnano-preset-advanced": "^6.1.2",
|
||||||
@@ -2256,9 +2257,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/logger": {
|
"node_modules/@docusaurus/logger": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.5.2.tgz",
|
||||||
"integrity": "sha512-bZwkX+9SJ8lB9kVRkXw+xvHYSMGG4bpYHKGXeXFvyVc79NMeeBSGgzd4TQLHH+DYeOJoCdl8flrFJVxlZ0wo/Q==",
|
"integrity": "sha512-LHC540SGkeLfyT3RHK3gAMK6aS5TRqOD4R72BEU/DE2M/TY8WwEUAMY576UUc/oNJXv8pGhBmQB6N9p3pt8LQw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
@@ -2269,14 +2270,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/mdx-loader": {
|
"node_modules/@docusaurus/mdx-loader": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.5.2.tgz",
|
||||||
"integrity": "sha512-kSSbrrk4nTjf4d+wtBA9H+FGauf2gCax89kV8SUSJu3qaTdSIKdWERlngsiHaCFgZ7laTJ8a67UFf+xlFPtuTw==",
|
"integrity": "sha512-ku3xO9vZdwpiMIVd8BzWV0DCqGEbCP5zs1iHfKX50vw6jX8vQo0ylYo1YJMZyz6e+JFJ17HYHT5FzVidz2IflA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/logger": "3.4.0",
|
"@docusaurus/logger": "3.5.2",
|
||||||
"@docusaurus/utils": "3.4.0",
|
"@docusaurus/utils": "3.5.2",
|
||||||
"@docusaurus/utils-validation": "3.4.0",
|
"@docusaurus/utils-validation": "3.5.2",
|
||||||
"@mdx-js/mdx": "^3.0.0",
|
"@mdx-js/mdx": "^3.0.0",
|
||||||
"@slorber/remark-comment": "^1.0.0",
|
"@slorber/remark-comment": "^1.0.0",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
@@ -2308,12 +2309,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/module-type-aliases": {
|
"node_modules/@docusaurus/module-type-aliases": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.5.2.tgz",
|
||||||
"integrity": "sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==",
|
"integrity": "sha512-Z+Xu3+2rvKef/YKTMxZHsEXp1y92ac0ngjDiExRdqGTmEKtCUpkbNYH8v5eXo5Ls+dnW88n6WTa+Q54kLOkwPg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/types": "3.4.0",
|
"@docusaurus/types": "3.5.2",
|
||||||
"@types/history": "^4.7.11",
|
"@types/history": "^4.7.11",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"@types/react-router-config": "*",
|
"@types/react-router-config": "*",
|
||||||
@@ -2326,52 +2327,21 @@
|
|||||||
"react-dom": "*"
|
"react-dom": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/plugin-content-blog": {
|
|
||||||
"version": "3.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.4.0.tgz",
|
|
||||||
"integrity": "sha512-vv6ZAj78ibR5Jh7XBUT4ndIjmlAxkijM3Sx5MAAzC1gyv0vupDQNhzuFg1USQmQVj3P5I6bquk12etPV3LJ+Xw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@docusaurus/core": "3.4.0",
|
|
||||||
"@docusaurus/logger": "3.4.0",
|
|
||||||
"@docusaurus/mdx-loader": "3.4.0",
|
|
||||||
"@docusaurus/types": "3.4.0",
|
|
||||||
"@docusaurus/utils": "3.4.0",
|
|
||||||
"@docusaurus/utils-common": "3.4.0",
|
|
||||||
"@docusaurus/utils-validation": "3.4.0",
|
|
||||||
"cheerio": "^1.0.0-rc.12",
|
|
||||||
"feed": "^4.2.2",
|
|
||||||
"fs-extra": "^11.1.1",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"reading-time": "^1.5.0",
|
|
||||||
"srcset": "^4.0.0",
|
|
||||||
"tslib": "^2.6.0",
|
|
||||||
"unist-util-visit": "^5.0.0",
|
|
||||||
"utility-types": "^3.10.0",
|
|
||||||
"webpack": "^5.88.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^18.0.0",
|
|
||||||
"react-dom": "^18.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@docusaurus/plugin-content-docs": {
|
"node_modules/@docusaurus/plugin-content-docs": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.5.2.tgz",
|
||||||
"integrity": "sha512-HkUCZffhBo7ocYheD9oZvMcDloRnGhBMOZRyVcAQRFmZPmNqSyISlXA1tQCIxW+r478fty97XXAGjNYzBjpCsg==",
|
"integrity": "sha512-Bt+OXn/CPtVqM3Di44vHjE7rPCEsRCB/DMo2qoOuozB9f7+lsdrHvD0QCHdBs0uhz6deYJDppAr2VgqybKPlVQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.4.0",
|
"@docusaurus/core": "3.5.2",
|
||||||
"@docusaurus/logger": "3.4.0",
|
"@docusaurus/logger": "3.5.2",
|
||||||
"@docusaurus/mdx-loader": "3.4.0",
|
"@docusaurus/mdx-loader": "3.5.2",
|
||||||
"@docusaurus/module-type-aliases": "3.4.0",
|
"@docusaurus/module-type-aliases": "3.5.2",
|
||||||
"@docusaurus/types": "3.4.0",
|
"@docusaurus/theme-common": "3.5.2",
|
||||||
"@docusaurus/utils": "3.4.0",
|
"@docusaurus/types": "3.5.2",
|
||||||
"@docusaurus/utils-common": "3.4.0",
|
"@docusaurus/utils": "3.5.2",
|
||||||
"@docusaurus/utils-validation": "3.4.0",
|
"@docusaurus/utils-common": "3.5.2",
|
||||||
|
"@docusaurus/utils-validation": "3.5.2",
|
||||||
"@types/react-router-config": "^5.0.7",
|
"@types/react-router-config": "^5.0.7",
|
||||||
"combine-promises": "^1.1.0",
|
"combine-promises": "^1.1.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
@@ -2389,38 +2359,15 @@
|
|||||||
"react-dom": "^18.0.0"
|
"react-dom": "^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/plugin-content-pages": {
|
|
||||||
"version": "3.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz",
|
|
||||||
"integrity": "sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@docusaurus/core": "3.4.0",
|
|
||||||
"@docusaurus/mdx-loader": "3.4.0",
|
|
||||||
"@docusaurus/types": "3.4.0",
|
|
||||||
"@docusaurus/utils": "3.4.0",
|
|
||||||
"@docusaurus/utils-validation": "3.4.0",
|
|
||||||
"fs-extra": "^11.1.1",
|
|
||||||
"tslib": "^2.6.0",
|
|
||||||
"webpack": "^5.88.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^18.0.0",
|
|
||||||
"react-dom": "^18.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@docusaurus/plugin-debug": {
|
"node_modules/@docusaurus/plugin-debug": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.5.2.tgz",
|
||||||
"integrity": "sha512-uV7FDUNXGyDSD3PwUaf5YijX91T5/H9SX4ErEcshzwgzWwBtK37nUWPU3ZLJfeTavX3fycTOqk9TglpOLaWkCg==",
|
"integrity": "sha512-kBK6GlN0itCkrmHuCS6aX1wmoWc5wpd5KJlqQ1FyrF0cLDnvsYSnh7+ftdwzt7G6lGBho8lrVwkkL9/iQvaSOA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.4.0",
|
"@docusaurus/core": "3.5.2",
|
||||||
"@docusaurus/types": "3.4.0",
|
"@docusaurus/types": "3.5.2",
|
||||||
"@docusaurus/utils": "3.4.0",
|
"@docusaurus/utils": "3.5.2",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"react-json-view-lite": "^1.2.0",
|
"react-json-view-lite": "^1.2.0",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
@@ -2434,14 +2381,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/plugin-google-analytics": {
|
"node_modules/@docusaurus/plugin-google-analytics": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.5.2.tgz",
|
||||||
"integrity": "sha512-mCArluxEGi3cmYHqsgpGGt3IyLCrFBxPsxNZ56Mpur0xSlInnIHoeLDH7FvVVcPJRPSQ9/MfRqLsainRw+BojA==",
|
"integrity": "sha512-rjEkJH/tJ8OXRE9bwhV2mb/WP93V441rD6XnM6MIluu7rk8qg38iSxS43ga2V2Q/2ib53PcqbDEJDG/yWQRJhQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.4.0",
|
"@docusaurus/core": "3.5.2",
|
||||||
"@docusaurus/types": "3.4.0",
|
"@docusaurus/types": "3.5.2",
|
||||||
"@docusaurus/utils-validation": "3.4.0",
|
"@docusaurus/utils-validation": "3.5.2",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2453,14 +2400,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/plugin-google-gtag": {
|
"node_modules/@docusaurus/plugin-google-gtag": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.5.2.tgz",
|
||||||
"integrity": "sha512-Dsgg6PLAqzZw5wZ4QjUYc8Z2KqJqXxHxq3vIoyoBWiLEEfigIs7wHR+oiWUQy3Zk9MIk6JTYj7tMoQU0Jm3nqA==",
|
"integrity": "sha512-lm8XL3xLkTPHFKKjLjEEAHUrW0SZBSHBE1I+i/tmYMBsjCcUB5UJ52geS5PSiOCFVR74tbPGcPHEV/gaaxFeSA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.4.0",
|
"@docusaurus/core": "3.5.2",
|
||||||
"@docusaurus/types": "3.4.0",
|
"@docusaurus/types": "3.5.2",
|
||||||
"@docusaurus/utils-validation": "3.4.0",
|
"@docusaurus/utils-validation": "3.5.2",
|
||||||
"@types/gtag.js": "^0.0.12",
|
"@types/gtag.js": "^0.0.12",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
@@ -2473,14 +2420,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/plugin-google-tag-manager": {
|
"node_modules/@docusaurus/plugin-google-tag-manager": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.5.2.tgz",
|
||||||
"integrity": "sha512-O9tX1BTwxIhgXpOLpFDueYA9DWk69WCbDRrjYoMQtFHSkTyE7RhNgyjSPREUWJb9i+YUg3OrsvrBYRl64FCPCQ==",
|
"integrity": "sha512-QkpX68PMOMu10Mvgvr5CfZAzZQFx8WLlOiUQ/Qmmcl6mjGK6H21WLT5x7xDmcpCoKA/3CegsqIqBR+nA137lQg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.4.0",
|
"@docusaurus/core": "3.5.2",
|
||||||
"@docusaurus/types": "3.4.0",
|
"@docusaurus/types": "3.5.2",
|
||||||
"@docusaurus/utils-validation": "3.4.0",
|
"@docusaurus/utils-validation": "3.5.2",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2492,17 +2439,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/plugin-sitemap": {
|
"node_modules/@docusaurus/plugin-sitemap": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.5.2.tgz",
|
||||||
"integrity": "sha512-+0VDvx9SmNrFNgwPoeoCha+tRoAjopwT0+pYO1xAbyLcewXSemq+eLxEa46Q1/aoOaJQ0qqHELuQM7iS2gp33Q==",
|
"integrity": "sha512-DnlqYyRAdQ4NHY28TfHuVk414ft2uruP4QWCH//jzpHjqvKyXjj2fmDtI8RPUBh9K8iZKFMHRnLtzJKySPWvFA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.4.0",
|
"@docusaurus/core": "3.5.2",
|
||||||
"@docusaurus/logger": "3.4.0",
|
"@docusaurus/logger": "3.5.2",
|
||||||
"@docusaurus/types": "3.4.0",
|
"@docusaurus/types": "3.5.2",
|
||||||
"@docusaurus/utils": "3.4.0",
|
"@docusaurus/utils": "3.5.2",
|
||||||
"@docusaurus/utils-common": "3.4.0",
|
"@docusaurus/utils-common": "3.5.2",
|
||||||
"@docusaurus/utils-validation": "3.4.0",
|
"@docusaurus/utils-validation": "3.5.2",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"sitemap": "^7.1.1",
|
"sitemap": "^7.1.1",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
@@ -2516,24 +2463,81 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/preset-classic": {
|
"node_modules/@docusaurus/preset-classic": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.5.2.tgz",
|
||||||
"integrity": "sha512-Ohj6KB7siKqZaQhNJVMBBUzT3Nnp6eTKqO+FXO3qu/n1hJl3YLwVKTWBg28LF7MWrKu46UuYavwMRxud0VyqHg==",
|
"integrity": "sha512-3ihfXQ95aOHiLB5uCu+9PRy2gZCeSZoDcqpnDvf3B+sTrMvMTr8qRUzBvWkoIqc82yG5prCboRjk1SVILKx6sg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.4.0",
|
"@docusaurus/core": "3.5.2",
|
||||||
"@docusaurus/plugin-content-blog": "3.4.0",
|
"@docusaurus/plugin-content-blog": "3.5.2",
|
||||||
"@docusaurus/plugin-content-docs": "3.4.0",
|
"@docusaurus/plugin-content-docs": "3.5.2",
|
||||||
"@docusaurus/plugin-content-pages": "3.4.0",
|
"@docusaurus/plugin-content-pages": "3.5.2",
|
||||||
"@docusaurus/plugin-debug": "3.4.0",
|
"@docusaurus/plugin-debug": "3.5.2",
|
||||||
"@docusaurus/plugin-google-analytics": "3.4.0",
|
"@docusaurus/plugin-google-analytics": "3.5.2",
|
||||||
"@docusaurus/plugin-google-gtag": "3.4.0",
|
"@docusaurus/plugin-google-gtag": "3.5.2",
|
||||||
"@docusaurus/plugin-google-tag-manager": "3.4.0",
|
"@docusaurus/plugin-google-tag-manager": "3.5.2",
|
||||||
"@docusaurus/plugin-sitemap": "3.4.0",
|
"@docusaurus/plugin-sitemap": "3.5.2",
|
||||||
"@docusaurus/theme-classic": "3.4.0",
|
"@docusaurus/theme-classic": "3.5.2",
|
||||||
"@docusaurus/theme-common": "3.4.0",
|
"@docusaurus/theme-common": "3.5.2",
|
||||||
"@docusaurus/theme-search-algolia": "3.4.0",
|
"@docusaurus/theme-search-algolia": "3.5.2",
|
||||||
"@docusaurus/types": "3.4.0"
|
"@docusaurus/types": "3.5.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/preset-classic/node_modules/@docusaurus/plugin-content-blog": {
|
||||||
|
"version": "3.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.5.2.tgz",
|
||||||
|
"integrity": "sha512-R7ghWnMvjSf+aeNDH0K4fjyQnt5L0KzUEnUhmf1e3jZrv3wogeytZNN6n7X8yHcMsuZHPOrctQhXWnmxu+IRRg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/core": "3.5.2",
|
||||||
|
"@docusaurus/logger": "3.5.2",
|
||||||
|
"@docusaurus/mdx-loader": "3.5.2",
|
||||||
|
"@docusaurus/theme-common": "3.5.2",
|
||||||
|
"@docusaurus/types": "3.5.2",
|
||||||
|
"@docusaurus/utils": "3.5.2",
|
||||||
|
"@docusaurus/utils-common": "3.5.2",
|
||||||
|
"@docusaurus/utils-validation": "3.5.2",
|
||||||
|
"cheerio": "1.0.0-rc.12",
|
||||||
|
"feed": "^4.2.2",
|
||||||
|
"fs-extra": "^11.1.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"reading-time": "^1.5.0",
|
||||||
|
"srcset": "^4.0.0",
|
||||||
|
"tslib": "^2.6.0",
|
||||||
|
"unist-util-visit": "^5.0.0",
|
||||||
|
"utility-types": "^3.10.0",
|
||||||
|
"webpack": "^5.88.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@docusaurus/plugin-content-docs": "*",
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/preset-classic/node_modules/@docusaurus/plugin-content-pages": {
|
||||||
|
"version": "3.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.5.2.tgz",
|
||||||
|
"integrity": "sha512-WzhHjNpoQAUz/ueO10cnundRz+VUtkjFhhaQ9jApyv1a46FPURO4cef89pyNIOMny1fjDz/NUN2z6Yi+5WUrCw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/core": "3.5.2",
|
||||||
|
"@docusaurus/mdx-loader": "3.5.2",
|
||||||
|
"@docusaurus/types": "3.5.2",
|
||||||
|
"@docusaurus/utils": "3.5.2",
|
||||||
|
"@docusaurus/utils-validation": "3.5.2",
|
||||||
|
"fs-extra": "^11.1.1",
|
||||||
|
"tslib": "^2.6.0",
|
||||||
|
"webpack": "^5.88.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=18.0"
|
||||||
@@ -2544,27 +2548,27 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/theme-classic": {
|
"node_modules/@docusaurus/theme-classic": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.5.2.tgz",
|
||||||
"integrity": "sha512-0IPtmxsBYv2adr1GnZRdMkEQt1YW6tpzrUPj02YxNpvJ5+ju4E13J5tB4nfdaen/tfR1hmpSPlTFPvTf4kwy8Q==",
|
"integrity": "sha512-XRpinSix3NBv95Rk7xeMF9k4safMkwnpSgThn0UNQNumKvmcIYjfkwfh2BhwYh/BxMXQHJ/PdmNh22TQFpIaYg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.4.0",
|
"@docusaurus/core": "3.5.2",
|
||||||
"@docusaurus/mdx-loader": "3.4.0",
|
"@docusaurus/mdx-loader": "3.5.2",
|
||||||
"@docusaurus/module-type-aliases": "3.4.0",
|
"@docusaurus/module-type-aliases": "3.5.2",
|
||||||
"@docusaurus/plugin-content-blog": "3.4.0",
|
"@docusaurus/plugin-content-blog": "3.5.2",
|
||||||
"@docusaurus/plugin-content-docs": "3.4.0",
|
"@docusaurus/plugin-content-docs": "3.5.2",
|
||||||
"@docusaurus/plugin-content-pages": "3.4.0",
|
"@docusaurus/plugin-content-pages": "3.5.2",
|
||||||
"@docusaurus/theme-common": "3.4.0",
|
"@docusaurus/theme-common": "3.5.2",
|
||||||
"@docusaurus/theme-translations": "3.4.0",
|
"@docusaurus/theme-translations": "3.5.2",
|
||||||
"@docusaurus/types": "3.4.0",
|
"@docusaurus/types": "3.5.2",
|
||||||
"@docusaurus/utils": "3.4.0",
|
"@docusaurus/utils": "3.5.2",
|
||||||
"@docusaurus/utils-common": "3.4.0",
|
"@docusaurus/utils-common": "3.5.2",
|
||||||
"@docusaurus/utils-validation": "3.4.0",
|
"@docusaurus/utils-validation": "3.5.2",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"copy-text-to-clipboard": "^3.2.0",
|
"copy-text-to-clipboard": "^3.2.0",
|
||||||
"infima": "0.2.0-alpha.43",
|
"infima": "0.2.0-alpha.44",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"postcss": "^8.4.26",
|
"postcss": "^8.4.26",
|
||||||
@@ -2583,19 +2587,73 @@
|
|||||||
"react-dom": "^18.0.0"
|
"react-dom": "^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/theme-common": {
|
"node_modules/@docusaurus/theme-classic/node_modules/@docusaurus/plugin-content-blog": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.5.2.tgz",
|
||||||
"integrity": "sha512-0A27alXuv7ZdCg28oPE8nH/Iz73/IUejVaCazqu9elS4ypjiLhK3KfzdSQBnL/g7YfHSlymZKdiOHEo8fJ0qMA==",
|
"integrity": "sha512-R7ghWnMvjSf+aeNDH0K4fjyQnt5L0KzUEnUhmf1e3jZrv3wogeytZNN6n7X8yHcMsuZHPOrctQhXWnmxu+IRRg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/mdx-loader": "3.4.0",
|
"@docusaurus/core": "3.5.2",
|
||||||
"@docusaurus/module-type-aliases": "3.4.0",
|
"@docusaurus/logger": "3.5.2",
|
||||||
"@docusaurus/plugin-content-blog": "3.4.0",
|
"@docusaurus/mdx-loader": "3.5.2",
|
||||||
"@docusaurus/plugin-content-docs": "3.4.0",
|
"@docusaurus/theme-common": "3.5.2",
|
||||||
"@docusaurus/plugin-content-pages": "3.4.0",
|
"@docusaurus/types": "3.5.2",
|
||||||
"@docusaurus/utils": "3.4.0",
|
"@docusaurus/utils": "3.5.2",
|
||||||
"@docusaurus/utils-common": "3.4.0",
|
"@docusaurus/utils-common": "3.5.2",
|
||||||
|
"@docusaurus/utils-validation": "3.5.2",
|
||||||
|
"cheerio": "1.0.0-rc.12",
|
||||||
|
"feed": "^4.2.2",
|
||||||
|
"fs-extra": "^11.1.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"reading-time": "^1.5.0",
|
||||||
|
"srcset": "^4.0.0",
|
||||||
|
"tslib": "^2.6.0",
|
||||||
|
"unist-util-visit": "^5.0.0",
|
||||||
|
"utility-types": "^3.10.0",
|
||||||
|
"webpack": "^5.88.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@docusaurus/plugin-content-docs": "*",
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-classic/node_modules/@docusaurus/plugin-content-pages": {
|
||||||
|
"version": "3.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.5.2.tgz",
|
||||||
|
"integrity": "sha512-WzhHjNpoQAUz/ueO10cnundRz+VUtkjFhhaQ9jApyv1a46FPURO4cef89pyNIOMny1fjDz/NUN2z6Yi+5WUrCw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/core": "3.5.2",
|
||||||
|
"@docusaurus/mdx-loader": "3.5.2",
|
||||||
|
"@docusaurus/types": "3.5.2",
|
||||||
|
"@docusaurus/utils": "3.5.2",
|
||||||
|
"@docusaurus/utils-validation": "3.5.2",
|
||||||
|
"fs-extra": "^11.1.1",
|
||||||
|
"tslib": "^2.6.0",
|
||||||
|
"webpack": "^5.88.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-common": {
|
||||||
|
"version": "3.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.5.2.tgz",
|
||||||
|
"integrity": "sha512-QXqlm9S6x9Ibwjs7I2yEDgsCocp708DrCrgHgKwg2n2AY0YQ6IjU0gAK35lHRLOvAoJUfCKpQAwUykB0R7+Eew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/mdx-loader": "3.5.2",
|
||||||
|
"@docusaurus/module-type-aliases": "3.5.2",
|
||||||
|
"@docusaurus/utils": "3.5.2",
|
||||||
|
"@docusaurus/utils-common": "3.5.2",
|
||||||
"@types/history": "^4.7.11",
|
"@types/history": "^4.7.11",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"@types/react-router-config": "*",
|
"@types/react-router-config": "*",
|
||||||
@@ -2609,24 +2667,25 @@
|
|||||||
"node": ">=18.0"
|
"node": ">=18.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"@docusaurus/plugin-content-docs": "*",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^18.0.0"
|
"react-dom": "^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/theme-search-algolia": {
|
"node_modules/@docusaurus/theme-search-algolia": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.5.2.tgz",
|
||||||
"integrity": "sha512-aiHFx7OCw4Wck1z6IoShVdUWIjntC8FHCw9c5dR8r3q4Ynh+zkS8y2eFFunN/DL6RXPzpnvKCg3vhLQYJDmT9Q==",
|
"integrity": "sha512-qW53kp3VzMnEqZGjakaV90sst3iN1o32PH+nawv1uepROO8aEGxptcq2R5rsv7aBShSRbZwIobdvSYKsZ5pqvA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docsearch/react": "^3.5.2",
|
"@docsearch/react": "^3.5.2",
|
||||||
"@docusaurus/core": "3.4.0",
|
"@docusaurus/core": "3.5.2",
|
||||||
"@docusaurus/logger": "3.4.0",
|
"@docusaurus/logger": "3.5.2",
|
||||||
"@docusaurus/plugin-content-docs": "3.4.0",
|
"@docusaurus/plugin-content-docs": "3.5.2",
|
||||||
"@docusaurus/theme-common": "3.4.0",
|
"@docusaurus/theme-common": "3.5.2",
|
||||||
"@docusaurus/theme-translations": "3.4.0",
|
"@docusaurus/theme-translations": "3.5.2",
|
||||||
"@docusaurus/utils": "3.4.0",
|
"@docusaurus/utils": "3.5.2",
|
||||||
"@docusaurus/utils-validation": "3.4.0",
|
"@docusaurus/utils-validation": "3.5.2",
|
||||||
"algoliasearch": "^4.18.0",
|
"algoliasearch": "^4.18.0",
|
||||||
"algoliasearch-helper": "^3.13.3",
|
"algoliasearch-helper": "^3.13.3",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
@@ -2645,9 +2704,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/theme-translations": {
|
"node_modules/@docusaurus/theme-translations": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.5.2.tgz",
|
||||||
"integrity": "sha512-zSxCSpmQCCdQU5Q4CnX/ID8CSUUI3fvmq4hU/GNP/XoAWtXo9SAVnM3TzpU8Gb//H3WCsT8mJcTfyOk3d9ftNg==",
|
"integrity": "sha512-GPZLcu4aT1EmqSTmbdpVrDENGR2yObFEX8ssEFYTCiAIVc0EihNSdOIBTazUvgNqwvnoU1A8vIs1xyzc3LITTw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
@@ -2658,9 +2717,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/types": {
|
"node_modules/@docusaurus/types": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.5.2.tgz",
|
||||||
"integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==",
|
"integrity": "sha512-N6GntLXoLVUwkZw7zCxwy9QiuEXIcTVzA9AkmNw16oc0AP3SXLrMmDMMBIfgqwuKWa6Ox6epHol9kMtJqekACw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdx-js/mdx": "^3.0.0",
|
"@mdx-js/mdx": "^3.0.0",
|
||||||
@@ -2679,13 +2738,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/utils": {
|
"node_modules/@docusaurus/utils": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.5.2.tgz",
|
||||||
"integrity": "sha512-fRwnu3L3nnWaXOgs88BVBmG1yGjcQqZNHG+vInhEa2Sz2oQB+ZjbEMO5Rh9ePFpZ0YDiDUhpaVjwmS+AU2F14g==",
|
"integrity": "sha512-33QvcNFh+Gv+C2dP9Y9xWEzMgf3JzrpL2nW9PopidiohS1nDcyknKRx2DWaFvyVTTYIkkABVSr073VTj/NITNA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/logger": "3.4.0",
|
"@docusaurus/logger": "3.5.2",
|
||||||
"@docusaurus/utils-common": "3.4.0",
|
"@docusaurus/utils-common": "3.5.2",
|
||||||
"@svgr/webpack": "^8.1.0",
|
"@svgr/webpack": "^8.1.0",
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
@@ -2718,9 +2777,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/utils-common": {
|
"node_modules/@docusaurus/utils-common": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.5.2.tgz",
|
||||||
"integrity": "sha512-NVx54Wr4rCEKsjOH5QEVvxIqVvm+9kh7q8aYTU5WzUU9/Hctd6aTrcZ3G0Id4zYJ+AeaG5K5qHA4CY5Kcm2iyQ==",
|
"integrity": "sha512-i0AZjHiRgJU6d7faQngIhuHKNrszpL/SHQPgF1zH4H+Ij6E9NBYGy6pkcGWToIv7IVPbs+pQLh1P3whn0gWXVg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
@@ -2738,14 +2797,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/utils-validation": {
|
"node_modules/@docusaurus/utils-validation": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.5.2.tgz",
|
||||||
"integrity": "sha512-hYQ9fM+AXYVTWxJOT1EuNaRnrR2WGpRdLDQG07O8UOpsvCPWUVOeo26Rbm0JWY2sGLfzAb+tvJ62yF+8F+TV0g==",
|
"integrity": "sha512-m+Foq7augzXqB6HufdS139PFxDC5d5q2QKZy8q0qYYvGdI6nnlNsGH4cIGsgBnV7smz+mopl3g4asbSDvMV0jA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/logger": "3.4.0",
|
"@docusaurus/logger": "3.5.2",
|
||||||
"@docusaurus/utils": "3.4.0",
|
"@docusaurus/utils": "3.5.2",
|
||||||
"@docusaurus/utils-common": "3.4.0",
|
"@docusaurus/utils-common": "3.5.2",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"joi": "^17.9.2",
|
"joi": "^17.9.2",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
@@ -4237,9 +4296,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/autoprefixer": {
|
"node_modules/autoprefixer": {
|
||||||
"version": "10.4.19",
|
"version": "10.4.20",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
|
||||||
"integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
|
"integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -4254,12 +4313,13 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"browserslist": "^4.23.0",
|
"browserslist": "^4.23.3",
|
||||||
"caniuse-lite": "^1.0.30001599",
|
"caniuse-lite": "^1.0.30001646",
|
||||||
"fraction.js": "^4.3.7",
|
"fraction.js": "^4.3.7",
|
||||||
"normalize-range": "^0.1.2",
|
"normalize-range": "^0.1.2",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.1",
|
||||||
"postcss-value-parser": "^4.2.0"
|
"postcss-value-parser": "^4.2.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -4531,9 +4591,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/browserslist": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.23.0",
|
"version": "4.23.3",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
|
||||||
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
|
"integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -4548,11 +4608,12 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001587",
|
"caniuse-lite": "^1.0.30001646",
|
||||||
"electron-to-chromium": "^1.4.668",
|
"electron-to-chromium": "^1.5.4",
|
||||||
"node-releases": "^2.0.14",
|
"node-releases": "^2.0.18",
|
||||||
"update-browserslist-db": "^1.0.13"
|
"update-browserslist-db": "^1.1.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"browserslist": "cli.js"
|
"browserslist": "cli.js"
|
||||||
@@ -4699,9 +4760,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001614",
|
"version": "1.0.30001651",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001614.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
|
||||||
"integrity": "sha512-jmZQ1VpmlRwHgdP1/uiKzgiAuGOfLEJsYFP4+GBou/QQ4U6IOJCB4NP1c+1p9RGLpwObcT94jA5/uO+F1vBbog==",
|
"integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -4715,7 +4776,8 @@
|
|||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
"node_modules/ccount": {
|
"node_modules/ccount": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@@ -6342,9 +6404,10 @@
|
|||||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.751",
|
"version": "1.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.751.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz",
|
||||||
"integrity": "sha512-2DEPi++qa89SMGRhufWTiLmzqyuGmNF3SK4+PQetW1JKiZdEpF4XQonJXJCzyuYSA6mauiMhbyVhqYAP45Hvfw=="
|
"integrity": "sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==",
|
||||||
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "9.2.2",
|
"version": "9.2.2",
|
||||||
@@ -8763,9 +8826,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/infima": {
|
"node_modules/infima": {
|
||||||
"version": "0.2.0-alpha.43",
|
"version": "0.2.0-alpha.44",
|
||||||
"resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.43.tgz",
|
"resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.44.tgz",
|
||||||
"integrity": "sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ==",
|
"integrity": "sha512-tuRkUSO/lB3rEhLJk25atwAjgLuzq070+pOW8XcvpHky/YbENnRRdPd85IBkyeTgttmOy5ah+yHYsK1HhUd4lQ==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
@@ -11958,9 +12022,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.14",
|
"version": "2.0.18",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||||
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
|
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nopt": {
|
"node_modules/nopt": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
@@ -16015,9 +16080,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.7",
|
"version": "3.4.10",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz",
|
||||||
"integrity": "sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==",
|
"integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
@@ -16608,9 +16673,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.0.13",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
|
||||||
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
|
"integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -16625,9 +16690,10 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"escalade": "^3.1.1",
|
"escalade": "^3.1.2",
|
||||||
"picocolors": "^1.0.0"
|
"picocolors": "^1.0.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"update-browserslist-db": "cli.js"
|
"update-browserslist-db": "cli.js"
|
||||||
|
|||||||
@@ -56,6 +56,6 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.16.0"
|
"node": "20.17.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ const guides: CommunityGuidesProps[] = [
|
|||||||
description: 'Access your local Immich installation over the internet using your own domain',
|
description: 'Access your local Immich installation over the internet using your own domain',
|
||||||
url: 'https://github.com/ppr88/immich-guides/blob/main/open-immich-custom-domain.md',
|
url: 'https://github.com/ppr88/immich-guides/blob/main/open-immich-custom-domain.md',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Nginx caching map server',
|
||||||
|
description: 'Increase privacy by using nginx as a caching proxy in front of a map tile server',
|
||||||
|
url: 'https://github.com/pcouy/pcouy.github.io/blob/main/_posts/2024-08-30-proxying-a-map-tile-server-for-increased-privacy.md',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function CommunityGuide({ title, description, url }: CommunityGuidesProps): JSX.Element {
|
function CommunityGuide({ title, description, url }: CommunityGuidesProps): JSX.Element {
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ const projects: CommunityProjectProps[] = [
|
|||||||
description: 'A simple way to remove orphaned offline assets from the Immich database',
|
description: 'A simple way to remove orphaned offline assets from the Immich database',
|
||||||
url: 'https://github.com/Thoroslives/immich_remove_offline_files',
|
url: 'https://github.com/Thoroslives/immich_remove_offline_files',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Create albums from folders',
|
|
||||||
description: 'A Python script to create albums based on the folder structure of an external library.',
|
|
||||||
url: 'https://github.com/Salvoxia/immich-folder-album-creator',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Immich-Tools',
|
title: 'Immich-Tools',
|
||||||
description: 'Provides scripts for handling problems on the repair page.',
|
description: 'Provides scripts for handling problems on the repair page.',
|
||||||
@@ -58,6 +53,11 @@ const projects: CommunityProjectProps[] = [
|
|||||||
description: 'Unofficial Immich Android TV app.',
|
description: 'Unofficial Immich Android TV app.',
|
||||||
url: 'https://github.com/giejay/Immich-Android-TV',
|
url: 'https://github.com/giejay/Immich-Android-TV',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Create albums from folders',
|
||||||
|
description: 'A Python script to create albums based on the folder structure of an external library.',
|
||||||
|
url: 'https://github.com/Salvoxia/immich-folder-album-creator',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Powershell Module PSImmich',
|
title: 'Powershell Module PSImmich',
|
||||||
description: 'Powershell Module for the Immich API',
|
description: 'Powershell Module for the Immich API',
|
||||||
@@ -68,6 +68,16 @@ const projects: CommunityProjectProps[] = [
|
|||||||
description: 'Snap package for easy install and zero-care auto updates of Immich. Self-hosted photo management.',
|
description: 'Snap package for easy install and zero-care auto updates of Immich. Self-hosted photo management.',
|
||||||
url: 'https://immich-distribution.nsg.cc',
|
url: 'https://immich-distribution.nsg.cc',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Immich Kiosk',
|
||||||
|
description: 'Lightweight slideshow to run on kiosk devices and browsers.',
|
||||||
|
url: 'https://github.com/damongolding/immich-kiosk',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Immich Power Tools',
|
||||||
|
description: 'Power tools for organizing your immich library.',
|
||||||
|
url: 'https://github.com/varun-raj/immich-power-tools',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element {
|
function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import '@docusaurus/theme-classic/lib/theme/Unlisted/index';
|
|
||||||
import { useWindowSize } from '@docusaurus/theme-common';
|
import { useWindowSize } from '@docusaurus/theme-common';
|
||||||
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
|
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
mdiCloudUploadOutline,
|
mdiCloudUploadOutline,
|
||||||
mdiCollage,
|
mdiCollage,
|
||||||
mdiContentDuplicate,
|
mdiContentDuplicate,
|
||||||
|
mdiCrop,
|
||||||
mdiDevices,
|
mdiDevices,
|
||||||
mdiEmailOutline,
|
mdiEmailOutline,
|
||||||
mdiExpansionCard,
|
mdiExpansionCard,
|
||||||
@@ -26,6 +27,7 @@ import {
|
|||||||
mdiFileSearch,
|
mdiFileSearch,
|
||||||
mdiFlash,
|
mdiFlash,
|
||||||
mdiFolder,
|
mdiFolder,
|
||||||
|
mdiFolderMultiple,
|
||||||
mdiForum,
|
mdiForum,
|
||||||
mdiHandshakeOutline,
|
mdiHandshakeOutline,
|
||||||
mdiHeart,
|
mdiHeart,
|
||||||
@@ -36,6 +38,7 @@ import {
|
|||||||
mdiImageMultipleOutline,
|
mdiImageMultipleOutline,
|
||||||
mdiImageSearch,
|
mdiImageSearch,
|
||||||
mdiKeyboardSettingsOutline,
|
mdiKeyboardSettingsOutline,
|
||||||
|
mdiLicense,
|
||||||
mdiLockOutline,
|
mdiLockOutline,
|
||||||
mdiMagnify,
|
mdiMagnify,
|
||||||
mdiMagnifyScan,
|
mdiMagnifyScan,
|
||||||
@@ -55,25 +58,29 @@ import {
|
|||||||
mdiScaleBalance,
|
mdiScaleBalance,
|
||||||
mdiSecurity,
|
mdiSecurity,
|
||||||
mdiServer,
|
mdiServer,
|
||||||
|
mdiShare,
|
||||||
mdiShareAll,
|
mdiShareAll,
|
||||||
mdiShareCircle,
|
mdiShareCircle,
|
||||||
mdiStar,
|
mdiStar,
|
||||||
|
mdiStarOutline,
|
||||||
mdiTableKey,
|
mdiTableKey,
|
||||||
mdiTag,
|
mdiTag,
|
||||||
|
mdiTagMultiple,
|
||||||
mdiText,
|
mdiText,
|
||||||
mdiThemeLightDark,
|
mdiThemeLightDark,
|
||||||
mdiTrashCanOutline,
|
mdiTrashCanOutline,
|
||||||
mdiVectorCombine,
|
mdiVectorCombine,
|
||||||
mdiVideo,
|
mdiVideo,
|
||||||
mdiWeb,
|
mdiWeb,
|
||||||
mdiLicense,
|
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Item, Timeline } from '../components/timeline';
|
import { Item, Timeline } from '../components/timeline';
|
||||||
|
|
||||||
const releases = {
|
const releases = {
|
||||||
// TODO
|
'v1.113.0': new Date(2024, 7, 30),
|
||||||
|
'v1.112.0': new Date(2024, 7, 14),
|
||||||
|
'v1.111.0': new Date(2024, 6, 26),
|
||||||
'v1.110.0': new Date(2024, 5, 11),
|
'v1.110.0': new Date(2024, 5, 11),
|
||||||
'v1.109.0': new Date(2024, 6, 18),
|
'v1.109.0': new Date(2024, 6, 18),
|
||||||
'v1.106.1': new Date(2024, 5, 11),
|
'v1.106.1': new Date(2024, 5, 11),
|
||||||
@@ -224,6 +231,47 @@ const roadmap: Item[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const milestones: Item[] = [
|
const milestones: Item[] = [
|
||||||
|
withRelease({
|
||||||
|
icon: mdiTagMultiple,
|
||||||
|
iconColor: 'orange',
|
||||||
|
title: 'Tags',
|
||||||
|
description: 'Tag your photos and videos',
|
||||||
|
release: 'v1.113.0',
|
||||||
|
}),
|
||||||
|
withRelease({
|
||||||
|
icon: mdiFolderMultiple,
|
||||||
|
iconColor: 'brown',
|
||||||
|
title: 'Folders',
|
||||||
|
description: 'View your photos and videos in folders',
|
||||||
|
release: 'v1.113.0',
|
||||||
|
}),
|
||||||
|
withRelease({
|
||||||
|
icon: mdiPalette,
|
||||||
|
title: 'Theming (mobile)',
|
||||||
|
description: 'Pick a primary color for the mobile app',
|
||||||
|
release: 'v1.112.0',
|
||||||
|
}),
|
||||||
|
withRelease({
|
||||||
|
icon: mdiStarOutline,
|
||||||
|
iconColor: 'gold',
|
||||||
|
title: 'Star rating',
|
||||||
|
description: 'Rate your photos and videos',
|
||||||
|
release: 'v1.112.0',
|
||||||
|
}),
|
||||||
|
withRelease({
|
||||||
|
icon: mdiCrop,
|
||||||
|
iconColor: 'royalblue',
|
||||||
|
title: 'Editor (mobile)',
|
||||||
|
description: 'Crop and rotate on mobile',
|
||||||
|
release: 'v1.111.0',
|
||||||
|
}),
|
||||||
|
withRelease({
|
||||||
|
icon: mdiMap,
|
||||||
|
iconColor: 'green',
|
||||||
|
title: 'Deploy tiles.immich.cloud',
|
||||||
|
description: 'Dedicated tile server for Immich',
|
||||||
|
release: 'v1.111.0',
|
||||||
|
}),
|
||||||
{
|
{
|
||||||
icon: mdiStar,
|
icon: mdiStar,
|
||||||
iconColor: 'gold',
|
iconColor: 'gold',
|
||||||
@@ -231,6 +279,12 @@ const milestones: Item[] = [
|
|||||||
description: 'Reached 40K Stars on GitHub!',
|
description: 'Reached 40K Stars on GitHub!',
|
||||||
getDateLabel: withLanguage(new Date(2024, 6, 21)),
|
getDateLabel: withLanguage(new Date(2024, 6, 21)),
|
||||||
},
|
},
|
||||||
|
withRelease({
|
||||||
|
icon: mdiShare,
|
||||||
|
title: 'Deploy my.immich.app',
|
||||||
|
description: 'Url router for immich links',
|
||||||
|
release: 'v1.109.0',
|
||||||
|
}),
|
||||||
withRelease({
|
withRelease({
|
||||||
icon: mdiLicense,
|
icon: mdiLicense,
|
||||||
iconColor: 'gold',
|
iconColor: 'gold',
|
||||||
|
|||||||
12
docs/static/archived-versions.json
vendored
12
docs/static/archived-versions.json
vendored
@@ -1,4 +1,16 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"label": "v1.113.0",
|
||||||
|
"url": "https://v1.113.0.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v1.112.1",
|
||||||
|
"url": "https://v1.112.1.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v1.112.0",
|
||||||
|
"url": "https://v1.112.0.archive.immich.app"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "v1.111.0",
|
"label": "v1.111.0",
|
||||||
"url": "https://v1.111.0.archive.immich.app"
|
"url": "https://v1.111.0.archive.immich.app"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module.exports = {
|
|||||||
corePlugins: {
|
corePlugins: {
|
||||||
preflight: false, // disable Tailwind's reset
|
preflight: false, // disable Tailwind's reset
|
||||||
},
|
},
|
||||||
content: ['./src/**/*.{js,jsx,ts,tsx}', '../docs/**/*.mdx'], // my markdown stuff is in ../docs, not /src
|
content: ['./src/**/*.{js,jsx,ts,tsx}', './{docs,blog}/**/*.{md,mdx}'], // my markdown stuff is in ../docs, not /src
|
||||||
darkMode: ['class', '[data-theme="dark"]'], // hooks into docusaurus' dark mode settigns
|
darkMode: ['class', '[data-theme="dark"]'], // hooks into docusaurus' dark mode settigns
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
20.16.0
|
20.17.0
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
name: immich-e2e
|
name: immich-e2e
|
||||||
|
|
||||||
services:
|
services:
|
||||||
@@ -32,7 +30,7 @@ services:
|
|||||||
- redis
|
- redis
|
||||||
- database
|
- database
|
||||||
ports:
|
ports:
|
||||||
- 2283:3001
|
- 2285:3001
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:6.2-alpine@sha256:e3b17ba9479deec4b7d1eeec1548a253acc5374d68d3b27937fcfe4df8d18c7e
|
image: redis:6.2-alpine@sha256:e3b17ba9479deec4b7d1eeec1548a253acc5374d68d3b27937fcfe4df8d18c7e
|
||||||
@@ -45,7 +43,7 @@ services:
|
|||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: immich
|
POSTGRES_DB: immich
|
||||||
ports:
|
ports:
|
||||||
- 5433:5432
|
- 5435:5432
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
model-cache:
|
model-cache:
|
||||||
|
|||||||
196
e2e/package-lock.json
generated
196
e2e/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.111.0",
|
"version": "1.113.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.111.0",
|
"version": "1.113.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.44.1",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^20.14.13",
|
"@types/node": "^20.16.1",
|
||||||
"@types/oidc-provider": "^8.5.1",
|
"@types/oidc-provider": "^8.5.1",
|
||||||
"@types/pg": "^8.11.0",
|
"@types/pg": "^8.11.0",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
},
|
},
|
||||||
"../cli": {
|
"../cli": {
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.13",
|
"version": "2.2.16",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
"@types/cli-progress": "^3.11.0",
|
"@types/cli-progress": "^3.11.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^20.14.13",
|
"@types/node": "^20.16.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@vitest/coverage-v8": "^2.0.5",
|
"@vitest/coverage-v8": "^2.0.5",
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
"prettier-plugin-organize-imports": "^4.0.0",
|
"prettier-plugin-organize-imports": "^4.0.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.0.12",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^5.0.0",
|
||||||
"vitest": "^2.0.5",
|
"vitest": "^2.0.5",
|
||||||
"vitest-fetch-mock": "^0.3.0",
|
"vitest-fetch-mock": "^0.3.0",
|
||||||
"yaml": "^2.3.1"
|
"yaml": "^2.3.1"
|
||||||
@@ -92,14 +92,14 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.111.0",
|
"version": "1.113.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.14.13",
|
"@types/node": "^20.16.1",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -799,9 +799,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "9.8.0",
|
"version": "9.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.0.tgz",
|
||||||
"integrity": "sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==",
|
"integrity": "sha512-hhetes6ZHP3BlXLxmd8K2SNgkhNSi+UcecbnwWKwpP7kyi/uC75DJ1lOOBO3xrC4jyojtGE3YxKZPHfk4yrgug==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1113,13 +1113,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@playwright/test": {
|
"node_modules/@playwright/test": {
|
||||||
"version": "1.45.3",
|
"version": "1.46.1",
|
||||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.3.tgz",
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.1.tgz",
|
||||||
"integrity": "sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==",
|
"integrity": "sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.45.3"
|
"playwright": "1.46.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@@ -1516,13 +1516,13 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.14.14",
|
"version": "20.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.1.tgz",
|
||||||
"integrity": "sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==",
|
"integrity": "sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~6.19.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/normalize-package-data": {
|
"node_modules/@types/normalize-package-data": {
|
||||||
@@ -1532,10 +1532,11 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/oidc-provider": {
|
"node_modules/@types/oidc-provider": {
|
||||||
"version": "8.5.1",
|
"version": "8.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/oidc-provider/-/oidc-provider-8.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/oidc-provider/-/oidc-provider-8.5.2.tgz",
|
||||||
"integrity": "sha512-NS8tBPOj9GG6SxyrUHWBzglOtAYNDX41J4cRE45oeK0iSqI6V6tDW70aPWg25pJFNSC1evccXFm9evfwjxm7HQ==",
|
"integrity": "sha512-NiD3VG49+cRCAAe8+uZLM4onOcX8y9+cwaml8JG1qlgc98rWoCRgsnOB4Ypx+ysays5jiwzfUgT0nWyXPB/9uQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/koa": "*",
|
"@types/koa": "*",
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
@@ -1673,17 +1674,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.2.0.tgz",
|
||||||
"integrity": "sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==",
|
"integrity": "sha512-02tJIs655em7fvt9gps/+4k4OsKULYGtLBPJfOsmOq1+3cdClYiF0+d6mHu6qDnTcg88wJBkcPLpQhq7FyDz0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.0.1",
|
"@typescript-eslint/scope-manager": "8.2.0",
|
||||||
"@typescript-eslint/type-utils": "8.0.1",
|
"@typescript-eslint/type-utils": "8.2.0",
|
||||||
"@typescript-eslint/utils": "8.0.1",
|
"@typescript-eslint/utils": "8.2.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.0.1",
|
"@typescript-eslint/visitor-keys": "8.2.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@@ -1707,16 +1708,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.2.0.tgz",
|
||||||
"integrity": "sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==",
|
"integrity": "sha512-j3Di+o0lHgPrb7FxL3fdEy6LJ/j2NE8u+AP/5cQ9SKb+JLH6V6UHDqJ+e0hXBkHP1wn1YDFjYCS9LBQsZDlDEg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.0.1",
|
"@typescript-eslint/scope-manager": "8.2.0",
|
||||||
"@typescript-eslint/types": "8.0.1",
|
"@typescript-eslint/types": "8.2.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.0.1",
|
"@typescript-eslint/typescript-estree": "8.2.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.0.1",
|
"@typescript-eslint/visitor-keys": "8.2.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1736,14 +1737,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.2.0.tgz",
|
||||||
"integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==",
|
"integrity": "sha512-OFn80B38yD6WwpoHU2Tz/fTz7CgFqInllBoC3WP+/jLbTb4gGPTy9HBSTsbDWkMdN55XlVU0mMDYAtgvlUspGw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.0.1",
|
"@typescript-eslint/types": "8.2.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.0.1"
|
"@typescript-eslint/visitor-keys": "8.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -1754,14 +1755,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.2.0.tgz",
|
||||||
"integrity": "sha512-+/UT25MWvXeDX9YaHv1IS6KI1fiuTto43WprE7pgSMswHbn1Jm9GEM4Txp+X74ifOWV8emu2AWcbLhpJAvD5Ng==",
|
"integrity": "sha512-g1CfXGFMQdT5S+0PSO0fvGXUaiSkl73U1n9LTK5aRAFnPlJ8dLKkXr4AaLFvPedW8lVDoMgLLE3JN98ZZfsj0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "8.0.1",
|
"@typescript-eslint/typescript-estree": "8.2.0",
|
||||||
"@typescript-eslint/utils": "8.0.1",
|
"@typescript-eslint/utils": "8.2.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.3.0"
|
"ts-api-utils": "^1.3.0"
|
||||||
},
|
},
|
||||||
@@ -1779,9 +1780,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.2.0.tgz",
|
||||||
"integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==",
|
"integrity": "sha512-6a9QSK396YqmiBKPkJtxsgZZZVjYQ6wQ/TlI0C65z7vInaETuC6HAHD98AGLC8DyIPqHytvNuS8bBVvNLKyqvQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1793,14 +1794,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.2.0.tgz",
|
||||||
"integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==",
|
"integrity": "sha512-kiG4EDUT4dImplOsbh47B1QnNmXSoUqOjWDvCJw/o8LgfD0yr7k2uy54D5Wm0j4t71Ge1NkynGhpWdS0dEIAUA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.0.1",
|
"@typescript-eslint/types": "8.2.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.0.1",
|
"@typescript-eslint/visitor-keys": "8.2.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"globby": "^11.1.0",
|
"globby": "^11.1.0",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@@ -1848,16 +1849,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.2.0.tgz",
|
||||||
"integrity": "sha512-CBFR0G0sCt0+fzfnKaciu9IBsKvEKYwN9UZ+eeogK1fYHg4Qxk1yf/wLQkLXlq8wbU2dFlgAesxt8Gi76E8RTA==",
|
"integrity": "sha512-O46eaYKDlV3TvAVDNcoDzd5N550ckSe8G4phko++OCSC1dYIb9LTc3HDGYdWqWIAT5qDUKphO6sd9RrpIJJPfg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@typescript-eslint/scope-manager": "8.0.1",
|
"@typescript-eslint/scope-manager": "8.2.0",
|
||||||
"@typescript-eslint/types": "8.0.1",
|
"@typescript-eslint/types": "8.2.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.0.1"
|
"@typescript-eslint/typescript-estree": "8.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -1871,13 +1872,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.0.1",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.2.0.tgz",
|
||||||
"integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==",
|
"integrity": "sha512-sbgsPMW9yLvS7IhCi8IpuK1oBmtbWUNP+hBdwl/I9nzqVsszGnNGti5r9dUtF5RLivHUFFIdRvLiTsPhzSyJ3Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.0.1",
|
"@typescript-eslint/types": "8.2.0",
|
||||||
"eslint-visitor-keys": "^3.4.3"
|
"eslint-visitor-keys": "^3.4.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2888,9 +2889,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "9.8.0",
|
"version": "9.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.0.tgz",
|
||||||
"integrity": "sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==",
|
"integrity": "sha512-JfiKJrbx0506OEerjK2Y1QlldtBxkAlLxT5OEcRF8uaQ86noDe2k31Vw9rnSWv+MXZHj7OOUV/dA0AhdLFcyvA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2898,7 +2899,7 @@
|
|||||||
"@eslint-community/regexpp": "^4.11.0",
|
"@eslint-community/regexpp": "^4.11.0",
|
||||||
"@eslint/config-array": "^0.17.1",
|
"@eslint/config-array": "^0.17.1",
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
"@eslint/js": "9.8.0",
|
"@eslint/js": "9.9.0",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@humanwhocodes/retry": "^0.3.0",
|
"@humanwhocodes/retry": "^0.3.0",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
@@ -2937,6 +2938,14 @@
|
|||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://eslint.org/donate"
|
"url": "https://eslint.org/donate"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"jiti": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"jiti": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-config-prettier": {
|
"node_modules/eslint-config-prettier": {
|
||||||
@@ -3176,9 +3185,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/exiftool-vendored": {
|
"node_modules/exiftool-vendored": {
|
||||||
"version": "28.2.0",
|
"version": "28.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.2.1.tgz",
|
||||||
"integrity": "sha512-s2k92EB8LSeYjXv4agtpANeH8y1CsEThYqMm7AF1jP64PyFb40AoD0RGf69j28G6RqXkT5JGl4Xwk9kOy3IkjQ==",
|
"integrity": "sha512-D3YsKErr3BbjKeJzUVsv6CVZ+SQNgAJKPFWVLXu0CBtr24FNuE3CZBXWKWysGu0MjzeDCNwQrQI5+bXUFeiYVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3186,7 +3195,7 @@
|
|||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"batch-cluster": "^13.0.0",
|
"batch-cluster": "^13.0.0",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"luxon": "^3.4.4"
|
"luxon": "^3.5.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"exiftool-vendored.exe": "12.91.0",
|
"exiftool-vendored.exe": "12.91.0",
|
||||||
@@ -4125,10 +4134,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jose": {
|
"node_modules/jose": {
|
||||||
"version": "5.6.3",
|
"version": "5.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/jose/-/jose-5.7.0.tgz",
|
||||||
"integrity": "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==",
|
"integrity": "sha512-3P9qfTYDVnNn642LCAqIKbTGb9a1TBxZ9ti5zEVEr48aDdflgRjhspWFb6WM4PzAfFbGMJYC4+803v8riCRAKw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/panva"
|
"url": "https://github.com/sponsors/panva"
|
||||||
}
|
}
|
||||||
@@ -4357,10 +4367,11 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/luxon": {
|
"node_modules/luxon": {
|
||||||
"version": "3.4.4",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz",
|
||||||
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
|
"integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
@@ -4435,9 +4446,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/micromatch": {
|
"node_modules/micromatch": {
|
||||||
"version": "4.0.7",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -5177,13 +5188,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/playwright": {
|
"node_modules/playwright": {
|
||||||
"version": "1.45.3",
|
"version": "1.46.1",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.3.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.1.tgz",
|
||||||
"integrity": "sha512-QhVaS+lpluxCaioejDZ95l4Y4jSFCsBvl2UZkpeXlzxmqS+aABr5c82YmfMHrL6x27nvrvykJAFpkzT2eWdJww==",
|
"integrity": "sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.45.3"
|
"playwright-core": "1.46.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@@ -5196,9 +5207,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/playwright-core": {
|
"node_modules/playwright-core": {
|
||||||
"version": "1.45.3",
|
"version": "1.46.1",
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.3.tgz",
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.1.tgz",
|
||||||
"integrity": "sha512-+ym0jNbcjikaOwwSZycFbwkWgfruWvYlJfThKYAlImbxUgdWFO2oW70ojPm4OpE4t6TAo2FY/smM+hpVTtkhDA==",
|
"integrity": "sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -6338,10 +6349,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "5.26.5",
|
"version": "6.19.8",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unpipe": {
|
"node_modules/unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.111.0",
|
"version": "1.113.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.44.1",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^20.14.13",
|
"@types/node": "^20.16.1",
|
||||||
"@types/oidc-provider": "^8.5.1",
|
"@types/oidc-provider": "^8.5.1",
|
||||||
"@types/pg": "^8.11.0",
|
"@types/pg": "^8.11.0",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
@@ -53,6 +53,6 @@
|
|||||||
"vitest": "^2.0.5"
|
"vitest": "^2.0.5"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.16.0"
|
"node": "20.17.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default defineConfig({
|
|||||||
workers: 1,
|
workers: 1,
|
||||||
reporter: 'html',
|
reporter: 'html',
|
||||||
use: {
|
use: {
|
||||||
baseURL: 'http://127.0.0.1:2283',
|
baseURL: 'http://127.0.0.1:2285',
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ export default defineConfig({
|
|||||||
/* Run your local dev server before starting the tests */
|
/* Run your local dev server before starting the tests */
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'docker compose up --build -V --remove-orphans',
|
command: 'docker compose up --build -V --remove-orphans',
|
||||||
url: 'http://127.0.0.1:2283',
|
url: 'http://127.0.0.1:2285',
|
||||||
reuseExistingServer: true,
|
reuseExistingServer: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -344,16 +344,16 @@ describe('/albums', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /albums/count', () => {
|
describe('GET /albums/statistics', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(app).get('/albums/count');
|
const { status, body } = await request(app).get('/albums/statistics');
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return total count of albums the user has access to', async () => {
|
it('should return total count of albums the user has access to', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get('/albums/count')
|
.get('/albums/statistics')
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
|
|||||||
258
e2e/src/api/specs/api-key.e2e-spec.ts
Normal file
258
e2e/src/api/specs/api-key.e2e-spec.ts
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
import { LoginResponseDto, Permission, createApiKey } from '@immich/sdk';
|
||||||
|
import { createUserDto, uuidDto } from 'src/fixtures';
|
||||||
|
import { errorDto } from 'src/responses';
|
||||||
|
import { app, asBearerAuth, utils } from 'src/utils';
|
||||||
|
import request from 'supertest';
|
||||||
|
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
const create = (accessToken: string, permissions: Permission[]) =>
|
||||||
|
createApiKey({ apiKeyCreateDto: { name: 'api key', permissions } }, { headers: asBearerAuth(accessToken) });
|
||||||
|
|
||||||
|
describe('/api-keys', () => {
|
||||||
|
let admin: LoginResponseDto;
|
||||||
|
let user: LoginResponseDto;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await utils.resetDatabase();
|
||||||
|
|
||||||
|
admin = await utils.adminSetup();
|
||||||
|
user = await utils.userSetup(admin.accessToken, createUserDto.user1);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await utils.resetDatabase(['api_keys']);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /api-keys', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).post('/api-keys').send({ name: 'API Key' });
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not work without permission', async () => {
|
||||||
|
const { secret } = await create(user.accessToken, [Permission.ApiKeyRead]);
|
||||||
|
const { status, body } = await request(app).post('/api-keys').set('x-api-key', secret).send({ name: 'API Key' });
|
||||||
|
expect(status).toBe(403);
|
||||||
|
expect(body).toEqual(errorDto.missingPermission('apiKey.create'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with apiKey.create', async () => {
|
||||||
|
const { secret } = await create(user.accessToken, [Permission.ApiKeyCreate, Permission.ApiKeyRead]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/api-keys')
|
||||||
|
.set('x-api-key', secret)
|
||||||
|
.send({
|
||||||
|
name: 'API Key',
|
||||||
|
permissions: [Permission.ApiKeyRead],
|
||||||
|
});
|
||||||
|
expect(body).toEqual({
|
||||||
|
secret: expect.any(String),
|
||||||
|
apiKey: {
|
||||||
|
id: expect.any(String),
|
||||||
|
name: 'API Key',
|
||||||
|
permissions: [Permission.ApiKeyRead],
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(status).toBe(201);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create an api key with all permissions', async () => {
|
||||||
|
const { secret } = await create(user.accessToken, [Permission.ApiKeyCreate]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/api-keys')
|
||||||
|
.set('x-api-key', secret)
|
||||||
|
.send({ name: 'API Key', permissions: [Permission.All] });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest('Cannot grant permissions you do not have'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create an api key with more permissions', async () => {
|
||||||
|
const { secret } = await create(user.accessToken, [Permission.ApiKeyCreate]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/api-keys')
|
||||||
|
.set('x-api-key', secret)
|
||||||
|
.send({ name: 'API Key', permissions: [Permission.ApiKeyRead] });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest('Cannot grant permissions you do not have'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an api key', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/api-keys')
|
||||||
|
.send({ name: 'API Key', permissions: [Permission.All] })
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(body).toEqual({
|
||||||
|
apiKey: {
|
||||||
|
id: expect.any(String),
|
||||||
|
name: 'API Key',
|
||||||
|
permissions: [Permission.All],
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String),
|
||||||
|
},
|
||||||
|
secret: expect.any(String),
|
||||||
|
});
|
||||||
|
expect(status).toEqual(201);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api-keys', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).get('/api-keys');
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should start off empty', async () => {
|
||||||
|
const { status, body } = await request(app).get('/api-keys').set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(body).toEqual([]);
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of api keys', async () => {
|
||||||
|
const [{ apiKey: apiKey1 }, { apiKey: apiKey2 }, { apiKey: apiKey3 }] = await Promise.all([
|
||||||
|
create(admin.accessToken, [Permission.All]),
|
||||||
|
create(admin.accessToken, [Permission.All]),
|
||||||
|
create(admin.accessToken, [Permission.All]),
|
||||||
|
]);
|
||||||
|
const { status, body } = await request(app).get('/api-keys').set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(body).toHaveLength(3);
|
||||||
|
expect(body).toEqual(expect.arrayContaining([apiKey1, apiKey2, apiKey3]));
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api-keys/:id', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).get(`/api-keys/${uuidDto.notFound}`);
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization', async () => {
|
||||||
|
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/api-keys/${apiKey.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest('API Key not found'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require a valid uuid', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/api-keys/${uuidDto.invalid}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get api key details', async () => {
|
||||||
|
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/api-keys/${apiKey.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: 'api key',
|
||||||
|
permissions: [Permission.All],
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PUT /api-keys/:id', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).put(`/api-keys/${uuidDto.notFound}`).send({ name: 'new name' });
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization', async () => {
|
||||||
|
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/api-keys/${apiKey.id}`)
|
||||||
|
.send({ name: 'new name' })
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest('API Key not found'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require a valid uuid', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/api-keys/${uuidDto.invalid}`)
|
||||||
|
.send({ name: 'new name' })
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update api key details', async () => {
|
||||||
|
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/api-keys/${apiKey.id}`)
|
||||||
|
.send({ name: 'new name' })
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: 'new name',
|
||||||
|
permissions: [Permission.All],
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DELETE /api-keys/:id', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).delete(`/api-keys/${uuidDto.notFound}`);
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization', async () => {
|
||||||
|
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.delete(`/api-keys/${apiKey.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest('API Key not found'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require a valid uuid', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.delete(`/api-keys/${uuidDto.invalid}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete an api key', async () => {
|
||||||
|
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||||
|
const { status } = await request(app)
|
||||||
|
.delete(`/api-keys/${apiKey.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(204);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('authentication', () => {
|
||||||
|
it('should work as a header', async () => {
|
||||||
|
const { secret } = await create(user.accessToken, [Permission.All]);
|
||||||
|
const { status, body } = await request(app).get('/api-keys').set('x-api-key', secret);
|
||||||
|
expect(body).toHaveLength(1);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work as a query param', async () => {
|
||||||
|
const { secret } = await create(user.accessToken, [Permission.All]);
|
||||||
|
const { status, body } = await request(app).get(`/api-keys?apiKey=${secret}`);
|
||||||
|
expect(body).toHaveLength(1);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
SharedLinkType,
|
SharedLinkType,
|
||||||
getAssetInfo,
|
getAssetInfo,
|
||||||
getMyUser,
|
getMyUser,
|
||||||
updateAssets,
|
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { exiftool } from 'exiftool-vendored';
|
import { exiftool } from 'exiftool-vendored';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
@@ -43,6 +42,7 @@ const makeUploadDto = (options?: { omit: string }): Record<string, any> => {
|
|||||||
const TEN_TIMES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
const TEN_TIMES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||||
|
|
||||||
const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
|
const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
|
||||||
|
const ratingAssetFilepath = `${testAssetDir}/metadata/rating/mongolels.jpg`;
|
||||||
|
|
||||||
const readTags = async (bytes: Buffer, filename: string) => {
|
const readTags = async (bytes: Buffer, filename: string) => {
|
||||||
const filepath = join(tempDir, filename);
|
const filepath = join(tempDir, filename);
|
||||||
@@ -66,25 +66,23 @@ describe('/asset', () => {
|
|||||||
let timeBucketUser: LoginResponseDto;
|
let timeBucketUser: LoginResponseDto;
|
||||||
let quotaUser: LoginResponseDto;
|
let quotaUser: LoginResponseDto;
|
||||||
let statsUser: LoginResponseDto;
|
let statsUser: LoginResponseDto;
|
||||||
let stackUser: LoginResponseDto;
|
|
||||||
|
|
||||||
let user1Assets: AssetMediaResponseDto[];
|
let user1Assets: AssetMediaResponseDto[];
|
||||||
let user2Assets: AssetMediaResponseDto[];
|
let user2Assets: AssetMediaResponseDto[];
|
||||||
let stackAssets: AssetMediaResponseDto[];
|
|
||||||
let locationAsset: AssetMediaResponseDto;
|
let locationAsset: AssetMediaResponseDto;
|
||||||
|
let ratingAsset: AssetMediaResponseDto;
|
||||||
|
|
||||||
const setupTests = async () => {
|
const setupTests = async () => {
|
||||||
await utils.resetDatabase();
|
await utils.resetDatabase();
|
||||||
admin = await utils.adminSetup({ onboarding: false });
|
admin = await utils.adminSetup({ onboarding: false });
|
||||||
|
|
||||||
[websocket, user1, user2, statsUser, quotaUser, timeBucketUser, stackUser] = await Promise.all([
|
[websocket, user1, user2, statsUser, quotaUser, timeBucketUser] = await Promise.all([
|
||||||
utils.connectWebsocket(admin.accessToken),
|
utils.connectWebsocket(admin.accessToken),
|
||||||
utils.userSetup(admin.accessToken, createUserDto.create('1')),
|
utils.userSetup(admin.accessToken, createUserDto.create('1')),
|
||||||
utils.userSetup(admin.accessToken, createUserDto.create('2')),
|
utils.userSetup(admin.accessToken, createUserDto.create('2')),
|
||||||
utils.userSetup(admin.accessToken, createUserDto.create('stats')),
|
utils.userSetup(admin.accessToken, createUserDto.create('stats')),
|
||||||
utils.userSetup(admin.accessToken, createUserDto.userQuota),
|
utils.userSetup(admin.accessToken, createUserDto.userQuota),
|
||||||
utils.userSetup(admin.accessToken, createUserDto.create('time-bucket')),
|
utils.userSetup(admin.accessToken, createUserDto.create('time-bucket')),
|
||||||
utils.userSetup(admin.accessToken, createUserDto.create('stack')),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await utils.createPartner(user1.accessToken, user2.userId);
|
await utils.createPartner(user1.accessToken, user2.userId);
|
||||||
@@ -99,6 +97,16 @@ describe('/asset', () => {
|
|||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: locationAsset.id });
|
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: locationAsset.id });
|
||||||
|
|
||||||
|
// asset rating
|
||||||
|
ratingAsset = await utils.createAsset(admin.accessToken, {
|
||||||
|
assetData: {
|
||||||
|
filename: 'mongolels.jpg',
|
||||||
|
bytes: await readFile(ratingAssetFilepath),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: ratingAsset.id });
|
||||||
|
|
||||||
user1Assets = await Promise.all([
|
user1Assets = await Promise.all([
|
||||||
utils.createAsset(user1.accessToken),
|
utils.createAsset(user1.accessToken),
|
||||||
utils.createAsset(user1.accessToken),
|
utils.createAsset(user1.accessToken),
|
||||||
@@ -137,20 +145,6 @@ describe('/asset', () => {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// stacks
|
|
||||||
stackAssets = await Promise.all([
|
|
||||||
utils.createAsset(stackUser.accessToken),
|
|
||||||
utils.createAsset(stackUser.accessToken),
|
|
||||||
utils.createAsset(stackUser.accessToken),
|
|
||||||
utils.createAsset(stackUser.accessToken),
|
|
||||||
utils.createAsset(stackUser.accessToken),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await updateAssets(
|
|
||||||
{ assetBulkUpdateDto: { stackParentId: stackAssets[0].id, ids: [stackAssets[1].id, stackAssets[2].id] } },
|
|
||||||
{ headers: asBearerAuth(stackUser.accessToken) },
|
|
||||||
);
|
|
||||||
|
|
||||||
const person1 = await utils.createPerson(user1.accessToken, {
|
const person1 = await utils.createPerson(user1.accessToken, {
|
||||||
name: 'Test Person',
|
name: 'Test Person',
|
||||||
});
|
});
|
||||||
@@ -214,6 +208,22 @@ describe('/asset', () => {
|
|||||||
expect(body).toMatchObject({ id: user1Assets[0].id });
|
expect(body).toMatchObject({ id: user1Assets[0].id });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should get the asset rating', async () => {
|
||||||
|
await utils.waitForWebsocketEvent({
|
||||||
|
event: 'assetUpload',
|
||||||
|
id: ratingAsset.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/assets/${ratingAsset.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
id: ratingAsset.id,
|
||||||
|
exifInfo: expect.objectContaining({ rating: 3 }),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should work with a shared link', async () => {
|
it('should work with a shared link', async () => {
|
||||||
const sharedLink = await utils.createSharedLink(user1.accessToken, {
|
const sharedLink = await utils.createSharedLink(user1.accessToken, {
|
||||||
type: SharedLinkType.Individual,
|
type: SharedLinkType.Individual,
|
||||||
@@ -353,6 +363,8 @@ describe('/asset', () => {
|
|||||||
utils.createAsset(user1.accessToken),
|
utils.createAsset(user1.accessToken),
|
||||||
utils.createAsset(user1.accessToken),
|
utils.createAsset(user1.accessToken),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, 'thumbnailGeneration');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
@@ -389,17 +401,14 @@ describe('/asset', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each(TEN_TIMES)(
|
it.skip('should return 1 asset if there are 10 assets in the database but user 2 only has 1', async () => {
|
||||||
'should return 1 asset if there are 10 assets in the database but user 2 only has 1',
|
const { status, body } = await request(app)
|
||||||
async () => {
|
.get('/assets/random')
|
||||||
const { status, body } = await request(app)
|
.set('Authorization', `Bearer ${user2.accessToken}`);
|
||||||
.get('/assets/random')
|
|
||||||
.set('Authorization', `Bearer ${user2.accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual([expect.objectContaining({ id: user2Assets[0].id })]);
|
expect(body).toEqual([expect.objectContaining({ id: user2Assets[0].id })]);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
it('should return error', async () => {
|
it('should return error', async () => {
|
||||||
const { status } = await request(app)
|
const { status } = await request(app)
|
||||||
@@ -575,6 +584,31 @@ describe('/asset', () => {
|
|||||||
expect(status).toEqual(200);
|
expect(status).toEqual(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set the rating', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/assets/${user1Assets[0].id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ rating: 2 });
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
id: user1Assets[0].id,
|
||||||
|
exifInfo: expect.objectContaining({
|
||||||
|
rating: 2,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid rating', async () => {
|
||||||
|
for (const test of [{ rating: 7 }, { rating: 3.5 }, { rating: null }]) {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/assets/${user1Assets[0].id}`)
|
||||||
|
.send(test)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('should return tagged people', async () => {
|
it('should return tagged people', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.put(`/assets/${user1Assets[0].id}`)
|
.put(`/assets/${user1Assets[0].id}`)
|
||||||
@@ -773,145 +807,8 @@ describe('/asset', () => {
|
|||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require a valid parent id', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.put('/assets')
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
|
||||||
.send({ stackParentId: uuidDto.invalid, ids: [stackAssets[0].id] });
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorDto.badRequest(['stackParentId must be a UUID']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require access to the parent', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.put('/assets')
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
|
||||||
.send({ stackParentId: stackAssets[3].id, ids: [user1Assets[0].id] });
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorDto.noPermission);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add stack children', async () => {
|
|
||||||
const { status } = await request(app)
|
|
||||||
.put('/assets')
|
|
||||||
.set('Authorization', `Bearer ${stackUser.accessToken}`)
|
|
||||||
.send({ stackParentId: stackAssets[0].id, ids: [stackAssets[3].id] });
|
|
||||||
|
|
||||||
expect(status).toBe(204);
|
|
||||||
|
|
||||||
const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
|
|
||||||
expect(asset.stack).not.toBeUndefined();
|
|
||||||
expect(asset.stack).toEqual(expect.arrayContaining([expect.objectContaining({ id: stackAssets[3].id })]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove stack children', async () => {
|
|
||||||
const { status } = await request(app)
|
|
||||||
.put('/assets')
|
|
||||||
.set('Authorization', `Bearer ${stackUser.accessToken}`)
|
|
||||||
.send({ removeParent: true, ids: [stackAssets[1].id] });
|
|
||||||
|
|
||||||
expect(status).toBe(204);
|
|
||||||
|
|
||||||
const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
|
|
||||||
expect(asset.stack).not.toBeUndefined();
|
|
||||||
expect(asset.stack).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
expect.objectContaining({ id: stackAssets[2].id }),
|
|
||||||
expect.objectContaining({ id: stackAssets[3].id }),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove all stack children', async () => {
|
|
||||||
const { status } = await request(app)
|
|
||||||
.put('/assets')
|
|
||||||
.set('Authorization', `Bearer ${stackUser.accessToken}`)
|
|
||||||
.send({ removeParent: true, ids: [stackAssets[2].id, stackAssets[3].id] });
|
|
||||||
|
|
||||||
expect(status).toBe(204);
|
|
||||||
|
|
||||||
const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
|
|
||||||
expect(asset.stack).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should merge stack children', async () => {
|
|
||||||
// create stack after previous test removed stack children
|
|
||||||
await updateAssets(
|
|
||||||
{ assetBulkUpdateDto: { stackParentId: stackAssets[0].id, ids: [stackAssets[1].id, stackAssets[2].id] } },
|
|
||||||
{ headers: asBearerAuth(stackUser.accessToken) },
|
|
||||||
);
|
|
||||||
|
|
||||||
const { status } = await request(app)
|
|
||||||
.put('/assets')
|
|
||||||
.set('Authorization', `Bearer ${stackUser.accessToken}`)
|
|
||||||
.send({ stackParentId: stackAssets[3].id, ids: [stackAssets[0].id] });
|
|
||||||
|
|
||||||
expect(status).toBe(204);
|
|
||||||
|
|
||||||
const asset = await getAssetInfo({ id: stackAssets[3].id }, { headers: asBearerAuth(stackUser.accessToken) });
|
|
||||||
expect(asset.stack).not.toBeUndefined();
|
|
||||||
expect(asset.stack).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
expect.objectContaining({ id: stackAssets[0].id }),
|
|
||||||
expect.objectContaining({ id: stackAssets[1].id }),
|
|
||||||
expect.objectContaining({ id: stackAssets[2].id }),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('PUT /assets/stack/parent', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(app).put('/assets/stack/parent');
|
|
||||||
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require a valid id', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.put('/assets/stack/parent')
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
|
||||||
.send({ oldParentId: uuidDto.invalid, newParentId: uuidDto.invalid });
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorDto.badRequest());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require access', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.put('/assets/stack/parent')
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
|
||||||
.send({ oldParentId: stackAssets[3].id, newParentId: stackAssets[0].id });
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorDto.noPermission);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should make old parent child of new parent', async () => {
|
|
||||||
const { status } = await request(app)
|
|
||||||
.put('/assets/stack/parent')
|
|
||||||
.set('Authorization', `Bearer ${stackUser.accessToken}`)
|
|
||||||
.send({ oldParentId: stackAssets[3].id, newParentId: stackAssets[0].id });
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
|
|
||||||
const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
|
|
||||||
|
|
||||||
// new parent
|
|
||||||
expect(asset.stack).not.toBeUndefined();
|
|
||||||
expect(asset.stack).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
expect.objectContaining({ id: stackAssets[1].id }),
|
|
||||||
expect.objectContaining({ id: stackAssets[2].id }),
|
|
||||||
expect.objectContaining({ id: stackAssets[3].id }),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('POST /assets', () => {
|
describe('POST /assets', () => {
|
||||||
beforeAll(setupTests, 30_000);
|
beforeAll(setupTests, 30_000);
|
||||||
|
|
||||||
@@ -940,13 +837,12 @@ describe('/asset', () => {
|
|||||||
expect(body).toEqual(errorDto.badRequest());
|
expect(body).toEqual(errorDto.badRequest());
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
const tests = [
|
||||||
{
|
{
|
||||||
input: 'formats/avif/8bit-sRGB.avif',
|
input: 'formats/avif/8bit-sRGB.avif',
|
||||||
expected: {
|
expected: {
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
originalFileName: '8bit-sRGB.avif',
|
originalFileName: '8bit-sRGB.avif',
|
||||||
resized: true,
|
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
description: '',
|
description: '',
|
||||||
exifImageHeight: 1080,
|
exifImageHeight: 1080,
|
||||||
@@ -962,7 +858,6 @@ describe('/asset', () => {
|
|||||||
expected: {
|
expected: {
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
originalFileName: 'el_torcal_rocks.jpg',
|
originalFileName: 'el_torcal_rocks.jpg',
|
||||||
resized: true,
|
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
dateTimeOriginal: '2012-08-05T11:39:59.000Z',
|
dateTimeOriginal: '2012-08-05T11:39:59.000Z',
|
||||||
exifImageWidth: 512,
|
exifImageWidth: 512,
|
||||||
@@ -986,7 +881,6 @@ describe('/asset', () => {
|
|||||||
expected: {
|
expected: {
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
originalFileName: '8bit-sRGB.jxl',
|
originalFileName: '8bit-sRGB.jxl',
|
||||||
resized: true,
|
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
description: '',
|
description: '',
|
||||||
exifImageHeight: 1080,
|
exifImageHeight: 1080,
|
||||||
@@ -1002,7 +896,6 @@ describe('/asset', () => {
|
|||||||
expected: {
|
expected: {
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
originalFileName: 'IMG_2682.heic',
|
originalFileName: 'IMG_2682.heic',
|
||||||
resized: true,
|
|
||||||
fileCreatedAt: '2019-03-21T16:04:22.348Z',
|
fileCreatedAt: '2019-03-21T16:04:22.348Z',
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
dateTimeOriginal: '2019-03-21T16:04:22.348Z',
|
dateTimeOriginal: '2019-03-21T16:04:22.348Z',
|
||||||
@@ -1027,7 +920,6 @@ describe('/asset', () => {
|
|||||||
expected: {
|
expected: {
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
originalFileName: 'density_plot.png',
|
originalFileName: 'density_plot.png',
|
||||||
resized: true,
|
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
exifImageWidth: 800,
|
exifImageWidth: 800,
|
||||||
exifImageHeight: 800,
|
exifImageHeight: 800,
|
||||||
@@ -1042,7 +934,6 @@ describe('/asset', () => {
|
|||||||
expected: {
|
expected: {
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
originalFileName: 'glarus.nef',
|
originalFileName: 'glarus.nef',
|
||||||
resized: true,
|
|
||||||
fileCreatedAt: '2010-07-20T17:27:12.000Z',
|
fileCreatedAt: '2010-07-20T17:27:12.000Z',
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
make: 'NIKON CORPORATION',
|
make: 'NIKON CORPORATION',
|
||||||
@@ -1064,7 +955,6 @@ describe('/asset', () => {
|
|||||||
expected: {
|
expected: {
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
originalFileName: 'philadelphia.nef',
|
originalFileName: 'philadelphia.nef',
|
||||||
resized: true,
|
|
||||||
fileCreatedAt: '2016-09-22T22:10:29.060Z',
|
fileCreatedAt: '2016-09-22T22:10:29.060Z',
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
make: 'NIKON CORPORATION',
|
make: 'NIKON CORPORATION',
|
||||||
@@ -1087,7 +977,6 @@ describe('/asset', () => {
|
|||||||
expected: {
|
expected: {
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
originalFileName: '4_3.rw2',
|
originalFileName: '4_3.rw2',
|
||||||
resized: true,
|
|
||||||
fileCreatedAt: '2018-05-10T08:42:37.842Z',
|
fileCreatedAt: '2018-05-10T08:42:37.842Z',
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
make: 'Panasonic',
|
make: 'Panasonic',
|
||||||
@@ -1111,7 +1000,6 @@ describe('/asset', () => {
|
|||||||
expected: {
|
expected: {
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
originalFileName: '12bit-compressed-(3_2).arw',
|
originalFileName: '12bit-compressed-(3_2).arw',
|
||||||
resized: true,
|
|
||||||
fileCreatedAt: '2016-09-27T10:51:44.000Z',
|
fileCreatedAt: '2016-09-27T10:51:44.000Z',
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
make: 'SONY',
|
make: 'SONY',
|
||||||
@@ -1136,7 +1024,6 @@ describe('/asset', () => {
|
|||||||
expected: {
|
expected: {
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
originalFileName: '14bit-uncompressed-(3_2).arw',
|
originalFileName: '14bit-uncompressed-(3_2).arw',
|
||||||
resized: true,
|
|
||||||
fileCreatedAt: '2016-01-08T14:08:01.000Z',
|
fileCreatedAt: '2016-01-08T14:08:01.000Z',
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
make: 'SONY',
|
make: 'SONY',
|
||||||
@@ -1156,21 +1043,32 @@ describe('/asset', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])(`should upload and generate a thumbnail for $input`, async ({ input, expected }) => {
|
];
|
||||||
const filepath = join(testAssetDir, input);
|
|
||||||
const { id, status } = await utils.createAsset(admin.accessToken, {
|
|
||||||
assetData: { bytes: await readFile(filepath), filename: basename(filepath) },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(status).toBe(AssetMediaStatus.Created);
|
it(`should upload and generate a thumbnail for different file types`, async () => {
|
||||||
|
// upload in parallel
|
||||||
|
const assets = await Promise.all(
|
||||||
|
tests.map(async ({ input }) => {
|
||||||
|
const filepath = join(testAssetDir, input);
|
||||||
|
return utils.createAsset(admin.accessToken, {
|
||||||
|
assetData: { bytes: await readFile(filepath), filename: basename(filepath) },
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: id });
|
for (const { id, status } of assets) {
|
||||||
|
expect(status).toBe(AssetMediaStatus.Created);
|
||||||
|
await utils.waitForWebsocketEvent({ event: 'assetUpload', id });
|
||||||
|
}
|
||||||
|
|
||||||
const asset = await utils.getAssetInfo(admin.accessToken, id);
|
for (const [i, { id }] of assets.entries()) {
|
||||||
|
const { expected } = tests[i];
|
||||||
|
const asset = await utils.getAssetInfo(admin.accessToken, id);
|
||||||
|
|
||||||
expect(asset.exifInfo).toBeDefined();
|
expect(asset.exifInfo).toBeDefined();
|
||||||
expect(asset.exifInfo).toMatchObject(expected.exifInfo);
|
expect(asset.exifInfo).toMatchObject(expected.exifInfo);
|
||||||
expect(asset).toMatchObject(expected);
|
expect(asset).toMatchObject(expected);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle a duplicate', async () => {
|
it('should handle a duplicate', async () => {
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ describe('/libraries', () => {
|
|||||||
|
|
||||||
expect(assets.count).toBe(2);
|
expect(assets.count).toBe(2);
|
||||||
|
|
||||||
utils.createImageFile(`${testAssetDir}/temp/directoryA/assetB.png`);
|
utils.createImageFile(`${testAssetDir}/temp/directoryA/assetC.png`);
|
||||||
|
|
||||||
await scan(admin.accessToken, library.id);
|
await scan(admin.accessToken, library.id);
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', total: 3 });
|
await utils.waitForWebsocketEvent({ event: 'assetUpload', total: 3 });
|
||||||
@@ -361,11 +361,11 @@ describe('/libraries', () => {
|
|||||||
const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
||||||
|
|
||||||
expect(newAssets.count).toBe(3);
|
expect(newAssets.count).toBe(3);
|
||||||
utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetB.png`);
|
utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetC.png`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should offline missing files', async () => {
|
it('should offline a file missing from disk', async () => {
|
||||||
utils.createImageFile(`${testAssetDir}/temp/directoryA/assetB.png`);
|
utils.createImageFile(`${testAssetDir}/temp/directoryA/assetC.png`);
|
||||||
const library = await utils.createLibrary(admin.accessToken, {
|
const library = await utils.createLibrary(admin.accessToken, {
|
||||||
ownerId: admin.userId,
|
ownerId: admin.userId,
|
||||||
importPaths: [`${testAssetDirInternal}/temp`],
|
importPaths: [`${testAssetDirInternal}/temp`],
|
||||||
@@ -374,7 +374,40 @@ describe('/libraries', () => {
|
|||||||
await scan(admin.accessToken, library.id);
|
await scan(admin.accessToken, library.id);
|
||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
|
||||||
utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetB.png`);
|
const { assets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
||||||
|
expect(assets.count).toBe(3);
|
||||||
|
|
||||||
|
utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetC.png`);
|
||||||
|
|
||||||
|
await scan(admin.accessToken, library.id);
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
|
||||||
|
const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
||||||
|
expect(newAssets.count).toBe(3);
|
||||||
|
|
||||||
|
expect(newAssets.items).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
isOffline: true,
|
||||||
|
originalFileName: 'assetC.png',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should offline a file outside of import paths', async () => {
|
||||||
|
const library = await utils.createLibrary(admin.accessToken, {
|
||||||
|
ownerId: admin.userId,
|
||||||
|
importPaths: [`${testAssetDirInternal}/temp`],
|
||||||
|
});
|
||||||
|
|
||||||
|
await scan(admin.accessToken, library.id);
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.put(`/libraries/${library.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ importPaths: [`${testAssetDirInternal}/temp/directoryA`] });
|
||||||
|
|
||||||
await scan(admin.accessToken, library.id);
|
await scan(admin.accessToken, library.id);
|
||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
@@ -383,6 +416,45 @@ describe('/libraries', () => {
|
|||||||
|
|
||||||
expect(assets.items).toEqual(
|
expect(assets.items).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
isOffline: false,
|
||||||
|
originalFileName: 'assetA.png',
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
isOffline: true,
|
||||||
|
originalFileName: 'assetB.png',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should offline a file covered by an exclusion pattern', async () => {
|
||||||
|
const library = await utils.createLibrary(admin.accessToken, {
|
||||||
|
ownerId: admin.userId,
|
||||||
|
importPaths: [`${testAssetDirInternal}/temp`],
|
||||||
|
});
|
||||||
|
|
||||||
|
await scan(admin.accessToken, library.id);
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.put(`/libraries/${library.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ exclusionPatterns: ['**/directoryB/**'] });
|
||||||
|
|
||||||
|
await scan(admin.accessToken, library.id);
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
|
||||||
|
const { assets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
||||||
|
|
||||||
|
expect(assets.count).toBe(2);
|
||||||
|
|
||||||
|
expect(assets.items).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
isOffline: false,
|
||||||
|
originalFileName: 'assetA.png',
|
||||||
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
isOffline: true,
|
isOffline: true,
|
||||||
originalFileName: 'assetB.png',
|
originalFileName: 'assetB.png',
|
||||||
@@ -434,6 +506,8 @@ describe('/libraries', () => {
|
|||||||
await utils.waitForWebsocketEvent({ event: 'assetDelete', total: 1 });
|
await utils.waitForWebsocketEvent({ event: 'assetDelete', total: 1 });
|
||||||
|
|
||||||
expect(existsSync(`${testAssetDir}/temp/offline1/assetA.png`)).toBe(true);
|
expect(existsSync(`${testAssetDir}/temp/offline1/assetA.png`)).toBe(true);
|
||||||
|
|
||||||
|
utils.removeImageFile(`${testAssetDir}/temp/offline1/assetA.png`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should scan new files', async () => {
|
it('should scan new files', async () => {
|
||||||
@@ -445,14 +519,14 @@ describe('/libraries', () => {
|
|||||||
await scan(admin.accessToken, library.id);
|
await scan(admin.accessToken, library.id);
|
||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
|
||||||
utils.createImageFile(`${testAssetDir}/temp/directoryA/assetC.png`);
|
utils.createImageFile(`${testAssetDir}/temp/directoryC/assetC.png`);
|
||||||
|
|
||||||
await scan(admin.accessToken, library.id);
|
await scan(admin.accessToken, library.id);
|
||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
|
||||||
utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetC.png`);
|
|
||||||
const { assets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
const { assets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
||||||
|
|
||||||
|
expect(assets.count).toBe(3);
|
||||||
expect(assets.items).toEqual(
|
expect(assets.items).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@@ -460,6 +534,8 @@ describe('/libraries', () => {
|
|||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
utils.removeImageFile(`${testAssetDir}/temp/directoryC/assetC.png`);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with refreshModifiedFiles=true', () => {
|
describe('with refreshModifiedFiles=true', () => {
|
||||||
|
|||||||
@@ -92,14 +92,14 @@ describe(`/oauth`, () => {
|
|||||||
it('should return a redirect uri', async () => {
|
it('should return a redirect uri', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.post('/oauth/authorize')
|
.post('/oauth/authorize')
|
||||||
.send({ redirectUri: 'http://127.0.0.1:2283/auth/login' });
|
.send({ redirectUri: 'http://127.0.0.1:2285/auth/login' });
|
||||||
expect(status).toBe(201);
|
expect(status).toBe(201);
|
||||||
expect(body).toEqual({ url: expect.stringContaining(`${authServer.internal}/auth?`) });
|
expect(body).toEqual({ url: expect.stringContaining(`${authServer.internal}/auth?`) });
|
||||||
|
|
||||||
const params = new URL(body.url).searchParams;
|
const params = new URL(body.url).searchParams;
|
||||||
expect(params.get('client_id')).toBe('client-default');
|
expect(params.get('client_id')).toBe('client-default');
|
||||||
expect(params.get('response_type')).toBe('code');
|
expect(params.get('response_type')).toBe('code');
|
||||||
expect(params.get('redirect_uri')).toBe('http://127.0.0.1:2283/auth/login');
|
expect(params.get('redirect_uri')).toBe('http://127.0.0.1:2285/auth/login');
|
||||||
expect(params.get('state')).toBeDefined();
|
expect(params.get('state')).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
211
e2e/src/api/specs/stack.e2e-spec.ts
Normal file
211
e2e/src/api/specs/stack.e2e-spec.ts
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
import { AssetMediaResponseDto, LoginResponseDto, searchStacks } from '@immich/sdk';
|
||||||
|
import { createUserDto, uuidDto } from 'src/fixtures';
|
||||||
|
import { errorDto } from 'src/responses';
|
||||||
|
import { app, asBearerAuth, utils } from 'src/utils';
|
||||||
|
import request from 'supertest';
|
||||||
|
import { beforeAll, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
describe('/stacks', () => {
|
||||||
|
let admin: LoginResponseDto;
|
||||||
|
let user1: LoginResponseDto;
|
||||||
|
let user2: LoginResponseDto;
|
||||||
|
let asset: AssetMediaResponseDto;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await utils.resetDatabase();
|
||||||
|
|
||||||
|
admin = await utils.adminSetup();
|
||||||
|
|
||||||
|
[user1, user2] = await Promise.all([
|
||||||
|
utils.userSetup(admin.accessToken, createUserDto.user1),
|
||||||
|
utils.userSetup(admin.accessToken, createUserDto.user2),
|
||||||
|
]);
|
||||||
|
|
||||||
|
asset = await utils.createAsset(user1.accessToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /stacks', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/stacks')
|
||||||
|
.send({ assetIds: [asset.id] });
|
||||||
|
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require at least two assets', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/stacks')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ assetIds: [asset.id] });
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require a valid id', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/stacks')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ assetIds: [uuidDto.invalid, uuidDto.invalid] });
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require access', async () => {
|
||||||
|
const user2Asset = await utils.createAsset(user2.accessToken);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/stacks')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ assetIds: [asset.id, user2Asset.id] });
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.noPermission);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a stack', async () => {
|
||||||
|
const [asset1, asset2] = await Promise.all([
|
||||||
|
utils.createAsset(user1.accessToken),
|
||||||
|
utils.createAsset(user1.accessToken),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/stacks')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ assetIds: [asset1.id, asset2.id] });
|
||||||
|
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
primaryAssetId: asset1.id,
|
||||||
|
assets: [expect.objectContaining({ id: asset1.id }), expect.objectContaining({ id: asset2.id })],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge an existing stack', async () => {
|
||||||
|
const [asset1, asset2, asset3] = await Promise.all([
|
||||||
|
utils.createAsset(user1.accessToken),
|
||||||
|
utils.createAsset(user1.accessToken),
|
||||||
|
utils.createAsset(user1.accessToken),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const response1 = await request(app)
|
||||||
|
.post('/stacks')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ assetIds: [asset1.id, asset2.id] });
|
||||||
|
|
||||||
|
expect(response1.status).toBe(201);
|
||||||
|
|
||||||
|
const stacksBefore = await searchStacks({}, { headers: asBearerAuth(user1.accessToken) });
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/stacks')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ assetIds: [asset1.id, asset3.id] });
|
||||||
|
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
primaryAssetId: asset1.id,
|
||||||
|
assets: expect.arrayContaining([
|
||||||
|
expect.objectContaining({ id: asset1.id }),
|
||||||
|
expect.objectContaining({ id: asset2.id }),
|
||||||
|
expect.objectContaining({ id: asset3.id }),
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
|
||||||
|
const stacksAfter = await searchStacks({}, { headers: asBearerAuth(user1.accessToken) });
|
||||||
|
expect(stacksAfter.length).toBe(stacksBefore.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
// it('should require a valid parent id', async () => {
|
||||||
|
// const { status, body } = await request(app)
|
||||||
|
// .put('/assets')
|
||||||
|
// .set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
// .send({ stackParentId: uuidDto.invalid, ids: [stackAssets[0].id] });
|
||||||
|
|
||||||
|
// expect(status).toBe(400);
|
||||||
|
// expect(body).toEqual(errorDto.badRequest(['stackParentId must be a UUID']));
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
|
||||||
|
// it('should require access to the parent', async () => {
|
||||||
|
// const { status, body } = await request(app)
|
||||||
|
// .put('/assets')
|
||||||
|
// .set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
// .send({ stackParentId: stackAssets[3].id, ids: [user1Assets[0].id] });
|
||||||
|
|
||||||
|
// expect(status).toBe(400);
|
||||||
|
// expect(body).toEqual(errorDto.noPermission);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('should add stack children', async () => {
|
||||||
|
// const { status } = await request(app)
|
||||||
|
// .put('/assets')
|
||||||
|
// .set('Authorization', `Bearer ${stackUser.accessToken}`)
|
||||||
|
// .send({ stackParentId: stackAssets[0].id, ids: [stackAssets[3].id] });
|
||||||
|
|
||||||
|
// expect(status).toBe(204);
|
||||||
|
|
||||||
|
// const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
|
||||||
|
// expect(asset.stack).not.toBeUndefined();
|
||||||
|
// expect(asset.stack).toEqual(expect.arrayContaining([expect.objectContaining({ id: stackAssets[3].id })]));
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('should remove stack children', async () => {
|
||||||
|
// const { status } = await request(app)
|
||||||
|
// .put('/assets')
|
||||||
|
// .set('Authorization', `Bearer ${stackUser.accessToken}`)
|
||||||
|
// .send({ removeParent: true, ids: [stackAssets[1].id] });
|
||||||
|
|
||||||
|
// expect(status).toBe(204);
|
||||||
|
|
||||||
|
// const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
|
||||||
|
// expect(asset.stack).not.toBeUndefined();
|
||||||
|
// expect(asset.stack).toEqual(
|
||||||
|
// expect.arrayContaining([
|
||||||
|
// expect.objectContaining({ id: stackAssets[2].id }),
|
||||||
|
// expect.objectContaining({ id: stackAssets[3].id }),
|
||||||
|
// ]),
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('should remove all stack children', async () => {
|
||||||
|
// const { status } = await request(app)
|
||||||
|
// .put('/assets')
|
||||||
|
// .set('Authorization', `Bearer ${stackUser.accessToken}`)
|
||||||
|
// .send({ removeParent: true, ids: [stackAssets[2].id, stackAssets[3].id] });
|
||||||
|
|
||||||
|
// expect(status).toBe(204);
|
||||||
|
|
||||||
|
// const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) });
|
||||||
|
// expect(asset.stack).toBeUndefined();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('should merge stack children', async () => {
|
||||||
|
// // create stack after previous test removed stack children
|
||||||
|
// await updateAssets(
|
||||||
|
// { assetBulkUpdateDto: { stackParentId: stackAssets[0].id, ids: [stackAssets[1].id, stackAssets[2].id] } },
|
||||||
|
// { headers: asBearerAuth(stackUser.accessToken) },
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const { status } = await request(app)
|
||||||
|
// .put('/assets')
|
||||||
|
// .set('Authorization', `Bearer ${stackUser.accessToken}`)
|
||||||
|
// .send({ stackParentId: stackAssets[3].id, ids: [stackAssets[0].id] });
|
||||||
|
|
||||||
|
// expect(status).toBe(204);
|
||||||
|
|
||||||
|
// const asset = await getAssetInfo({ id: stackAssets[3].id }, { headers: asBearerAuth(stackUser.accessToken) });
|
||||||
|
// expect(asset.stack).not.toBeUndefined();
|
||||||
|
// expect(asset.stack).toEqual(
|
||||||
|
// expect.arrayContaining([
|
||||||
|
// expect.objectContaining({ id: stackAssets[0].id }),
|
||||||
|
// expect.objectContaining({ id: stackAssets[1].id }),
|
||||||
|
// expect.objectContaining({ id: stackAssets[2].id }),
|
||||||
|
// ]),
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
});
|
||||||
603
e2e/src/api/specs/tag.e2e-spec.ts
Normal file
603
e2e/src/api/specs/tag.e2e-spec.ts
Normal file
@@ -0,0 +1,603 @@
|
|||||||
|
import {
|
||||||
|
AssetMediaResponseDto,
|
||||||
|
LoginResponseDto,
|
||||||
|
Permission,
|
||||||
|
TagCreateDto,
|
||||||
|
TagResponseDto,
|
||||||
|
createTag,
|
||||||
|
getAllTags,
|
||||||
|
tagAssets,
|
||||||
|
upsertTags,
|
||||||
|
} from '@immich/sdk';
|
||||||
|
import { createUserDto, uuidDto } from 'src/fixtures';
|
||||||
|
import { errorDto } from 'src/responses';
|
||||||
|
import { app, asBearerAuth, utils } from 'src/utils';
|
||||||
|
import request from 'supertest';
|
||||||
|
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
const create = (accessToken: string, dto: TagCreateDto) =>
|
||||||
|
createTag({ tagCreateDto: dto }, { headers: asBearerAuth(accessToken) });
|
||||||
|
|
||||||
|
const upsert = (accessToken: string, tags: string[]) =>
|
||||||
|
upsertTags({ tagUpsertDto: { tags } }, { headers: asBearerAuth(accessToken) });
|
||||||
|
|
||||||
|
describe('/tags', () => {
|
||||||
|
let admin: LoginResponseDto;
|
||||||
|
let user: LoginResponseDto;
|
||||||
|
let userAsset: AssetMediaResponseDto;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await utils.resetDatabase();
|
||||||
|
|
||||||
|
admin = await utils.adminSetup();
|
||||||
|
user = await utils.userSetup(admin.accessToken, createUserDto.user1);
|
||||||
|
userAsset = await utils.createAsset(user.accessToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// tagging assets eventually triggers metadata extraction which can impact other tests
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
|
||||||
|
await utils.resetDatabase(['tags']);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /tags', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).post('/tags').send({ name: 'TagA' });
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization (api key)', async () => {
|
||||||
|
const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]);
|
||||||
|
const { status, body } = await request(app).post('/tags').set('x-api-key', secret).send({ name: 'TagA' });
|
||||||
|
expect(status).toBe(403);
|
||||||
|
expect(body).toEqual(errorDto.missingPermission('tag.create'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with tag.create', async () => {
|
||||||
|
const { secret } = await utils.createApiKey(user.accessToken, [Permission.TagCreate]);
|
||||||
|
const { status, body } = await request(app).post('/tags').set('x-api-key', secret).send({ name: 'TagA' });
|
||||||
|
expect(body).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: 'TagA',
|
||||||
|
value: 'TagA',
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String),
|
||||||
|
});
|
||||||
|
expect(status).toBe(201);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a tag', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/tags')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ name: 'TagA' });
|
||||||
|
expect(body).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: 'TagA',
|
||||||
|
value: 'TagA',
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String),
|
||||||
|
});
|
||||||
|
expect(status).toBe(201);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow multiple users to create tags with the same value', async () => {
|
||||||
|
await create(admin.accessToken, { name: 'TagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/tags')
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`)
|
||||||
|
.send({ name: 'TagA' });
|
||||||
|
expect(body).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: 'TagA',
|
||||||
|
value: 'TagA',
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String),
|
||||||
|
});
|
||||||
|
expect(status).toBe(201);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a nested tag', async () => {
|
||||||
|
const parent = await create(admin.accessToken, { name: 'TagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/tags')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ name: 'TagB', parentId: parent.id });
|
||||||
|
expect(body).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
parentId: parent.id,
|
||||||
|
name: 'TagB',
|
||||||
|
value: 'TagA/TagB',
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String),
|
||||||
|
});
|
||||||
|
expect(status).toBe(201);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /tags', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).get('/tags');
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization (api key)', async () => {
|
||||||
|
const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]);
|
||||||
|
const { status, body } = await request(app).get('/tags').set('x-api-key', secret);
|
||||||
|
expect(status).toBe(403);
|
||||||
|
expect(body).toEqual(errorDto.missingPermission('tag.read'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should start off empty', async () => {
|
||||||
|
const { status, body } = await request(app).get('/tags').set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(body).toEqual([]);
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of tags', async () => {
|
||||||
|
const [tagA, tagB, tagC] = await Promise.all([
|
||||||
|
create(admin.accessToken, { name: 'TagA' }),
|
||||||
|
create(admin.accessToken, { name: 'TagB' }),
|
||||||
|
create(admin.accessToken, { name: 'TagC' }),
|
||||||
|
]);
|
||||||
|
const { status, body } = await request(app).get('/tags').set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(body).toHaveLength(3);
|
||||||
|
expect(body).toEqual([tagA, tagB, tagC]);
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a nested tags', async () => {
|
||||||
|
await upsert(admin.accessToken, ['TagA/TagB/TagC', 'TagD']);
|
||||||
|
const { status, body } = await request(app).get('/tags').set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
expect(body).toHaveLength(4);
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
|
||||||
|
const tags = body as TagResponseDto[];
|
||||||
|
const tagA = tags.find((tag) => tag.value === 'TagA') as TagResponseDto;
|
||||||
|
const tagB = tags.find((tag) => tag.value === 'TagA/TagB') as TagResponseDto;
|
||||||
|
const tagC = tags.find((tag) => tag.value === 'TagA/TagB/TagC') as TagResponseDto;
|
||||||
|
const tagD = tags.find((tag) => tag.value === 'TagD') as TagResponseDto;
|
||||||
|
|
||||||
|
expect(tagA).toEqual(expect.objectContaining({ name: 'TagA', value: 'TagA' }));
|
||||||
|
expect(tagB).toEqual(expect.objectContaining({ name: 'TagB', value: 'TagA/TagB', parentId: tagA.id }));
|
||||||
|
expect(tagC).toEqual(expect.objectContaining({ name: 'TagC', value: 'TagA/TagB/TagC', parentId: tagB.id }));
|
||||||
|
expect(tagD).toEqual(expect.objectContaining({ name: 'TagD', value: 'TagD' }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PUT /tags', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).put(`/tags`).send({ name: 'TagA/TagB' });
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization (api key)', async () => {
|
||||||
|
const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]);
|
||||||
|
const { status, body } = await request(app).put('/tags').set('x-api-key', secret).send({ name: 'TagA' });
|
||||||
|
expect(status).toBe(403);
|
||||||
|
expect(body).toEqual(errorDto.missingPermission('tag.create'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should upsert tags', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags`)
|
||||||
|
.send({ tags: ['TagA/TagB/TagC/TagD'] })
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual([expect.objectContaining({ name: 'TagD', value: 'TagA/TagB/TagC/TagD' })]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should upsert tags in parallel without conflicts', async () => {
|
||||||
|
const [[tag1], [tag2], [tag3], [tag4]] = await Promise.all([
|
||||||
|
upsert(admin.accessToken, ['TagA/TagB/TagC/TagD']),
|
||||||
|
upsert(admin.accessToken, ['TagA/TagB/TagC/TagD']),
|
||||||
|
upsert(admin.accessToken, ['TagA/TagB/TagC/TagD']),
|
||||||
|
upsert(admin.accessToken, ['TagA/TagB/TagC/TagD']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { id, parentId, createdAt } = tag1;
|
||||||
|
for (const tag of [tag1, tag2, tag3, tag4]) {
|
||||||
|
expect(tag).toMatchObject({
|
||||||
|
id,
|
||||||
|
parentId,
|
||||||
|
createdAt,
|
||||||
|
name: 'TagD',
|
||||||
|
value: 'TagA/TagB/TagC/TagD',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PUT /tags/assets', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).put(`/tags/assets`).send({ tagIds: [], assetIds: [] });
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization (api key)', async () => {
|
||||||
|
const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put('/tags/assets')
|
||||||
|
.set('x-api-key', secret)
|
||||||
|
.send({ assetIds: [], tagIds: [] });
|
||||||
|
expect(status).toBe(403);
|
||||||
|
expect(body).toEqual(errorDto.missingPermission('tag.asset'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip assets that are not owned by the user', async () => {
|
||||||
|
const [tagA, tagB, tagC, assetA, assetB] = await Promise.all([
|
||||||
|
create(user.accessToken, { name: 'TagA' }),
|
||||||
|
create(user.accessToken, { name: 'TagB' }),
|
||||||
|
create(user.accessToken, { name: 'TagC' }),
|
||||||
|
utils.createAsset(user.accessToken),
|
||||||
|
utils.createAsset(admin.accessToken),
|
||||||
|
]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags/assets`)
|
||||||
|
.send({ tagIds: [tagA.id, tagB.id, tagC.id], assetIds: [assetA.id, assetB.id] })
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({ count: 3 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip tags that are not owned by the user', async () => {
|
||||||
|
const [tagA, tagB, tagC, assetA, assetB] = await Promise.all([
|
||||||
|
create(user.accessToken, { name: 'TagA' }),
|
||||||
|
create(user.accessToken, { name: 'TagB' }),
|
||||||
|
create(admin.accessToken, { name: 'TagC' }),
|
||||||
|
utils.createAsset(user.accessToken),
|
||||||
|
utils.createAsset(user.accessToken),
|
||||||
|
]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags/assets`)
|
||||||
|
.send({ tagIds: [tagA.id, tagB.id, tagC.id], assetIds: [assetA.id, assetB.id] })
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({ count: 4 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bulk tag assets', async () => {
|
||||||
|
const [tagA, tagB, tagC, assetA, assetB] = await Promise.all([
|
||||||
|
create(user.accessToken, { name: 'TagA' }),
|
||||||
|
create(user.accessToken, { name: 'TagB' }),
|
||||||
|
create(user.accessToken, { name: 'TagC' }),
|
||||||
|
utils.createAsset(user.accessToken),
|
||||||
|
utils.createAsset(user.accessToken),
|
||||||
|
]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags/assets`)
|
||||||
|
.send({ tagIds: [tagA.id, tagB.id, tagC.id], assetIds: [assetA.id, assetB.id] })
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({ count: 6 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /tags/:id', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).get(`/tags/${uuidDto.notFound}`);
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization', async () => {
|
||||||
|
const tag = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/tags/${tag.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.noPermission);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization (api key)', async () => {
|
||||||
|
const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/tags/${uuidDto.notFound}`)
|
||||||
|
.set('x-api-key', secret)
|
||||||
|
.send({ assetIds: [], tagIds: [] });
|
||||||
|
expect(status).toBe(403);
|
||||||
|
expect(body).toEqual(errorDto.missingPermission('tag.read'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require a valid uuid', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/tags/${uuidDto.invalid}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get tag details', async () => {
|
||||||
|
const tag = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/tags/${tag.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: 'TagA',
|
||||||
|
value: 'TagA',
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get nested tag details', async () => {
|
||||||
|
const tagA = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const tagB = await create(user.accessToken, { name: 'TagB', parentId: tagA.id });
|
||||||
|
const tagC = await create(user.accessToken, { name: 'TagC', parentId: tagB.id });
|
||||||
|
const tagD = await create(user.accessToken, { name: 'TagD', parentId: tagC.id });
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/tags/${tagD.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
parentId: tagC.id,
|
||||||
|
name: 'TagD',
|
||||||
|
value: 'TagA/TagB/TagC/TagD',
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PUT /tags/:id', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const tag = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const { status, body } = await request(app).put(`/tags/${tag.id}`).send({ color: '#000000' });
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization', async () => {
|
||||||
|
const tag = await create(admin.accessToken, { name: 'tagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags/${tag.id}`)
|
||||||
|
.send({ color: '#000000' })
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.noPermission);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization (api key)', async () => {
|
||||||
|
const tag = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags/${tag.id}`)
|
||||||
|
.set('x-api-key', secret)
|
||||||
|
.send({ color: '#000000' });
|
||||||
|
expect(status).toBe(403);
|
||||||
|
expect(body).toEqual(errorDto.missingPermission('tag.update'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update a tag', async () => {
|
||||||
|
const tag = await create(user.accessToken, { name: 'tagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags/${tag.id}`)
|
||||||
|
.send({ color: '#000000' })
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(expect.objectContaining({ color: `#000000` }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update a tag color without a # prefix', async () => {
|
||||||
|
const tag = await create(user.accessToken, { name: 'tagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags/${tag.id}`)
|
||||||
|
.send({ color: '000000' })
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(expect.objectContaining({ color: `#000000` }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DELETE /tags/:id', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).delete(`/tags/${uuidDto.notFound}`);
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization', async () => {
|
||||||
|
const tag = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.delete(`/tags/${tag.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.noPermission);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization (api key)', async () => {
|
||||||
|
const tag = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]);
|
||||||
|
const { status, body } = await request(app).delete(`/tags/${tag.id}`).set('x-api-key', secret);
|
||||||
|
expect(status).toBe(403);
|
||||||
|
expect(body).toEqual(errorDto.missingPermission('tag.delete'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require a valid uuid', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.delete(`/tags/${uuidDto.invalid}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete a tag', async () => {
|
||||||
|
const tag = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const { status } = await request(app)
|
||||||
|
.delete(`/tags/${tag.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(204);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete a nested tag (root)', async () => {
|
||||||
|
const tagA = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
await create(user.accessToken, { name: 'TagB', parentId: tagA.id });
|
||||||
|
const { status } = await request(app)
|
||||||
|
.delete(`/tags/${tagA.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(204);
|
||||||
|
const tags = await getAllTags({ headers: asBearerAuth(user.accessToken) });
|
||||||
|
expect(tags.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete a nested tag (leaf)', async () => {
|
||||||
|
const tagA = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const tagB = await create(user.accessToken, { name: 'TagB', parentId: tagA.id });
|
||||||
|
const { status } = await request(app)
|
||||||
|
.delete(`/tags/${tagB.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(204);
|
||||||
|
const tags = await getAllTags({ headers: asBearerAuth(user.accessToken) });
|
||||||
|
expect(tags.length).toBe(1);
|
||||||
|
expect(tags[0]).toEqual(tagA);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PUT /tags/:id/assets', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const tagA = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags/${tagA.id}/assets`)
|
||||||
|
.send({ ids: [userAsset.id] });
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization', async () => {
|
||||||
|
const tag = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags/${tag.id}/assets`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ ids: [userAsset.id] });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.noPermission);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization (api key)', async () => {
|
||||||
|
const tag = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags/${tag.id}/assets`)
|
||||||
|
.set('x-api-key', secret)
|
||||||
|
.send({ ids: [userAsset.id] });
|
||||||
|
expect(status).toBe(403);
|
||||||
|
expect(body).toEqual(errorDto.missingPermission('tag.asset'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to tag own asset', async () => {
|
||||||
|
const tagA = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags/${tagA.id}/assets`)
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`)
|
||||||
|
.send({ ids: [userAsset.id] });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual([expect.objectContaining({ id: userAsset.id, success: true })]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not be able to add assets to another user's tag", async () => {
|
||||||
|
const tagA = await create(admin.accessToken, { name: 'TagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags/${tagA.id}/assets`)
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`)
|
||||||
|
.send({ ids: [userAsset.id] });
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest('Not found or no tag.asset access'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add duplicate assets only once', async () => {
|
||||||
|
const tagA = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/tags/${tagA.id}/assets`)
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`)
|
||||||
|
.send({ ids: [userAsset.id, userAsset.id] });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual([
|
||||||
|
expect.objectContaining({ id: userAsset.id, success: true }),
|
||||||
|
expect.objectContaining({ id: userAsset.id, success: false, error: 'duplicate' }),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DELETE /tags/:id/assets', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const tagA = await create(admin.accessToken, { name: 'TagA' });
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.delete(`/tags/${tagA}/assets`)
|
||||||
|
.send({ ids: [userAsset.id] });
|
||||||
|
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization', async () => {
|
||||||
|
const tagA = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
await tagAssets(
|
||||||
|
{ id: tagA.id, bulkIdsDto: { ids: [userAsset.id] } },
|
||||||
|
{ headers: asBearerAuth(user.accessToken) },
|
||||||
|
);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.delete(`/tags/${tagA.id}/assets`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ ids: [userAsset.id] });
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.noPermission);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authorization (api key)', async () => {
|
||||||
|
const tag = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.delete(`/tags/${tag.id}/assets`)
|
||||||
|
.set('x-api-key', secret)
|
||||||
|
.send({ ids: [userAsset.id] });
|
||||||
|
expect(status).toBe(403);
|
||||||
|
expect(body).toEqual(errorDto.missingPermission('tag.asset'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to remove own asset from own tag', async () => {
|
||||||
|
const tagA = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
await tagAssets(
|
||||||
|
{ id: tagA.id, bulkIdsDto: { ids: [userAsset.id] } },
|
||||||
|
{ headers: asBearerAuth(user.accessToken) },
|
||||||
|
);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.delete(`/tags/${tagA.id}/assets`)
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`)
|
||||||
|
.send({ ids: [userAsset.id] });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual([expect.objectContaining({ id: userAsset.id, success: true })]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove duplicate assets only once', async () => {
|
||||||
|
const tagA = await create(user.accessToken, { name: 'TagA' });
|
||||||
|
await tagAssets(
|
||||||
|
{ id: tagA.id, bulkIdsDto: { ids: [userAsset.id] } },
|
||||||
|
{ headers: asBearerAuth(user.accessToken) },
|
||||||
|
);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.delete(`/tags/${tagA.id}/assets`)
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`)
|
||||||
|
.send({ ids: [userAsset.id, userAsset.id] });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual([
|
||||||
|
expect.objectContaining({ id: userAsset.id, success: true }),
|
||||||
|
expect.objectContaining({ id: userAsset.id, success: false, error: 'not_found' }),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
LoginResponseDto,
|
LoginResponseDto,
|
||||||
|
createStack,
|
||||||
deleteUserAdmin,
|
deleteUserAdmin,
|
||||||
getMyUser,
|
getMyUser,
|
||||||
getUserAdmin,
|
getUserAdmin,
|
||||||
getUserPreferencesAdmin,
|
getUserPreferencesAdmin,
|
||||||
login,
|
login,
|
||||||
updateAssets,
|
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { createUserDto, uuidDto } from 'src/fixtures';
|
import { createUserDto, uuidDto } from 'src/fixtures';
|
||||||
@@ -321,8 +321,8 @@ describe('/admin/users', () => {
|
|||||||
utils.createAsset(user.accessToken),
|
utils.createAsset(user.accessToken),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await updateAssets(
|
await createStack(
|
||||||
{ assetBulkUpdateDto: { stackParentId: asset1.id, ids: [asset2.id] } },
|
{ stackCreateDto: { assetIds: [asset1.id, asset2.id] } },
|
||||||
{ headers: asBearerAuth(user.accessToken) },
|
{ headers: asBearerAuth(user.accessToken) },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -236,6 +236,32 @@ describe('/users', () => {
|
|||||||
const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
|
const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
|
||||||
expect(after).toMatchObject({ download: { archiveSize: 1_234_567 } });
|
expect(after).toMatchObject({ download: { archiveSize: 1_234_567 } });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should require a boolean for download include embedded videos', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/users/me/preferences`)
|
||||||
|
.send({ download: { includeEmbeddedVideos: 1_234_567.89 } })
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['download.includeEmbeddedVideos must be a boolean value']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update download include embedded videos', async () => {
|
||||||
|
const before = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
|
||||||
|
expect(before).toMatchObject({ download: { includeEmbeddedVideos: false } });
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/users/me/preferences`)
|
||||||
|
.send({ download: { includeEmbeddedVideos: true } })
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toMatchObject({ download: { includeEmbeddedVideos: true } });
|
||||||
|
|
||||||
|
const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
|
||||||
|
expect(after).toMatchObject({ download: { includeEmbeddedVideos: true } });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /users/:id', () => {
|
describe('GET /users/:id', () => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Permission } from '@immich/sdk';
|
||||||
import { stat } from 'node:fs/promises';
|
import { stat } from 'node:fs/promises';
|
||||||
import { app, immichCli, utils } from 'src/utils';
|
import { app, immichCli, utils } from 'src/utils';
|
||||||
import { beforeEach, describe, expect, it } from 'vitest';
|
import { beforeEach, describe, expect, it } from 'vitest';
|
||||||
@@ -29,10 +30,10 @@ describe(`immich login`, () => {
|
|||||||
|
|
||||||
it('should login and save auth.yml with 600', async () => {
|
it('should login and save auth.yml with 600', async () => {
|
||||||
const admin = await utils.adminSetup();
|
const admin = await utils.adminSetup();
|
||||||
const key = await utils.createApiKey(admin.accessToken);
|
const key = await utils.createApiKey(admin.accessToken, [Permission.All]);
|
||||||
const { stdout, stderr, exitCode } = await immichCli(['login', app, `${key.secret}`]);
|
const { stdout, stderr, exitCode } = await immichCli(['login', app, `${key.secret}`]);
|
||||||
expect(stdout.split('\n')).toEqual([
|
expect(stdout.split('\n')).toEqual([
|
||||||
'Logging in to http://127.0.0.1:2283/api',
|
'Logging in to http://127.0.0.1:2285/api',
|
||||||
'Logged in as admin@immich.cloud',
|
'Logged in as admin@immich.cloud',
|
||||||
'Wrote auth info to /tmp/immich/auth.yml',
|
'Wrote auth info to /tmp/immich/auth.yml',
|
||||||
]);
|
]);
|
||||||
@@ -46,11 +47,11 @@ describe(`immich login`, () => {
|
|||||||
|
|
||||||
it('should login without /api in the url', async () => {
|
it('should login without /api in the url', async () => {
|
||||||
const admin = await utils.adminSetup();
|
const admin = await utils.adminSetup();
|
||||||
const key = await utils.createApiKey(admin.accessToken);
|
const key = await utils.createApiKey(admin.accessToken, [Permission.All]);
|
||||||
const { stdout, stderr, exitCode } = await immichCli(['login', app.replaceAll('/api', ''), `${key.secret}`]);
|
const { stdout, stderr, exitCode } = await immichCli(['login', app.replaceAll('/api', ''), `${key.secret}`]);
|
||||||
expect(stdout.split('\n')).toEqual([
|
expect(stdout.split('\n')).toEqual([
|
||||||
'Logging in to http://127.0.0.1:2283',
|
'Logging in to http://127.0.0.1:2285',
|
||||||
'Discovered API at http://127.0.0.1:2283/api',
|
'Discovered API at http://127.0.0.1:2285/api',
|
||||||
'Logged in as admin@immich.cloud',
|
'Logged in as admin@immich.cloud',
|
||||||
'Wrote auth info to /tmp/immich/auth.yml',
|
'Wrote auth info to /tmp/immich/auth.yml',
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ describe(`immich server-info`, () => {
|
|||||||
const { stderr, stdout, exitCode } = await immichCli(['server-info']);
|
const { stderr, stdout, exitCode } = await immichCli(['server-info']);
|
||||||
expect(stdout.split('\n')).toEqual([
|
expect(stdout.split('\n')).toEqual([
|
||||||
expect.stringContaining('Server Info (via admin@immich.cloud'),
|
expect.stringContaining('Server Info (via admin@immich.cloud'),
|
||||||
' Url: http://127.0.0.1:2283/api',
|
' Url: http://127.0.0.1:2285/api',
|
||||||
expect.stringContaining('Version:'),
|
expect.stringContaining('Version:'),
|
||||||
' Formats:',
|
' Formats:',
|
||||||
expect.stringContaining('Images:'),
|
expect.stringContaining('Images:'),
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ export const errorDto = {
|
|||||||
message: expect.any(String),
|
message: expect.any(String),
|
||||||
correlationId: expect.any(String),
|
correlationId: expect.any(String),
|
||||||
},
|
},
|
||||||
|
missingPermission: (permission: string) => ({
|
||||||
|
error: 'Forbidden',
|
||||||
|
statusCode: 403,
|
||||||
|
message: `Missing required permission: ${permission}`,
|
||||||
|
correlationId: expect.any(String),
|
||||||
|
}),
|
||||||
wrongPassword: {
|
wrongPassword: {
|
||||||
error: 'Bad Request',
|
error: 'Bad Request',
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
|
|||||||
@@ -86,14 +86,14 @@ const setup = async () => {
|
|||||||
{
|
{
|
||||||
client_id: OAuthClient.DEFAULT,
|
client_id: OAuthClient.DEFAULT,
|
||||||
client_secret: OAuthClient.DEFAULT,
|
client_secret: OAuthClient.DEFAULT,
|
||||||
redirect_uris: ['http://127.0.0.1:2283/auth/login'],
|
redirect_uris: ['http://127.0.0.1:2285/auth/login'],
|
||||||
grant_types: ['authorization_code'],
|
grant_types: ['authorization_code'],
|
||||||
response_types: ['code'],
|
response_types: ['code'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
client_id: OAuthClient.RS256_TOKENS,
|
client_id: OAuthClient.RS256_TOKENS,
|
||||||
client_secret: OAuthClient.RS256_TOKENS,
|
client_secret: OAuthClient.RS256_TOKENS,
|
||||||
redirect_uris: ['http://127.0.0.1:2283/auth/login'],
|
redirect_uris: ['http://127.0.0.1:2285/auth/login'],
|
||||||
grant_types: ['authorization_code'],
|
grant_types: ['authorization_code'],
|
||||||
id_token_signed_response_alg: 'RS256',
|
id_token_signed_response_alg: 'RS256',
|
||||||
jwks: { keys: [await exportJWK(publicKey)] },
|
jwks: { keys: [await exportJWK(publicKey)] },
|
||||||
@@ -101,7 +101,7 @@ const setup = async () => {
|
|||||||
{
|
{
|
||||||
client_id: OAuthClient.RS256_PROFILE,
|
client_id: OAuthClient.RS256_PROFILE,
|
||||||
client_secret: OAuthClient.RS256_PROFILE,
|
client_secret: OAuthClient.RS256_PROFILE,
|
||||||
redirect_uris: ['http://127.0.0.1:2283/auth/login'],
|
redirect_uris: ['http://127.0.0.1:2285/auth/login'],
|
||||||
grant_types: ['authorization_code'],
|
grant_types: ['authorization_code'],
|
||||||
userinfo_signed_response_alg: 'RS256',
|
userinfo_signed_response_alg: 'RS256',
|
||||||
jwks: { keys: [await exportJWK(publicKey)] },
|
jwks: { keys: [await exportJWK(publicKey)] },
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
CreateAlbumDto,
|
CreateAlbumDto,
|
||||||
CreateLibraryDto,
|
CreateLibraryDto,
|
||||||
MetadataSearchDto,
|
MetadataSearchDto,
|
||||||
|
Permission,
|
||||||
PersonCreateDto,
|
PersonCreateDto,
|
||||||
SharedLinkCreateDto,
|
SharedLinkCreateDto,
|
||||||
UserAdminCreateDto,
|
UserAdminCreateDto,
|
||||||
@@ -52,8 +53,8 @@ type WaitOptions = { event: EventType; id?: string; total?: number; timeout?: nu
|
|||||||
type AdminSetupOptions = { onboarding?: boolean };
|
type AdminSetupOptions = { onboarding?: boolean };
|
||||||
type FileData = { bytes?: Buffer; filename: string };
|
type FileData = { bytes?: Buffer; filename: string };
|
||||||
|
|
||||||
const dbUrl = 'postgres://postgres:postgres@127.0.0.1:5433/immich';
|
const dbUrl = 'postgres://postgres:postgres@127.0.0.1:5435/immich';
|
||||||
export const baseUrl = 'http://127.0.0.1:2283';
|
export const baseUrl = 'http://127.0.0.1:2285';
|
||||||
export const shareUrl = `${baseUrl}/share`;
|
export const shareUrl = `${baseUrl}/share`;
|
||||||
export const app = `${baseUrl}/api`;
|
export const app = `${baseUrl}/api`;
|
||||||
// TODO move test assets into e2e/assets
|
// TODO move test assets into e2e/assets
|
||||||
@@ -147,6 +148,7 @@ export const utils = {
|
|||||||
'sessions',
|
'sessions',
|
||||||
'users',
|
'users',
|
||||||
'system_metadata',
|
'system_metadata',
|
||||||
|
'tags',
|
||||||
];
|
];
|
||||||
|
|
||||||
const sql: string[] = [];
|
const sql: string[] = [];
|
||||||
@@ -279,8 +281,8 @@ export const utils = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
createApiKey: (accessToken: string) => {
|
createApiKey: (accessToken: string, permissions: Permission[]) => {
|
||||||
return createApiKey({ apiKeyCreateDto: { name: 'e2e' } }, { headers: asBearerAuth(accessToken) });
|
return createApiKey({ apiKeyCreateDto: { name: 'e2e', permissions } }, { headers: asBearerAuth(accessToken) });
|
||||||
},
|
},
|
||||||
|
|
||||||
createAlbum: (accessToken: string, dto: CreateAlbumDto) =>
|
createAlbum: (accessToken: string, dto: CreateAlbumDto) =>
|
||||||
@@ -492,7 +494,7 @@ export const utils = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
cliLogin: async (accessToken: string) => {
|
cliLogin: async (accessToken: string) => {
|
||||||
const key = await utils.createApiKey(accessToken);
|
const key = await utils.createApiKey(accessToken, [Permission.All]);
|
||||||
await immichCli(['login', app, `${key.secret}`]);
|
await immichCli(['login', app, `${key.secret}`]);
|
||||||
return key.secret;
|
return key.secret;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import { AssetMediaResponseDto, LoginResponseDto, SharedLinkType } from '@immich/sdk';
|
import { AssetMediaResponseDto, LoginResponseDto, SharedLinkType } from '@immich/sdk';
|
||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
import type { Socket } from 'socket.io-client';
|
||||||
import { utils } from 'src/utils';
|
import { utils } from 'src/utils';
|
||||||
|
|
||||||
test.describe('Detail Panel', () => {
|
test.describe('Detail Panel', () => {
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
let asset: AssetMediaResponseDto;
|
let asset: AssetMediaResponseDto;
|
||||||
|
let websocket: Socket;
|
||||||
|
|
||||||
test.beforeAll(async () => {
|
test.beforeAll(async () => {
|
||||||
utils.initSdk();
|
utils.initSdk();
|
||||||
await utils.resetDatabase();
|
await utils.resetDatabase();
|
||||||
admin = await utils.adminSetup();
|
admin = await utils.adminSetup();
|
||||||
asset = await utils.createAsset(admin.accessToken);
|
asset = await utils.createAsset(admin.accessToken);
|
||||||
|
websocket = await utils.connectWebsocket(admin.accessToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(() => {
|
||||||
|
utils.disconnectWebsocket(websocket);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can be opened for shared links', async ({ page }) => {
|
test('can be opened for shared links', async ({ page }) => {
|
||||||
@@ -57,4 +64,23 @@ test.describe('Detail Panel', () => {
|
|||||||
await expect(textarea).toBeVisible();
|
await expect(textarea).toBeVisible();
|
||||||
await expect(textarea).not.toBeDisabled();
|
await expect(textarea).not.toBeDisabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('description changes are visible after reopening', async ({ context, page }) => {
|
||||||
|
await utils.setAuthCookies(context, admin.accessToken);
|
||||||
|
await page.goto(`/photos/${asset.id}`);
|
||||||
|
await page.waitForSelector('#immich-asset-viewer');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Info' }).click();
|
||||||
|
const textarea = page.getByRole('textbox', { name: 'Add a description' });
|
||||||
|
await textarea.fill('new description');
|
||||||
|
await expect(textarea).toHaveValue('new description');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Info' }).click();
|
||||||
|
await expect(textarea).not.toBeVisible();
|
||||||
|
await page.getByRole('button', { name: 'Info' }).click();
|
||||||
|
await expect(textarea).toBeVisible();
|
||||||
|
|
||||||
|
await utils.waitForWebsocketEvent({ event: 'assetUpdate', id: asset.id });
|
||||||
|
await expect(textarea).toHaveValue('new description');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ test.describe('Registration', () => {
|
|||||||
// onboarding
|
// onboarding
|
||||||
await expect(page).toHaveURL('/auth/onboarding');
|
await expect(page).toHaveURL('/auth/onboarding');
|
||||||
await page.getByRole('button', { name: 'Theme' }).click();
|
await page.getByRole('button', { name: 'Theme' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Privacy' }).click();
|
||||||
await page.getByRole('button', { name: 'Storage Template' }).click();
|
await page.getByRole('button', { name: 'Storage Template' }).click();
|
||||||
await page.getByRole('button', { name: 'Done' }).click();
|
await page.getByRole('button', { name: 'Done' }).click();
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ test.describe('Photo Viewer', () => {
|
|||||||
await page.waitForLoadState('load');
|
await page.waitForLoadState('load');
|
||||||
// this is the spinner
|
// this is the spinner
|
||||||
await page.waitForSelector('svg[role=status]');
|
await page.waitForSelector('svg[role=status]');
|
||||||
await expect(page.getByRole('status')).toBeVisible();
|
await expect(page.getByTestId('loading-spinner')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('loads high resolution photo when zoomed', async ({ page }) => {
|
test('loads high resolution photo when zoomed', async ({ page }) => {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ test.describe('Shared Links', () => {
|
|||||||
test('download from a shared link', async ({ page }) => {
|
test('download from a shared link', async ({ page }) => {
|
||||||
await page.goto(`/share/${sharedLink.key}`);
|
await page.goto(`/share/${sharedLink.key}`);
|
||||||
await page.getByRole('heading', { name: 'Test Album' }).waitFor();
|
await page.getByRole('heading', { name: 'Test Album' }).waitFor();
|
||||||
await page.locator('.group > div').first().hover();
|
await page.locator('.group').first().hover();
|
||||||
await page.waitForSelector('#asset-group-by-date svg');
|
await page.waitForSelector('#asset-group-by-date svg');
|
||||||
await page.getByRole('checkbox').click();
|
await page.getByRole('checkbox').click();
|
||||||
await page.getByRole('button', { name: 'Download' }).click();
|
await page.getByRole('button', { name: 'Download' }).click();
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ test.describe('Websocket', () => {
|
|||||||
|
|
||||||
test('connects using ipv4', async ({ page, context }) => {
|
test('connects using ipv4', async ({ page, context }) => {
|
||||||
await utils.setAuthCookies(context, admin.accessToken);
|
await utils.setAuthCookies(context, admin.accessToken);
|
||||||
await page.goto('http://127.0.0.1:2283/');
|
await page.goto('http://127.0.0.1:2285/');
|
||||||
await expect(page.locator('#sidebar')).toContainText('Server Online');
|
await expect(page.locator('#sidebar')).toContainText('Server Online');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('connects using ipv6', async ({ page, context }) => {
|
test('connects using ipv6', async ({ page, context }) => {
|
||||||
await utils.setAuthCookies(context, admin.accessToken, '[::1]');
|
await utils.setAuthCookies(context, admin.accessToken, '[::1]');
|
||||||
await page.goto('http://[::1]:2283/');
|
await page.goto('http://[::1]:2285/');
|
||||||
await expect(page.locator('#sidebar')).toContainText('Server Online');
|
await expect(page.locator('#sidebar')).toContainText('Server Online');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Submodule e2e/test-assets updated: 898069e47f...4e9731d3fc
@@ -3,7 +3,7 @@ import { defineConfig } from 'vitest/config';
|
|||||||
// skip `docker compose up` if `make e2e` was already run
|
// skip `docker compose up` if `make e2e` was already run
|
||||||
const globalSetup: string[] = ['src/setup/auth-server.ts'];
|
const globalSetup: string[] = ['src/setup/auth-server.ts'];
|
||||||
try {
|
try {
|
||||||
await fetch('http://127.0.0.1:2283/api/server-info/ping');
|
await fetch('http://127.0.0.1:2285/api/server-info/ping');
|
||||||
} catch {
|
} catch {
|
||||||
globalSetup.push('src/setup/docker-compose.ts');
|
globalSetup.push('src/setup/docker-compose.ts');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
ARG DEVICE=cpu
|
ARG DEVICE=cpu
|
||||||
|
|
||||||
FROM python:3.11-bookworm@sha256:d0131ce0ff4bdb5e9eae6bc86ebde891c207d5cac1f3f582b5de0f903cc68384 AS builder-cpu
|
FROM python:3.11-bookworm@sha256:f7543d9969bdc112dd9819ca642e14433fdacfe857f170f6b803392fc7e451ad AS builder-cpu
|
||||||
|
|
||||||
FROM builder-cpu AS builder-openvino
|
FROM builder-cpu AS builder-openvino
|
||||||
|
|
||||||
@@ -34,15 +34,21 @@ RUN python3 -m venv /opt/venv
|
|||||||
COPY poetry.lock pyproject.toml ./
|
COPY poetry.lock pyproject.toml ./
|
||||||
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
|
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
|
||||||
|
|
||||||
FROM python:3.11-slim-bookworm@sha256:a90e299af8a9cd6b59c4aaed2b024c78561476978244a1ab89742a4a5ac8c974 AS prod-cpu
|
FROM python:3.11-slim-bookworm@sha256:ad5dadd957a398226996bc4846e522c39f2a77340b531b28aaab85b2d361210b AS prod-cpu
|
||||||
|
|
||||||
FROM prod-cpu AS prod-openvino
|
FROM prod-cpu AS prod-openvino
|
||||||
|
|
||||||
COPY scripts/configure-apt.sh ./
|
RUN apt-get update && \
|
||||||
RUN ./configure-apt.sh && \
|
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
|
||||||
apt-get update && \
|
wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17193.4/intel-igc-core_1.0.17193.4_amd64.deb && \
|
||||||
apt-get install -t unstable --no-install-recommends -yqq intel-opencl-icd && \
|
wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17193.4/intel-igc-opencl_1.0.17193.4_amd64.deb && \
|
||||||
rm configure-apt.sh
|
wget https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-opencl-icd-dbgsym_24.26.30049.6_amd64.ddeb && \
|
||||||
|
wget https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-opencl-icd_24.26.30049.6_amd64.deb && \
|
||||||
|
wget https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/libigdgmm12_22.3.20_amd64.deb && \
|
||||||
|
dpkg -i *.deb && \
|
||||||
|
rm *.deb && \
|
||||||
|
apt-get remove wget -yqq && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
FROM nvidia/cuda:12.3.2-cudnn9-runtime-ubuntu22.04@sha256:fa44193567d1908f7ca1f3abf8623ce9c63bc8cba7bcfdb32702eb04d326f7a8 AS prod-cuda
|
FROM nvidia/cuda:12.3.2-cudnn9-runtime-ubuntu22.04@sha256:fa44193567d1908f7ca1f3abf8623ce9c63bc8cba7bcfdb32702eb04d326f7a8 AS prod-cuda
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from tokenizers import Encoding, Tokenizer
|
|||||||
|
|
||||||
from app.config import log
|
from app.config import log
|
||||||
from app.models.base import InferenceModel
|
from app.models.base import InferenceModel
|
||||||
|
from app.models.transforms import clean_text
|
||||||
from app.schemas import ModelSession, ModelTask, ModelType
|
from app.schemas import ModelSession, ModelTask, ModelType
|
||||||
|
|
||||||
|
|
||||||
@@ -25,6 +26,8 @@ class BaseCLIPTextualEncoder(InferenceModel):
|
|||||||
session = super()._load()
|
session = super()._load()
|
||||||
log.debug(f"Loading tokenizer for CLIP model '{self.model_name}'")
|
log.debug(f"Loading tokenizer for CLIP model '{self.model_name}'")
|
||||||
self.tokenizer = self._load_tokenizer()
|
self.tokenizer = self._load_tokenizer()
|
||||||
|
tokenizer_kwargs: dict[str, Any] | None = self.text_cfg.get("tokenizer_kwargs")
|
||||||
|
self.canonicalize = tokenizer_kwargs is not None and tokenizer_kwargs.get("clean") == "canonicalize"
|
||||||
log.debug(f"Loaded tokenizer for CLIP model '{self.model_name}'")
|
log.debug(f"Loaded tokenizer for CLIP model '{self.model_name}'")
|
||||||
|
|
||||||
return session
|
return session
|
||||||
@@ -56,6 +59,11 @@ class BaseCLIPTextualEncoder(InferenceModel):
|
|||||||
log.debug(f"Loaded model config for CLIP model '{self.model_name}'")
|
log.debug(f"Loaded model config for CLIP model '{self.model_name}'")
|
||||||
return model_cfg
|
return model_cfg
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text_cfg(self) -> dict[str, Any]:
|
||||||
|
text_cfg: dict[str, Any] = self.model_cfg["text_cfg"]
|
||||||
|
return text_cfg
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def tokenizer_file(self) -> dict[str, Any]:
|
def tokenizer_file(self) -> dict[str, Any]:
|
||||||
log.debug(f"Loading tokenizer file for CLIP model '{self.model_name}'")
|
log.debug(f"Loading tokenizer file for CLIP model '{self.model_name}'")
|
||||||
@@ -73,8 +81,7 @@ class BaseCLIPTextualEncoder(InferenceModel):
|
|||||||
|
|
||||||
class OpenClipTextualEncoder(BaseCLIPTextualEncoder):
|
class OpenClipTextualEncoder(BaseCLIPTextualEncoder):
|
||||||
def _load_tokenizer(self) -> Tokenizer:
|
def _load_tokenizer(self) -> Tokenizer:
|
||||||
text_cfg: dict[str, Any] = self.model_cfg["text_cfg"]
|
context_length: int = self.text_cfg.get("context_length", 77)
|
||||||
context_length: int = text_cfg.get("context_length", 77)
|
|
||||||
pad_token: str = self.tokenizer_cfg["pad_token"]
|
pad_token: str = self.tokenizer_cfg["pad_token"]
|
||||||
|
|
||||||
tokenizer: Tokenizer = Tokenizer.from_file(self.tokenizer_file_path.as_posix())
|
tokenizer: Tokenizer = Tokenizer.from_file(self.tokenizer_file_path.as_posix())
|
||||||
@@ -86,12 +93,14 @@ class OpenClipTextualEncoder(BaseCLIPTextualEncoder):
|
|||||||
return tokenizer
|
return tokenizer
|
||||||
|
|
||||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||||
|
text = clean_text(text, canonicalize=self.canonicalize)
|
||||||
tokens: Encoding = self.tokenizer.encode(text)
|
tokens: Encoding = self.tokenizer.encode(text)
|
||||||
return {"text": np.array([tokens.ids], dtype=np.int32)}
|
return {"text": np.array([tokens.ids], dtype=np.int32)}
|
||||||
|
|
||||||
|
|
||||||
class MClipTextualEncoder(OpenClipTextualEncoder):
|
class MClipTextualEncoder(OpenClipTextualEncoder):
|
||||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||||
|
text = clean_text(text, canonicalize=self.canonicalize)
|
||||||
tokens: Encoding = self.tokenizer.encode(text)
|
tokens: Encoding = self.tokenizer.encode(text)
|
||||||
return {
|
return {
|
||||||
"input_ids": np.array([tokens.ids], dtype=np.int32),
|
"input_ids": np.array([tokens.ids], dtype=np.int32),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import string
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import IO
|
from typing import IO
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ from numpy.typing import NDArray
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
_PIL_RESAMPLING_METHODS = {resampling.name.lower(): resampling for resampling in Image.Resampling}
|
_PIL_RESAMPLING_METHODS = {resampling.name.lower(): resampling for resampling in Image.Resampling}
|
||||||
|
_PUNCTUATION_TRANS = str.maketrans("", "", string.punctuation)
|
||||||
|
|
||||||
|
|
||||||
def resize_pil(img: Image.Image, size: int) -> Image.Image:
|
def resize_pil(img: Image.Image, size: int) -> Image.Image:
|
||||||
@@ -60,3 +62,10 @@ def decode_cv2(image_bytes: NDArray[np.uint8] | bytes | Image.Image) -> NDArray[
|
|||||||
if isinstance(image_bytes, Image.Image):
|
if isinstance(image_bytes, Image.Image):
|
||||||
return pil_to_cv2(image_bytes)
|
return pil_to_cv2(image_bytes)
|
||||||
return image_bytes
|
return image_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def clean_text(text: str, canonicalize: bool = False) -> str:
|
||||||
|
text = " ".join(text.split())
|
||||||
|
if canonicalize:
|
||||||
|
text = text.translate(_PUNCTUATION_TRANS).lower()
|
||||||
|
return text
|
||||||
|
|||||||
@@ -379,13 +379,40 @@ class TestCLIP:
|
|||||||
|
|
||||||
clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache")
|
clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache")
|
||||||
clip_encoder._load()
|
clip_encoder._load()
|
||||||
tokens = clip_encoder.tokenize("test search query")
|
tokens = clip_encoder.tokenize("test search query")
|
||||||
|
|
||||||
assert "text" in tokens
|
assert "text" in tokens
|
||||||
assert isinstance(tokens["text"], np.ndarray)
|
assert isinstance(tokens["text"], np.ndarray)
|
||||||
assert tokens["text"].shape == (1, 77)
|
assert tokens["text"].shape == (1, 77)
|
||||||
assert tokens["text"].dtype == np.int32
|
assert tokens["text"].dtype == np.int32
|
||||||
assert np.allclose(tokens["text"], np.array([mock_ids], dtype=np.int32), atol=0)
|
assert np.allclose(tokens["text"], np.array([mock_ids], dtype=np.int32), atol=0)
|
||||||
|
mock_tokenizer.encode.assert_called_once_with("test search query")
|
||||||
|
|
||||||
|
def test_openclip_tokenizer_canonicalizes_text(
|
||||||
|
self,
|
||||||
|
mocker: MockerFixture,
|
||||||
|
clip_model_cfg: dict[str, Any],
|
||||||
|
clip_tokenizer_cfg: Callable[[Path], dict[str, Any]],
|
||||||
|
) -> None:
|
||||||
|
clip_model_cfg["text_cfg"]["tokenizer_kwargs"] = {"clean": "canonicalize"}
|
||||||
|
mocker.patch.object(OpenClipTextualEncoder, "download")
|
||||||
|
mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg)
|
||||||
|
mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||||
|
mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value
|
||||||
|
mock_tokenizer = mocker.patch("app.models.clip.textual.Tokenizer.from_file", autospec=True).return_value
|
||||||
|
mock_ids = [randint(0, 50000) for _ in range(77)]
|
||||||
|
mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids)
|
||||||
|
|
||||||
|
clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache")
|
||||||
|
clip_encoder._load()
|
||||||
|
tokens = clip_encoder.tokenize("Test Search Query!")
|
||||||
|
|
||||||
|
assert "text" in tokens
|
||||||
|
assert isinstance(tokens["text"], np.ndarray)
|
||||||
|
assert tokens["text"].shape == (1, 77)
|
||||||
|
assert tokens["text"].dtype == np.int32
|
||||||
|
assert np.allclose(tokens["text"], np.array([mock_ids], dtype=np.int32), atol=0)
|
||||||
|
mock_tokenizer.encode.assert_called_once_with("test search query")
|
||||||
|
|
||||||
def test_mclip_tokenizer(
|
def test_mclip_tokenizer(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM mambaorg/micromamba:bookworm-slim@sha256:954e438daab0ad0835430ea84acb27dd47d1ea35a7120c3c9dd9d1a5578f4b13 AS builder
|
FROM mambaorg/micromamba:bookworm-slim@sha256:475730daef12ff9c0733e70092aeeefdf4c373a584c952dac3f7bdb739601990 AS builder
|
||||||
|
|
||||||
ENV TRANSFORMERS_CACHE=/cache \
|
ENV TRANSFORMERS_CACHE=/cache \
|
||||||
PYTHONDONTWRITEBYTECODE=1 \
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
|||||||
606
machine-learning/poetry.lock
generated
606
machine-learning/poetry.lock
generated
@@ -64,33 +64,33 @@ trio = ["trio (>=0.23)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "black"
|
name = "black"
|
||||||
version = "24.4.2"
|
version = "24.8.0"
|
||||||
description = "The uncompromising code formatter."
|
description = "The uncompromising code formatter."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"},
|
{file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"},
|
||||||
{file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"},
|
{file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"},
|
||||||
{file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"},
|
{file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"},
|
||||||
{file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"},
|
{file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"},
|
||||||
{file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"},
|
{file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"},
|
||||||
{file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"},
|
{file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"},
|
||||||
{file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"},
|
{file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"},
|
||||||
{file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"},
|
{file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"},
|
||||||
{file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"},
|
{file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"},
|
||||||
{file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"},
|
{file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"},
|
||||||
{file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"},
|
{file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"},
|
||||||
{file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"},
|
{file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"},
|
||||||
{file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"},
|
{file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"},
|
||||||
{file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"},
|
{file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"},
|
||||||
{file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"},
|
{file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"},
|
||||||
{file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"},
|
{file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"},
|
||||||
{file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"},
|
{file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"},
|
||||||
{file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"},
|
{file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"},
|
||||||
{file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"},
|
{file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"},
|
||||||
{file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"},
|
{file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"},
|
||||||
{file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"},
|
{file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"},
|
||||||
{file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"},
|
{file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -680,23 +680,23 @@ test = ["pytest (>=6)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi-slim"
|
name = "fastapi-slim"
|
||||||
version = "0.111.1"
|
version = "0.112.1"
|
||||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "fastapi_slim-0.111.1-py3-none-any.whl", hash = "sha256:ac29948dcbf84cc78d68ed2c4df4e695ac265cf53c339e5794008476e9befbbb"},
|
{file = "fastapi_slim-0.112.1-py3-none-any.whl", hash = "sha256:cc227cf9402d0ba54a24f80eb205c33bcb25d3ea18d53fdac3fd76ea5af8e76d"},
|
||||||
{file = "fastapi_slim-0.111.1.tar.gz", hash = "sha256:f799a60658f56c49fe3842eb534730fabe1168731c0b407b98a042c8d57be39d"},
|
{file = "fastapi_slim-0.112.1.tar.gz", hash = "sha256:876ebd24e72273986709db2d469b75dc18f04c3ab9140ffd78b29d7785d26687"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
||||||
starlette = ">=0.37.2,<0.38.0"
|
starlette = ">=0.37.2,<0.39.0"
|
||||||
typing-extensions = ">=4.8.0"
|
typing-extensions = ">=4.8.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
all = ["email_validator (>=2.0.0)", "fastapi-cli (>=0.0.2)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
all = ["email_validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
standard = ["email_validator (>=2.0.0)", "fastapi-cli (>=0.0.2)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"]
|
standard = ["email_validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filelock"
|
name = "filelock"
|
||||||
@@ -878,13 +878,13 @@ tqdm = ["tqdm"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ftfy"
|
name = "ftfy"
|
||||||
version = "6.2.0"
|
version = "6.2.3"
|
||||||
description = "Fixes mojibake and other problems with Unicode, after the fact"
|
description = "Fixes mojibake and other problems with Unicode, after the fact"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8,<4"
|
python-versions = "<4,>=3.8.1"
|
||||||
files = [
|
files = [
|
||||||
{file = "ftfy-6.2.0-py3-none-any.whl", hash = "sha256:f94a2c34b76e07475720e3096f5ca80911d152406fbde66fdb45c4d0c9150026"},
|
{file = "ftfy-6.2.3-py3-none-any.whl", hash = "sha256:f15761b023f3061a66207d33f0c0149ad40a8319fd16da91796363e2c049fdf8"},
|
||||||
{file = "ftfy-6.2.0.tar.gz", hash = "sha256:5e42143c7025ef97944ca2619d6b61b0619fc6654f98771d39e862c1424c75c0"},
|
{file = "ftfy-6.2.3.tar.gz", hash = "sha256:79b505988f29d577a58a9069afe75553a02a46e42de6091c0660cdc67812badc"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1111,13 +1111,13 @@ test = ["objgraph", "psutil"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gunicorn"
|
name = "gunicorn"
|
||||||
version = "22.0.0"
|
version = "23.0.0"
|
||||||
description = "WSGI HTTP Server for UNIX"
|
description = "WSGI HTTP Server for UNIX"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"},
|
{file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"},
|
||||||
{file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"},
|
{file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1236,13 +1236,13 @@ socks = ["socksio (==1.*)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "huggingface-hub"
|
name = "huggingface-hub"
|
||||||
version = "0.24.5"
|
version = "0.24.6"
|
||||||
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
|
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.0"
|
python-versions = ">=3.8.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "huggingface_hub-0.24.5-py3-none-any.whl", hash = "sha256:d93fb63b1f1a919a22ce91a14518974e81fc4610bf344dfe7572343ce8d3aced"},
|
{file = "huggingface_hub-0.24.6-py3-none-any.whl", hash = "sha256:a990f3232aa985fe749bc9474060cbad75e8b2f115f6665a9fda5b9c97818970"},
|
||||||
{file = "huggingface_hub-0.24.5.tar.gz", hash = "sha256:7b45d6744dd53ce9cbf9880957de00e9d10a9ae837f1c9b7255fc8fa4e8264f3"},
|
{file = "huggingface_hub-0.24.6.tar.gz", hash = "sha256:cc2579e761d070713eaa9c323e3debe39d5b464ae3a7261c39a9195b27bb8000"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1530,13 +1530,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "locust"
|
name = "locust"
|
||||||
version = "2.31.1"
|
version = "2.31.3"
|
||||||
description = "Developer-friendly load testing framework"
|
description = "Developer-friendly load testing framework"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "locust-2.31.1-py3-none-any.whl", hash = "sha256:20756509939004e95c622ac3042886edab38b736f00534cc03ce2774064e7f71"},
|
{file = "locust-2.31.3-py3-none-any.whl", hash = "sha256:03122e007519b371a5a553d578af502826755de83551d79ea8a412ea1c660115"},
|
||||||
{file = "locust-2.31.1.tar.gz", hash = "sha256:d26b7333cdef80645f3978d8ff9aabab4d53e41ed82cc8490212aa68e8498fdd"},
|
{file = "locust-2.31.3.tar.gz", hash = "sha256:25f4603f24afa11ef1ee1f26b1c86a232eb9a1140be30b2a4642c12d7a7af8ae"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1962,42 +1962,42 @@ reference = ["Pillow", "google-re2"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "onnxruntime"
|
name = "onnxruntime"
|
||||||
version = "1.18.1"
|
version = "1.19.0"
|
||||||
description = "ONNX Runtime is a runtime accelerator for Machine Learning models"
|
description = "ONNX Runtime is a runtime accelerator for Machine Learning models"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "onnxruntime-1.18.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:29ef7683312393d4ba04252f1b287d964bd67d5e6048b94d2da3643986c74d80"},
|
{file = "onnxruntime-1.19.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6ce22a98dfec7b646ae305f52d0ce14a189a758b02ea501860ca719f4b0ae04b"},
|
||||||
{file = "onnxruntime-1.18.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc706eb1df06ddf55776e15a30519fb15dda7697f987a2bbda4962845e3cec05"},
|
{file = "onnxruntime-1.19.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19019c72873f26927aa322c54cf2bf7312b23451b27451f39b88f57016c94f8b"},
|
||||||
{file = "onnxruntime-1.18.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7de69f5ced2a263531923fa68bbec52a56e793b802fcd81a03487b5e292bc3a"},
|
{file = "onnxruntime-1.19.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8eaa16df99171dc636e30108d15597aed8c4c2dd9dbfdd07cc464d57d73fb275"},
|
||||||
{file = "onnxruntime-1.18.1-cp310-cp310-win32.whl", hash = "sha256:221e5b16173926e6c7de2cd437764492aa12b6811f45abd37024e7cf2ae5d7e3"},
|
{file = "onnxruntime-1.19.0-cp310-cp310-win32.whl", hash = "sha256:0eb0f8dbe596fd0f4737fe511fdbb17603853a7d204c5b2ca38d3c7808fc556b"},
|
||||||
{file = "onnxruntime-1.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:75211b619275199c861ee94d317243b8a0fcde6032e5a80e1aa9ded8ab4c6060"},
|
{file = "onnxruntime-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:616092d54ba8023b7bc0a5f6d900a07a37cc1cfcc631873c15f8c1d6e9e184d4"},
|
||||||
{file = "onnxruntime-1.18.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:f26582882f2dc581b809cfa41a125ba71ad9e715738ec6402418df356969774a"},
|
{file = "onnxruntime-1.19.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a2b53b3c287cd933e5eb597273926e899082d8c84ab96e1b34035764a1627e17"},
|
||||||
{file = "onnxruntime-1.18.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef36f3a8b768506d02be349ac303fd95d92813ba3ba70304d40c3cd5c25d6a4c"},
|
{file = "onnxruntime-1.19.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e94984663963e74fbb468bde9ec6f19dcf890b594b35e249c4dc8789d08993c5"},
|
||||||
{file = "onnxruntime-1.18.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:170e711393e0618efa8ed27b59b9de0ee2383bd2a1f93622a97006a5ad48e434"},
|
{file = "onnxruntime-1.19.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f379d1f050cfb55ce015d53727b78ee362febc065c38eed81512b22b757da73"},
|
||||||
{file = "onnxruntime-1.18.1-cp311-cp311-win32.whl", hash = "sha256:9b6a33419b6949ea34e0dc009bc4470e550155b6da644571ecace4b198b0d88f"},
|
{file = "onnxruntime-1.19.0-cp311-cp311-win32.whl", hash = "sha256:4ccb48faea02503275ae7e79e351434fc43c294c4cb5c4d8bcb7479061396614"},
|
||||||
{file = "onnxruntime-1.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:5c1380a9f1b7788da742c759b6a02ba771fe1ce620519b2b07309decbd1a2fe1"},
|
{file = "onnxruntime-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:9cdc8d311289a84e77722de68bd22b8adfb94eea26f4be6f9e017350faac8b18"},
|
||||||
{file = "onnxruntime-1.18.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:31bd57a55e3f983b598675dfc7e5d6f0877b70ec9864b3cc3c3e1923d0a01919"},
|
{file = "onnxruntime-1.19.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1b59eaec1be9a8613c5fdeaafe67f73a062edce3ac03bbbdc9e2d98b58a30617"},
|
||||||
{file = "onnxruntime-1.18.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9e03c4ba9f734500691a4d7d5b381cd71ee2f3ce80a1154ac8f7aed99d1ecaa"},
|
{file = "onnxruntime-1.19.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be4144d014a4b25184e63ce7a463a2e7796e2f3df931fccc6a6aefa6f1365dc5"},
|
||||||
{file = "onnxruntime-1.18.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:781aa9873640f5df24524f96f6070b8c550c66cb6af35710fd9f92a20b4bfbf6"},
|
{file = "onnxruntime-1.19.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10d7e7d4ca7021ce7f29a66dbc6071addf2de5839135339bd855c6d9c2bba371"},
|
||||||
{file = "onnxruntime-1.18.1-cp312-cp312-win32.whl", hash = "sha256:3a2d9ab6254ca62adbb448222e630dc6883210f718065063518c8f93a32432be"},
|
{file = "onnxruntime-1.19.0-cp312-cp312-win32.whl", hash = "sha256:87f2c58b577a1fb31dc5d92b647ecc588fd5f1ea0c3ad4526f5f80a113357c8d"},
|
||||||
{file = "onnxruntime-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:ad93c560b1c38c27c0275ffd15cd7f45b3ad3fc96653c09ce2931179982ff204"},
|
{file = "onnxruntime-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:8a1f50d49676d7b69566536ff039d9e4e95fc482a55673719f46528218ecbb94"},
|
||||||
{file = "onnxruntime-1.18.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:3b55dc9d3c67626388958a3eb7ad87eb7c70f75cb0f7ff4908d27b8b42f2475c"},
|
{file = "onnxruntime-1.19.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:71423c8c4b2d7a58956271534302ec72721c62a41efd0c4896343249b8399ab0"},
|
||||||
{file = "onnxruntime-1.18.1-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f80dbcfb6763cc0177a31168b29b4bd7662545b99a19e211de8c734b657e0669"},
|
{file = "onnxruntime-1.19.0-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d63630d45e9498f96e75bbeb7fd4a56acb10155de0de4d0e18d1b6cbb0b358a"},
|
||||||
{file = "onnxruntime-1.18.1-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1ff2c61a16d6c8631796c54139bafea41ee7736077a0fc64ee8ae59432f5c58"},
|
{file = "onnxruntime-1.19.0-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3bfd15db1e8794d379a86c1a9116889f47f2cca40cc82208fc4f7e8c38e8522"},
|
||||||
{file = "onnxruntime-1.18.1-cp38-cp38-win32.whl", hash = "sha256:219855bd272fe0c667b850bf1a1a5a02499269a70d59c48e6f27f9c8bcb25d02"},
|
{file = "onnxruntime-1.19.0-cp38-cp38-win32.whl", hash = "sha256:3b098003b6b4cb37cc84942e5f1fe27f945dd857cbd2829c824c26b0ba4a247e"},
|
||||||
{file = "onnxruntime-1.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:afdf16aa607eb9a2c60d5ca2d5abf9f448e90c345b6b94c3ed14f4fb7e6a2d07"},
|
{file = "onnxruntime-1.19.0-cp38-cp38-win_amd64.whl", hash = "sha256:cea067a6541d6787d903ee6843401c5b1332a266585160d9700f9f0939443886"},
|
||||||
{file = "onnxruntime-1.18.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:128df253ade673e60cea0955ec9d0e89617443a6d9ce47c2d79eb3f72a3be3de"},
|
{file = "onnxruntime-1.19.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:c4fcff12dc5ca963c5f76b9822bb404578fa4a98c281e8c666b429192799a099"},
|
||||||
{file = "onnxruntime-1.18.1-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9839491e77e5c5a175cab3621e184d5a88925ee297ff4c311b68897197f4cde9"},
|
{file = "onnxruntime-1.19.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6dcad8a4db908fbe70b98c79cea1c8b6ac3316adf4ce93453136e33a524ac59"},
|
||||||
{file = "onnxruntime-1.18.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad3187c1faff3ac15f7f0e7373ef4788c582cafa655a80fdbb33eaec88976c66"},
|
{file = "onnxruntime-1.19.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bc449907c6e8d99eee5ae5cc9c8fdef273d801dcd195393d3f9ab8ad3f49522"},
|
||||||
{file = "onnxruntime-1.18.1-cp39-cp39-win32.whl", hash = "sha256:34657c78aa4e0b5145f9188b550ded3af626651b15017bf43d280d7e23dbf195"},
|
{file = "onnxruntime-1.19.0-cp39-cp39-win32.whl", hash = "sha256:947febd48405afcf526e45ccff97ff23b15e530434705f734870d22ae7fcf236"},
|
||||||
{file = "onnxruntime-1.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:9c14fd97c3ddfa97da5feef595e2c73f14c2d0ec1d4ecbea99c8d96603c89589"},
|
{file = "onnxruntime-1.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:f60be47eff5ee77fd28a466b0fd41d7debc42a32179d1ddb21e05d6067d7b48b"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
coloredlogs = "*"
|
coloredlogs = "*"
|
||||||
flatbuffers = "*"
|
flatbuffers = "*"
|
||||||
numpy = ">=1.21.6,<2.0"
|
numpy = ">=1.21.6"
|
||||||
packaging = "*"
|
packaging = "*"
|
||||||
protobuf = "*"
|
protobuf = "*"
|
||||||
sympy = "*"
|
sympy = "*"
|
||||||
@@ -2082,62 +2082,68 @@ numpy = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "orjson"
|
name = "orjson"
|
||||||
version = "3.10.6"
|
version = "3.10.7"
|
||||||
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "orjson-3.10.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7"},
|
{file = "orjson-3.10.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:74f4544f5a6405b90da8ea724d15ac9c36da4d72a738c64685003337401f5c12"},
|
||||||
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365"},
|
{file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a566f22c28222b08875b18b0dfbf8a947e69df21a9ed5c51a6bf91cfb944ac"},
|
||||||
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eadc8fd310edb4bdbd333374f2c8fec6794bbbae99b592f448d8214a5e4050c0"},
|
{file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf6ba8ebc8ef5792e2337fb0419f8009729335bb400ece005606336b7fd7bab7"},
|
||||||
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61272a5aec2b2661f4fa2b37c907ce9701e821b2c1285d5c3ab0207ebd358d38"},
|
{file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac7cf6222b29fbda9e3a472b41e6a5538b48f2c8f99261eecd60aafbdb60690c"},
|
||||||
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57985ee7e91d6214c837936dc1608f40f330a6b88bb13f5a57ce5257807da143"},
|
{file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de817e2f5fc75a9e7dd350c4b0f54617b280e26d1631811a43e7e968fa71e3e9"},
|
||||||
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:633a3b31d9d7c9f02d49c4ab4d0a86065c4a6f6adc297d63d272e043472acab5"},
|
{file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:348bdd16b32556cf8d7257b17cf2bdb7ab7976af4af41ebe79f9796c218f7e91"},
|
||||||
{file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1c680b269d33ec444afe2bdc647c9eb73166fa47a16d9a75ee56a374f4a45f43"},
|
{file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:479fd0844ddc3ca77e0fd99644c7fe2de8e8be1efcd57705b5c92e5186e8a250"},
|
||||||
{file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f759503a97a6ace19e55461395ab0d618b5a117e8d0fbb20e70cfd68a47327f2"},
|
{file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fdf5197a21dd660cf19dfd2a3ce79574588f8f5e2dbf21bda9ee2d2b46924d84"},
|
||||||
{file = "orjson-3.10.6-cp310-none-win32.whl", hash = "sha256:95a0cce17f969fb5391762e5719575217bd10ac5a189d1979442ee54456393f3"},
|
{file = "orjson-3.10.7-cp310-none-win32.whl", hash = "sha256:d374d36726746c81a49f3ff8daa2898dccab6596864ebe43d50733275c629175"},
|
||||||
{file = "orjson-3.10.6-cp310-none-win_amd64.whl", hash = "sha256:df25d9271270ba2133cc88ee83c318372bdc0f2cd6f32e7a450809a111efc45c"},
|
{file = "orjson-3.10.7-cp310-none-win_amd64.whl", hash = "sha256:cb61938aec8b0ffb6eef484d480188a1777e67b05d58e41b435c74b9d84e0b9c"},
|
||||||
{file = "orjson-3.10.6-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b1ec490e10d2a77c345def52599311849fc063ae0e67cf4f84528073152bb2ba"},
|
{file = "orjson-3.10.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7db8539039698ddfb9a524b4dd19508256107568cdad24f3682d5773e60504a2"},
|
||||||
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d43d3feb8f19d07e9f01e5b9be4f28801cf7c60d0fa0d279951b18fae1932b"},
|
{file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:480f455222cb7a1dea35c57a67578848537d2602b46c464472c995297117fa09"},
|
||||||
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3045267e98fe749408eee1593a142e02357c5c99be0802185ef2170086a863"},
|
{file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a9c9b168b3a19e37fe2778c0003359f07822c90fdff8f98d9d2a91b3144d8e0"},
|
||||||
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c27bc6a28ae95923350ab382c57113abd38f3928af3c80be6f2ba7eb8d8db0b0"},
|
{file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8de062de550f63185e4c1c54151bdddfc5625e37daf0aa1e75d2a1293e3b7d9a"},
|
||||||
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d27456491ca79532d11e507cadca37fb8c9324a3976294f68fb1eff2dc6ced5a"},
|
{file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b0dd04483499d1de9c8f6203f8975caf17a6000b9c0c54630cef02e44ee624e"},
|
||||||
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05ac3d3916023745aa3b3b388e91b9166be1ca02b7c7e41045da6d12985685f0"},
|
{file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b58d3795dafa334fc8fd46f7c5dc013e6ad06fd5b9a4cc98cb1456e7d3558bd6"},
|
||||||
{file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1335d4ef59ab85cab66fe73fd7a4e881c298ee7f63ede918b7faa1b27cbe5212"},
|
{file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33cfb96c24034a878d83d1a9415799a73dc77480e6c40417e5dda0710d559ee6"},
|
||||||
{file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4bbc6d0af24c1575edc79994c20e1b29e6fb3c6a570371306db0993ecf144dc5"},
|
{file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e724cebe1fadc2b23c6f7415bad5ee6239e00a69f30ee423f319c6af70e2a5c0"},
|
||||||
{file = "orjson-3.10.6-cp311-none-win32.whl", hash = "sha256:450e39ab1f7694465060a0550b3f6d328d20297bf2e06aa947b97c21e5241fbd"},
|
{file = "orjson-3.10.7-cp311-none-win32.whl", hash = "sha256:82763b46053727a7168d29c772ed5c870fdae2f61aa8a25994c7984a19b1021f"},
|
||||||
{file = "orjson-3.10.6-cp311-none-win_amd64.whl", hash = "sha256:227df19441372610b20e05bdb906e1742ec2ad7a66ac8350dcfd29a63014a83b"},
|
{file = "orjson-3.10.7-cp311-none-win_amd64.whl", hash = "sha256:eb8d384a24778abf29afb8e41d68fdd9a156cf6e5390c04cc07bbc24b89e98b5"},
|
||||||
{file = "orjson-3.10.6-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ea2977b21f8d5d9b758bb3f344a75e55ca78e3ff85595d248eee813ae23ecdfb"},
|
{file = "orjson-3.10.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44a96f2d4c3af51bfac6bc4ef7b182aa33f2f054fd7f34cc0ee9a320d051d41f"},
|
||||||
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6f3d167d13a16ed263b52dbfedff52c962bfd3d270b46b7518365bcc2121eed"},
|
{file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ac14cd57df0572453543f8f2575e2d01ae9e790c21f57627803f5e79b0d3c3"},
|
||||||
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f710f346e4c44a4e8bdf23daa974faede58f83334289df80bc9cd12fe82573c7"},
|
{file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bdbb61dcc365dd9be94e8f7df91975edc9364d6a78c8f7adb69c1cdff318ec93"},
|
||||||
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7275664f84e027dcb1ad5200b8b18373e9c669b2a9ec33d410c40f5ccf4b257e"},
|
{file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b48b3db6bb6e0a08fa8c83b47bc169623f801e5cc4f24442ab2b6617da3b5313"},
|
||||||
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0943e4c701196b23c240b3d10ed8ecd674f03089198cf503105b474a4f77f21f"},
|
{file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23820a1563a1d386414fef15c249040042b8e5d07b40ab3fe3efbfbbcbcb8864"},
|
||||||
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:446dee5a491b5bc7d8f825d80d9637e7af43f86a331207b9c9610e2f93fee22a"},
|
{file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09"},
|
||||||
{file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:64c81456d2a050d380786413786b057983892db105516639cb5d3ee3c7fd5148"},
|
{file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d352ee8ac1926d6193f602cbe36b1643bbd1bbcb25e3c1a657a4390f3000c9a5"},
|
||||||
{file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34"},
|
{file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b"},
|
||||||
{file = "orjson-3.10.6-cp312-none-win32.whl", hash = "sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5"},
|
{file = "orjson-3.10.7-cp312-none-win32.whl", hash = "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb"},
|
||||||
{file = "orjson-3.10.6-cp312-none-win_amd64.whl", hash = "sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc"},
|
{file = "orjson-3.10.7-cp312-none-win_amd64.whl", hash = "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1"},
|
||||||
{file = "orjson-3.10.6-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:66680eae4c4e7fc193d91cfc1353ad6d01b4801ae9b5314f17e11ba55e934183"},
|
{file = "orjson-3.10.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149"},
|
||||||
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caff75b425db5ef8e8f23af93c80f072f97b4fb3afd4af44482905c9f588da28"},
|
{file = "orjson-3.10.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe"},
|
||||||
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3722fddb821b6036fd2a3c814f6bd9b57a89dc6337b9924ecd614ebce3271394"},
|
{file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c"},
|
||||||
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2c116072a8533f2fec435fde4d134610f806bdac20188c7bd2081f3e9e0133f"},
|
{file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad"},
|
||||||
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6eeb13218c8cf34c61912e9df2de2853f1d009de0e46ea09ccdf3d757896af0a"},
|
{file = "orjson-3.10.7-cp313-none-win32.whl", hash = "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2"},
|
||||||
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:965a916373382674e323c957d560b953d81d7a8603fbeee26f7b8248638bd48b"},
|
{file = "orjson-3.10.7-cp313-none-win_amd64.whl", hash = "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024"},
|
||||||
{file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03c95484d53ed8e479cade8628c9cea00fd9d67f5554764a1110e0d5aa2de96e"},
|
{file = "orjson-3.10.7-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6ea2b2258eff652c82652d5e0f02bd5e0463a6a52abb78e49ac288827aaa1469"},
|
||||||
{file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e060748a04cccf1e0a6f2358dffea9c080b849a4a68c28b1b907f272b5127e9b"},
|
{file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430ee4d85841e1483d487e7b81401785a5dfd69db5de01314538f31f8fbf7ee1"},
|
||||||
{file = "orjson-3.10.6-cp38-none-win32.whl", hash = "sha256:738dbe3ef909c4b019d69afc19caf6b5ed0e2f1c786b5d6215fbb7539246e4c6"},
|
{file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4b6146e439af4c2472c56f8540d799a67a81226e11992008cb47e1267a9b3225"},
|
||||||
{file = "orjson-3.10.6-cp38-none-win_amd64.whl", hash = "sha256:d40f839dddf6a7d77114fe6b8a70218556408c71d4d6e29413bb5f150a692ff7"},
|
{file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:084e537806b458911137f76097e53ce7bf5806dda33ddf6aaa66a028f8d43a23"},
|
||||||
{file = "orjson-3.10.6-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:697a35a083c4f834807a6232b3e62c8b280f7a44ad0b759fd4dce748951e70db"},
|
{file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829cf2195838e3f93b70fd3b4292156fc5e097aac3739859ac0dcc722b27ac0"},
|
||||||
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd502f96bf5ea9a61cbc0b2b5900d0dd68aa0da197179042bdd2be67e51a1e4b"},
|
{file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1193b2416cbad1a769f868b1749535d5da47626ac29445803dae7cc64b3f5c98"},
|
||||||
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f215789fb1667cdc874c1b8af6a84dc939fd802bf293a8334fce185c79cd359b"},
|
{file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4e6c3da13e5a57e4b3dca2de059f243ebec705857522f188f0180ae88badd354"},
|
||||||
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2debd8ddce948a8c0938c8c93ade191d2f4ba4649a54302a7da905a81f00b56"},
|
{file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c31008598424dfbe52ce8c5b47e0752dca918a4fdc4a2a32004efd9fab41d866"},
|
||||||
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5410111d7b6681d4b0d65e0f58a13be588d01b473822483f77f513c7f93bd3b2"},
|
{file = "orjson-3.10.7-cp38-none-win32.whl", hash = "sha256:7122a99831f9e7fe977dc45784d3b2edc821c172d545e6420c375e5a935f5a1c"},
|
||||||
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb1f28a137337fdc18384079fa5726810681055b32b92253fa15ae5656e1dddb"},
|
{file = "orjson-3.10.7-cp38-none-win_amd64.whl", hash = "sha256:a763bc0e58504cc803739e7df040685816145a6f3c8a589787084b54ebc9f16e"},
|
||||||
{file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bf2fbbce5fe7cd1aa177ea3eab2b8e6a6bc6e8592e4279ed3db2d62e57c0e1b2"},
|
{file = "orjson-3.10.7-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e76be12658a6fa376fcd331b1ea4e58f5a06fd0220653450f0d415b8fd0fbe20"},
|
||||||
{file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:79b9b9e33bd4c517445a62b90ca0cc279b0f1f3970655c3df9e608bc3f91741a"},
|
{file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed350d6978d28b92939bfeb1a0570c523f6170efc3f0a0ef1f1df287cd4f4960"},
|
||||||
{file = "orjson-3.10.6-cp39-none-win32.whl", hash = "sha256:30b0a09a2014e621b1adf66a4f705f0809358350a757508ee80209b2d8dae219"},
|
{file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:144888c76f8520e39bfa121b31fd637e18d4cc2f115727865fdf9fa325b10412"},
|
||||||
{file = "orjson-3.10.6-cp39-none-win_amd64.whl", hash = "sha256:49e3bc615652617d463069f91b867a4458114c5b104e13b7ae6872e5f79d0844"},
|
{file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09b2d92fd95ad2402188cf51573acde57eb269eddabaa60f69ea0d733e789fe9"},
|
||||||
{file = "orjson-3.10.6.tar.gz", hash = "sha256:e54b63d0a7c6c54a5f5f726bc93a2078111ef060fec4ecbf34c5db800ca3b3a7"},
|
{file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b24a579123fa884f3a3caadaed7b75eb5715ee2b17ab5c66ac97d29b18fe57f"},
|
||||||
|
{file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591bcfe7512353bd609875ab38050efe3d55e18934e2f18950c108334b4ff"},
|
||||||
|
{file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f4db56635b58cd1a200b0a23744ff44206ee6aa428185e2b6c4a65b3197abdcd"},
|
||||||
|
{file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0fa5886854673222618638c6df7718ea7fe2f3f2384c452c9ccedc70b4a510a5"},
|
||||||
|
{file = "orjson-3.10.7-cp39-none-win32.whl", hash = "sha256:8272527d08450ab16eb405f47e0f4ef0e5ff5981c3d82afe0efd25dcbef2bcd2"},
|
||||||
|
{file = "orjson-3.10.7-cp39-none-win_amd64.whl", hash = "sha256:974683d4618c0c7dbf4f69c95a979734bf183d0658611760017f6e70a145af58"},
|
||||||
|
{file = "orjson-3.10.7.tar.gz", hash = "sha256:75ef0640403f945f3a1f9f6400686560dbfb0fb5b16589ad62cd477043c4eee3"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2367,54 +2373,54 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "1.10.17"
|
version = "1.10.18"
|
||||||
description = "Data validation and settings management using python type hints"
|
description = "Data validation and settings management using python type hints"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fa51175313cc30097660b10eec8ca55ed08bfa07acbfe02f7a42f6c242e9a4b"},
|
{file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"},
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7e8988bb16988890c985bd2093df9dd731bfb9d5e0860db054c23034fab8f7a"},
|
{file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"},
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:371dcf1831f87c9e217e2b6a0c66842879a14873114ebb9d0861ab22e3b5bb1e"},
|
{file = "pydantic-1.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d9d9b87b50338b1b7de4ebf34fd29fdb0d219dc07ade29effc74d3d2609c62"},
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4866a1579c0c3ca2c40575398a24d805d4db6cb353ee74df75ddeee3c657f9a7"},
|
{file = "pydantic-1.10.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b661ce52c7b5e5f600c0c3c5839e71918346af2ef20062705ae76b5c16914cab"},
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:543da3c6914795b37785703ffc74ba4d660418620cc273490d42c53949eeeca6"},
|
{file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c20f682defc9ef81cd7eaa485879ab29a86a0ba58acf669a78ed868e72bb89e0"},
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7623b59876f49e61c2e283551cc3647616d2fbdc0b4d36d3d638aae8547ea681"},
|
{file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c5ae6b7c8483b1e0bf59e5f1843e4fd8fd405e11df7de217ee65b98eb5462861"},
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-win_amd64.whl", hash = "sha256:409b2b36d7d7d19cd8310b97a4ce6b1755ef8bd45b9a2ec5ec2b124db0a0d8f3"},
|
{file = "pydantic-1.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:74fe19dda960b193b0eb82c1f4d2c8e5e26918d9cda858cbf3f41dd28549cb70"},
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fa43f362b46741df8f201bf3e7dff3569fa92069bcc7b4a740dea3602e27ab7a"},
|
{file = "pydantic-1.10.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72fa46abace0a7743cc697dbb830a41ee84c9db8456e8d77a46d79b537efd7ec"},
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a72d2a5ff86a3075ed81ca031eac86923d44bc5d42e719d585a8eb547bf0c9b"},
|
{file = "pydantic-1.10.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef0fe7ad7cbdb5f372463d42e6ed4ca9c443a52ce544472d8842a0576d830da5"},
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4ad32aed3bf5eea5ca5decc3d1bbc3d0ec5d4fbcd72a03cdad849458decbc63"},
|
{file = "pydantic-1.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00e63104346145389b8e8f500bc6a241e729feaf0559b88b8aa513dd2065481"},
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb4e741782e236ee7dc1fb11ad94dc56aabaf02d21df0e79e0c21fe07c95741"},
|
{file = "pydantic-1.10.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae6fa2008e1443c46b7b3a5eb03800121868d5ab6bc7cda20b5df3e133cde8b3"},
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d2f89a719411cb234105735a520b7c077158a81e0fe1cb05a79c01fc5eb59d3c"},
|
{file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9f463abafdc92635da4b38807f5b9972276be7c8c5121989768549fceb8d2588"},
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db3b48d9283d80a314f7a682f7acae8422386de659fffaba454b77a083c3937d"},
|
{file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3445426da503c7e40baccefb2b2989a0c5ce6b163679dd75f55493b460f05a8f"},
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-win_amd64.whl", hash = "sha256:9c803a5113cfab7bbb912f75faa4fc1e4acff43e452c82560349fff64f852e1b"},
|
{file = "pydantic-1.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:467a14ee2183bc9c902579bb2f04c3d3dac00eff52e252850509a562255b2a33"},
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:820ae12a390c9cbb26bb44913c87fa2ff431a029a785642c1ff11fed0a095fcb"},
|
{file = "pydantic-1.10.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:efbc8a7f9cb5fe26122acba1852d8dcd1e125e723727c59dcd244da7bdaa54f2"},
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1e51d1af306641b7d1574d6d3307eaa10a4991542ca324f0feb134fee259815"},
|
{file = "pydantic-1.10.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24a4a159d0f7a8e26bf6463b0d3d60871d6a52eac5bb6a07a7df85c806f4c048"},
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e53fb834aae96e7b0dadd6e92c66e7dd9cdf08965340ed04c16813102a47fab"},
|
{file = "pydantic-1.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b74be007703547dc52e3c37344d130a7bfacca7df112a9e5ceeb840a9ce195c7"},
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2495309b1266e81d259a570dd199916ff34f7f51f1b549a0d37a6d9b17b4dc"},
|
{file = "pydantic-1.10.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcb20d4cb355195c75000a49bb4a31d75e4295200df620f454bbc6bdf60ca890"},
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:098ad8de840c92ea586bf8efd9e2e90c6339d33ab5c1cfbb85be66e4ecf8213f"},
|
{file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46f379b8cb8a3585e3f61bf9ae7d606c70d133943f339d38b76e041ec234953f"},
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:525bbef620dac93c430d5d6bdbc91bdb5521698d434adf4434a7ef6ffd5c4b7f"},
|
{file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbfbca662ed3729204090c4d09ee4beeecc1a7ecba5a159a94b5a4eb24e3759a"},
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-win_amd64.whl", hash = "sha256:6654028d1144df451e1da69a670083c27117d493f16cf83da81e1e50edce72ad"},
|
{file = "pydantic-1.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:c6d0a9f9eccaf7f438671a64acf654ef0d045466e63f9f68a579e2383b63f357"},
|
||||||
{file = "pydantic-1.10.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c87cedb4680d1614f1d59d13fea353faf3afd41ba5c906a266f3f2e8c245d655"},
|
{file = "pydantic-1.10.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d5492dbf953d7d849751917e3b2433fb26010d977aa7a0765c37425a4026ff1"},
|
||||||
{file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11289fa895bcbc8f18704efa1d8020bb9a86314da435348f59745473eb042e6b"},
|
{file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe734914977eed33033b70bfc097e1baaffb589517863955430bf2e0846ac30f"},
|
||||||
{file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94833612d6fd18b57c359a127cbfd932d9150c1b72fea7c86ab58c2a77edd7c7"},
|
{file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15fdbe568beaca9aacfccd5ceadfb5f1a235087a127e8af5e48df9d8a45ae85c"},
|
||||||
{file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d4ecb515fa7cb0e46e163ecd9d52f9147ba57bc3633dca0e586cdb7a232db9e3"},
|
{file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c3e742f62198c9eb9201781fbebe64533a3bbf6a76a91b8d438d62b813079dbc"},
|
||||||
{file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7017971ffa7fd7808146880aa41b266e06c1e6e12261768a28b8b41ba55c8076"},
|
{file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19a3bd00b9dafc2cd7250d94d5b578edf7a0bd7daf102617153ff9a8fa37871c"},
|
||||||
{file = "pydantic-1.10.17-cp37-cp37m-win_amd64.whl", hash = "sha256:e840e6b2026920fc3f250ea8ebfdedf6ea7a25b77bf04c6576178e681942ae0f"},
|
{file = "pydantic-1.10.18-cp37-cp37m-win_amd64.whl", hash = "sha256:2ce3fcf75b2bae99aa31bd4968de0474ebe8c8258a0110903478bd83dfee4e3b"},
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bfbb18b616abc4df70591b8c1ff1b3eabd234ddcddb86b7cac82657ab9017e33"},
|
{file = "pydantic-1.10.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:335a32d72c51a313b33fa3a9b0fe283503272ef6467910338e123f90925f0f03"},
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebb249096d873593e014535ab07145498957091aa6ae92759a32d40cb9998e2e"},
|
{file = "pydantic-1.10.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:34a3613c7edb8c6fa578e58e9abe3c0f5e7430e0fc34a65a415a1683b9c32d9a"},
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c209af63ccd7b22fba94b9024e8b7fd07feffee0001efae50dd99316b27768"},
|
{file = "pydantic-1.10.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ee4e6ca1d9616797fa2e9c0bfb8815912c7d67aca96f77428e316741082a1b"},
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b40c9e13a0b61583e5599e7950490c700297b4a375b55b2b592774332798b7"},
|
{file = "pydantic-1.10.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23e8ec1ce4e57b4f441fc91e3c12adba023fedd06868445a5b5f1d48f0ab3682"},
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c31d281c7485223caf6474fc2b7cf21456289dbaa31401844069b77160cab9c7"},
|
{file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:44ae8a3e35a54d2e8fa88ed65e1b08967a9ef8c320819a969bfa09ce5528fafe"},
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae5184e99a060a5c80010a2d53c99aee76a3b0ad683d493e5f0620b5d86eeb75"},
|
{file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5389eb3b48a72da28c6e061a247ab224381435256eb541e175798483368fdd3"},
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-win_amd64.whl", hash = "sha256:ad1e33dc6b9787a6f0f3fd132859aa75626528b49cc1f9e429cdacb2608ad5f0"},
|
{file = "pydantic-1.10.18-cp38-cp38-win_amd64.whl", hash = "sha256:069b9c9fc645474d5ea3653788b544a9e0ccd3dca3ad8c900c4c6eac844b4620"},
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e17c0ee7192e54a10943f245dc79e36d9fe282418ea05b886e1c666063a7b54"},
|
{file = "pydantic-1.10.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80b982d42515632eb51f60fa1d217dfe0729f008e81a82d1544cc392e0a50ddf"},
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cafb9c938f61d1b182dfc7d44a7021326547b7b9cf695db5b68ec7b590214773"},
|
{file = "pydantic-1.10.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aad8771ec8dbf9139b01b56f66386537c6fe4e76c8f7a47c10261b69ad25c2c9"},
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ef534e3c22e5abbdbdd6f66b6ea9dac3ca3e34c5c632894f8625d13d084cbe"},
|
{file = "pydantic-1.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941a2eb0a1509bd7f31e355912eb33b698eb0051730b2eaf9e70e2e1589cae1d"},
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d96b8799ae3d782df7ec9615cb59fc32c32e1ed6afa1b231b0595f6516e8ab"},
|
{file = "pydantic-1.10.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65f7361a09b07915a98efd17fdec23103307a54db2000bb92095457ca758d485"},
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ab2f976336808fd5d539fdc26eb51f9aafc1f4b638e212ef6b6f05e753c8011d"},
|
{file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6951f3f47cb5ca4da536ab161ac0163cab31417d20c54c6de5ddcab8bc813c3f"},
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8ad363330557beac73159acfbeed220d5f1bfcd6b930302a987a375e02f74fd"},
|
{file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a4c5eec138a9b52c67f664c7d51d4c7234c5ad65dd8aacd919fb47445a62c86"},
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-win_amd64.whl", hash = "sha256:48db882e48575ce4b39659558b2f9f37c25b8d348e37a2b4e32971dd5a7d6227"},
|
{file = "pydantic-1.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:49e26c51ca854286bffc22b69787a8d4063a62bf7d83dc21d44d2ff426108518"},
|
||||||
{file = "pydantic-1.10.17-py3-none-any.whl", hash = "sha256:e41b5b973e5c64f674b3b4720286ded184dcc26a691dd55f34391c62c6934688"},
|
{file = "pydantic-1.10.18-py3-none-any.whl", hash = "sha256:06a189b81ffc52746ec9c8c007f16e5167c8b0a696e1a726369327e3db7b2a82"},
|
||||||
{file = "pydantic-1.10.17.tar.gz", hash = "sha256:f434160fb14b353caf634149baaf847206406471ba70e64657c1e8330277a991"},
|
{file = "pydantic-1.10.18.tar.gz", hash = "sha256:baebdff1907d1d96a139c25136a9bb7d17e118f133a76a2ef3b845e831e3403a"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -2488,17 +2494,17 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest-asyncio"
|
name = "pytest-asyncio"
|
||||||
version = "0.23.8"
|
version = "0.24.0"
|
||||||
description = "Pytest support for asyncio"
|
description = "Pytest support for asyncio"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"},
|
{file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"},
|
||||||
{file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"},
|
{file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
pytest = ">=7.0.0,<9"
|
pytest = ">=8.2,<9"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
|
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
|
||||||
@@ -2506,13 +2512,13 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest-cov"
|
name = "pytest-cov"
|
||||||
version = "4.1.0"
|
version = "5.0.0"
|
||||||
description = "Pytest plugin for measuring coverage."
|
description = "Pytest plugin for measuring coverage."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
|
{file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"},
|
||||||
{file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
|
{file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -2520,7 +2526,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]}
|
|||||||
pytest = ">=4.6"
|
pytest = ">=4.6"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
|
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest-mock"
|
name = "pytest-mock"
|
||||||
@@ -2827,29 +2833,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.5.6"
|
version = "0.6.2"
|
||||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "ruff-0.5.6-py3-none-linux_armv6l.whl", hash = "sha256:a0ef5930799a05522985b9cec8290b185952f3fcd86c1772c3bdbd732667fdcd"},
|
{file = "ruff-0.6.2-py3-none-linux_armv6l.whl", hash = "sha256:5c8cbc6252deb3ea840ad6a20b0f8583caab0c5ef4f9cca21adc5a92b8f79f3c"},
|
||||||
{file = "ruff-0.5.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b652dc14f6ef5d1552821e006f747802cc32d98d5509349e168f6bf0ee9f8f42"},
|
{file = "ruff-0.6.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17002fe241e76544448a8e1e6118abecbe8cd10cf68fde635dad480dba594570"},
|
||||||
{file = "ruff-0.5.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:80521b88d26a45e871f31e4b88938fd87db7011bb961d8afd2664982dfc3641a"},
|
{file = "ruff-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3dbeac76ed13456f8158b8f4fe087bf87882e645c8e8b606dd17b0b66c2c1158"},
|
||||||
{file = "ruff-0.5.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9bc8f328a9f1309ae80e4d392836e7dbc77303b38ed4a7112699e63d3b066ab"},
|
{file = "ruff-0.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:094600ee88cda325988d3f54e3588c46de5c18dae09d683ace278b11f9d4d534"},
|
||||||
{file = "ruff-0.5.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d394940f61f7720ad371ddedf14722ee1d6250fd8d020f5ea5a86e7be217daf"},
|
{file = "ruff-0.6.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:316d418fe258c036ba05fbf7dfc1f7d3d4096db63431546163b472285668132b"},
|
||||||
{file = "ruff-0.5.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111a99cdb02f69ddb2571e2756e017a1496c2c3a2aeefe7b988ddab38b416d36"},
|
{file = "ruff-0.6.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d72b8b3abf8a2d51b7b9944a41307d2f442558ccb3859bbd87e6ae9be1694a5d"},
|
||||||
{file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e395daba77a79f6dc0d07311f94cc0560375ca20c06f354c7c99af3bf4560c5d"},
|
{file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2aed7e243be68487aa8982e91c6e260982d00da3f38955873aecd5a9204b1d66"},
|
||||||
{file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c476acb43c3c51e3c614a2e878ee1589655fa02dab19fe2db0423a06d6a5b1b6"},
|
{file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d371f7fc9cec83497fe7cf5eaf5b76e22a8efce463de5f775a1826197feb9df8"},
|
||||||
{file = "ruff-0.5.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2ff8003f5252fd68425fd53d27c1f08b201d7ed714bb31a55c9ac1d4c13e2eb"},
|
{file = "ruff-0.6.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f310d63af08f583363dfb844ba8f9417b558199c58a5999215082036d795a1"},
|
||||||
{file = "ruff-0.5.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c94e084ba3eaa80c2172918c2ca2eb2230c3f15925f4ed8b6297260c6ef179ad"},
|
{file = "ruff-0.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7db6880c53c56addb8638fe444818183385ec85eeada1d48fc5abe045301b2f1"},
|
||||||
{file = "ruff-0.5.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f77c1c3aa0669fb230b06fb24ffa3e879391a3ba3f15e3d633a752da5a3e670"},
|
{file = "ruff-0.6.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1175d39faadd9a50718f478d23bfc1d4da5743f1ab56af81a2b6caf0a2394f23"},
|
||||||
{file = "ruff-0.5.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f908148c93c02873210a52cad75a6eda856b2cbb72250370ce3afef6fb99b1ed"},
|
{file = "ruff-0.6.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939f9c86d51635fe486585389f54582f0d65b8238e08c327c1534844b3bb9a"},
|
||||||
{file = "ruff-0.5.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:563a7ae61ad284187d3071d9041c08019975693ff655438d8d4be26e492760bd"},
|
{file = "ruff-0.6.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0d62ca91219f906caf9b187dea50d17353f15ec9bb15aae4a606cd697b49b4c"},
|
||||||
{file = "ruff-0.5.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:94fe60869bfbf0521e04fd62b74cbca21cbc5beb67cbb75ab33fe8c174f54414"},
|
{file = "ruff-0.6.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7438a7288f9d67ed3c8ce4d059e67f7ed65e9fe3aa2ab6f5b4b3610e57e3cb56"},
|
||||||
{file = "ruff-0.5.6-py3-none-win32.whl", hash = "sha256:e6a584c1de6f8591c2570e171cc7ce482bb983d49c70ddf014393cd39e9dfaed"},
|
{file = "ruff-0.6.2-py3-none-win32.whl", hash = "sha256:279d5f7d86696df5f9549b56b9b6a7f6c72961b619022b5b7999b15db392a4da"},
|
||||||
{file = "ruff-0.5.6-py3-none-win_amd64.whl", hash = "sha256:d7fe7dccb1a89dc66785d7aa0ac283b2269712d8ed19c63af908fdccca5ccc1a"},
|
{file = "ruff-0.6.2-py3-none-win_amd64.whl", hash = "sha256:d9f3469c7dd43cd22eb1c3fc16926fb8258d50cb1b216658a07be95dd117b0f2"},
|
||||||
{file = "ruff-0.5.6-py3-none-win_arm64.whl", hash = "sha256:57c6c0dd997b31b536bff49b9eee5ed3194d60605a4427f735eeb1f9c1b8d264"},
|
{file = "ruff-0.6.2-py3-none-win_arm64.whl", hash = "sha256:f28fcd2cd0e02bdf739297516d5643a945cc7caf09bd9bcb4d932540a5ea4fa9"},
|
||||||
{file = "ruff-0.5.6.tar.gz", hash = "sha256:07c9e3c2a8e1fe377dd460371c3462671a728c981c3205a5217291422209f642"},
|
{file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3087,111 +3093,111 @@ all = ["defusedxml", "fsspec", "imagecodecs (>=2023.8.12)", "lxml", "matplotlib"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokenizers"
|
name = "tokenizers"
|
||||||
version = "0.19.1"
|
version = "0.20.0"
|
||||||
description = ""
|
description = ""
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "tokenizers-0.19.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:952078130b3d101e05ecfc7fc3640282d74ed26bcf691400f872563fca15ac97"},
|
{file = "tokenizers-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6cff5c5e37c41bc5faa519d6f3df0679e4b37da54ea1f42121719c5e2b4905c0"},
|
||||||
{file = "tokenizers-0.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82c8b8063de6c0468f08e82c4e198763e7b97aabfe573fd4cf7b33930ca4df77"},
|
{file = "tokenizers-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:62a56bf75c27443432456f4ca5ca055befa95e25be8a28141cc495cac8ae4d6d"},
|
||||||
{file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f03727225feaf340ceeb7e00604825addef622d551cbd46b7b775ac834c1e1c4"},
|
{file = "tokenizers-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68cc7de6a63f09c4a86909c2597b995aa66e19df852a23aea894929c74369929"},
|
||||||
{file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:453e4422efdfc9c6b6bf2eae00d5e323f263fff62b29a8c9cd526c5003f3f642"},
|
{file = "tokenizers-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:053c37ecee482cc958fdee53af3c6534286a86f5d35aac476f7c246830e53ae5"},
|
||||||
{file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:02e81bf089ebf0e7f4df34fa0207519f07e66d8491d963618252f2e0729e0b46"},
|
{file = "tokenizers-0.20.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d7074aaabc151a6363fa03db5493fc95b423b2a1874456783989e96d541c7b6"},
|
||||||
{file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b07c538ba956843833fee1190cf769c60dc62e1cf934ed50d77d5502194d63b1"},
|
{file = "tokenizers-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a11435780f2acd89e8fefe5e81cecf01776f6edb9b3ac95bcb76baee76b30b90"},
|
||||||
{file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28cab1582e0eec38b1f38c1c1fb2e56bce5dc180acb1724574fc5f47da2a4fe"},
|
{file = "tokenizers-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a81cd2712973b007d84268d45fc3f6f90a79c31dfe7f1925e6732f8d2959987"},
|
||||||
{file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b01afb7193d47439f091cd8f070a1ced347ad0f9144952a30a41836902fe09e"},
|
{file = "tokenizers-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7dfd796ab9d909f76fb93080e1c7c8309f196ecb316eb130718cd5e34231c69"},
|
||||||
{file = "tokenizers-0.19.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7fb297edec6c6841ab2e4e8f357209519188e4a59b557ea4fafcf4691d1b4c98"},
|
{file = "tokenizers-0.20.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8029ad2aa8cb00605c9374566034c1cc1b15130713e0eb5afcef6cface8255c9"},
|
||||||
{file = "tokenizers-0.19.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2e8a3dd055e515df7054378dc9d6fa8c8c34e1f32777fb9a01fea81496b3f9d3"},
|
{file = "tokenizers-0.20.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ca4d54260ebe97d59dfa9a30baa20d0c4dd9137d99a8801700055c561145c24e"},
|
||||||
{file = "tokenizers-0.19.1-cp310-none-win32.whl", hash = "sha256:7ff898780a155ea053f5d934925f3902be2ed1f4d916461e1a93019cc7250837"},
|
{file = "tokenizers-0.20.0-cp310-none-win32.whl", hash = "sha256:95ee16b57cec11b86a7940174ec5197d506439b0f415ab3859f254b1dffe9df0"},
|
||||||
{file = "tokenizers-0.19.1-cp310-none-win_amd64.whl", hash = "sha256:bea6f9947e9419c2fda21ae6c32871e3d398cba549b93f4a65a2d369662d9403"},
|
{file = "tokenizers-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:0a61a11e93eeadbf02aea082ffc75241c4198e0608bbbac4f65a9026851dcf37"},
|
||||||
{file = "tokenizers-0.19.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5c88d1481f1882c2e53e6bb06491e474e420d9ac7bdff172610c4f9ad3898059"},
|
{file = "tokenizers-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6636b798b3c4d6c9b1af1a918bd07c867808e5a21c64324e95318a237e6366c3"},
|
||||||
{file = "tokenizers-0.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddf672ed719b4ed82b51499100f5417d7d9f6fb05a65e232249268f35de5ed14"},
|
{file = "tokenizers-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ec603e42eaf499ffd58b9258162add948717cf21372458132f14e13a6bc7172"},
|
||||||
{file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dadc509cc8a9fe460bd274c0e16ac4184d0958117cf026e0ea8b32b438171594"},
|
{file = "tokenizers-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cce124264903a8ea6f8f48e1cc7669e5ef638c18bd4ab0a88769d5f92debdf7f"},
|
||||||
{file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfedf31824ca4915b511b03441784ff640378191918264268e6923da48104acc"},
|
{file = "tokenizers-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07bbeba0231cf8de07aa6b9e33e9779ff103d47042eeeb859a8c432e3292fb98"},
|
||||||
{file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac11016d0a04aa6487b1513a3a36e7bee7eec0e5d30057c9c0408067345c48d2"},
|
{file = "tokenizers-0.20.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:06c0ca8397b35d38b83a44a9c6929790c1692957d88541df061cb34d82ebbf08"},
|
||||||
{file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76951121890fea8330d3a0df9a954b3f2a37e3ec20e5b0530e9a0044ca2e11fe"},
|
{file = "tokenizers-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca6557ac3b83d912dfbb1f70ab56bd4b0594043916688e906ede09f42e192401"},
|
||||||
{file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b342d2ce8fc8d00f376af068e3274e2e8649562e3bc6ae4a67784ded6b99428d"},
|
{file = "tokenizers-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a5ad94c9e80ac6098328bee2e3264dbced4c6faa34429994d473f795ec58ef4"},
|
||||||
{file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d16ff18907f4909dca9b076b9c2d899114dd6abceeb074eca0c93e2353f943aa"},
|
{file = "tokenizers-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b5c7f906ee6bec30a9dc20268a8b80f3b9584de1c9f051671cb057dc6ce28f6"},
|
||||||
{file = "tokenizers-0.19.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:706a37cc5332f85f26efbe2bdc9ef8a9b372b77e4645331a405073e4b3a8c1c6"},
|
{file = "tokenizers-0.20.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:31e087e9ee1b8f075b002bfee257e858dc695f955b43903e1bb4aa9f170e37fe"},
|
||||||
{file = "tokenizers-0.19.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16baac68651701364b0289979ecec728546133e8e8fe38f66fe48ad07996b88b"},
|
{file = "tokenizers-0.20.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c3124fb6f3346cb3d8d775375d3b429bf4dcfc24f739822702009d20a4297990"},
|
||||||
{file = "tokenizers-0.19.1-cp311-none-win32.whl", hash = "sha256:9ed240c56b4403e22b9584ee37d87b8bfa14865134e3e1c3fb4b2c42fafd3256"},
|
{file = "tokenizers-0.20.0-cp311-none-win32.whl", hash = "sha256:a4bb8b40ba9eefa621fdcabf04a74aa6038ae3be0c614c6458bd91a4697a452f"},
|
||||||
{file = "tokenizers-0.19.1-cp311-none-win_amd64.whl", hash = "sha256:ad57d59341710b94a7d9dbea13f5c1e7d76fd8d9bcd944a7a6ab0b0da6e0cc66"},
|
{file = "tokenizers-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:2b709d371f1fe60a28ef0c5c67815952d455ca7f34dbe7197eaaed3cc54b658e"},
|
||||||
{file = "tokenizers-0.19.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:621d670e1b1c281a1c9698ed89451395d318802ff88d1fc1accff0867a06f153"},
|
{file = "tokenizers-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:15c81a17d0d66f4987c6ca16f4bea7ec253b8c7ed1bb00fdc5d038b1bb56e714"},
|
||||||
{file = "tokenizers-0.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d924204a3dbe50b75630bd16f821ebda6a5f729928df30f582fb5aade90c818a"},
|
{file = "tokenizers-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a531cdf1fb6dc41c984c785a3b299cb0586de0b35683842a3afbb1e5207f910"},
|
||||||
{file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4f3fefdc0446b1a1e6d81cd4c07088ac015665d2e812f6dbba4a06267d1a2c95"},
|
{file = "tokenizers-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06caabeb4587f8404e0cd9d40f458e9cba3e815c8155a38e579a74ff3e2a4301"},
|
||||||
{file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9620b78e0b2d52ef07b0d428323fb34e8ea1219c5eac98c2596311f20f1f9266"},
|
{file = "tokenizers-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8768f964f23f5b9f50546c0369c75ab3262de926983888bbe8b98be05392a79c"},
|
||||||
{file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04ce49e82d100594715ac1b2ce87d1a36e61891a91de774755f743babcd0dd52"},
|
{file = "tokenizers-0.20.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:626403860152c816f97b649fd279bd622c3d417678c93b4b1a8909b6380b69a8"},
|
||||||
{file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5c2ff13d157afe413bf7e25789879dd463e5a4abfb529a2d8f8473d8042e28f"},
|
{file = "tokenizers-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c1b88fa9e5ff062326f4bf82681da5a96fca7104d921a6bd7b1e6fcf224af26"},
|
||||||
{file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3174c76efd9d08f836bfccaca7cfec3f4d1c0a4cf3acbc7236ad577cc423c840"},
|
{file = "tokenizers-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7e559436a07dc547f22ce1101f26d8b2fad387e28ec8e7e1e3b11695d681d8"},
|
||||||
{file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9d5b6c0e7a1e979bec10ff960fae925e947aab95619a6fdb4c1d8ff3708ce3"},
|
{file = "tokenizers-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48afb75e50449848964e4a67b0da01261dd3aa8df8daecf10db8fd7f5b076eb"},
|
||||||
{file = "tokenizers-0.19.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a179856d1caee06577220ebcfa332af046d576fb73454b8f4d4b0ba8324423ea"},
|
{file = "tokenizers-0.20.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:baf5d0e1ff44710a95eefc196dd87666ffc609fd447c5e5b68272a7c3d342a1d"},
|
||||||
{file = "tokenizers-0.19.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:952b80dac1a6492170f8c2429bd11fcaa14377e097d12a1dbe0ef2fb2241e16c"},
|
{file = "tokenizers-0.20.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e5e56df0e8ed23ba60ae3848c3f069a0710c4b197218fe4f89e27eba38510768"},
|
||||||
{file = "tokenizers-0.19.1-cp312-none-win32.whl", hash = "sha256:01d62812454c188306755c94755465505836fd616f75067abcae529c35edeb57"},
|
{file = "tokenizers-0.20.0-cp312-none-win32.whl", hash = "sha256:ec53e5ecc142a82432f9c6c677dbbe5a2bfee92b8abf409a9ecb0d425ee0ce75"},
|
||||||
{file = "tokenizers-0.19.1-cp312-none-win_amd64.whl", hash = "sha256:b70bfbe3a82d3e3fb2a5e9b22a39f8d1740c96c68b6ace0086b39074f08ab89a"},
|
{file = "tokenizers-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:f18661ece72e39c0dfaa174d6223248a15b457dbd4b0fc07809b8e6d3ca1a234"},
|
||||||
{file = "tokenizers-0.19.1-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:bb9dfe7dae85bc6119d705a76dc068c062b8b575abe3595e3c6276480e67e3f1"},
|
{file = "tokenizers-0.20.0-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:f7065b1084d8d1a03dc89d9aad69bcbc8415d4bc123c367063eb32958cd85054"},
|
||||||
{file = "tokenizers-0.19.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:1f0360cbea28ea99944ac089c00de7b2e3e1c58f479fb8613b6d8d511ce98267"},
|
{file = "tokenizers-0.20.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e5d4069e4714e3f7ba0a4d3d44f9d84a432cd4e4aa85c3d7dd1f51440f12e4a1"},
|
||||||
{file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:71e3ec71f0e78780851fef28c2a9babe20270404c921b756d7c532d280349214"},
|
{file = "tokenizers-0.20.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:799b808529e54b7e1a36350bda2aeb470e8390e484d3e98c10395cee61d4e3c6"},
|
||||||
{file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b82931fa619dbad979c0ee8e54dd5278acc418209cc897e42fac041f5366d626"},
|
{file = "tokenizers-0.20.0-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f9baa027cc8a281ad5f7725a93c204d7a46986f88edbe8ef7357f40a23fb9c7"},
|
||||||
{file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e8ff5b90eabdcdaa19af697885f70fe0b714ce16709cf43d4952f1f85299e73a"},
|
{file = "tokenizers-0.20.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:010ec7f3f7a96adc4c2a34a3ada41fa14b4b936b5628b4ff7b33791258646c6b"},
|
||||||
{file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e742d76ad84acbdb1a8e4694f915fe59ff6edc381c97d6dfdd054954e3478ad4"},
|
{file = "tokenizers-0.20.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98d88f06155335b14fd78e32ee28ca5b2eb30fced4614e06eb14ae5f7fba24ed"},
|
||||||
{file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8c5d59d7b59885eab559d5bc082b2985555a54cda04dda4c65528d90ad252ad"},
|
{file = "tokenizers-0.20.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e13eb000ef540c2280758d1b9cfa5fe424b0424ae4458f440e6340a4f18b2638"},
|
||||||
{file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b2da5c32ed869bebd990c9420df49813709e953674c0722ff471a116d97b22d"},
|
{file = "tokenizers-0.20.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fab3cf066ff426f7e6d70435dc28a9ff01b2747be83810e397cba106f39430b0"},
|
||||||
{file = "tokenizers-0.19.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:638e43936cc8b2cbb9f9d8dde0fe5e7e30766a3318d2342999ae27f68fdc9bd6"},
|
{file = "tokenizers-0.20.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:39fa3761b30a89368f322e5daf4130dce8495b79ad831f370449cdacfb0c0d37"},
|
||||||
{file = "tokenizers-0.19.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:78e769eb3b2c79687d9cb0f89ef77223e8e279b75c0a968e637ca7043a84463f"},
|
{file = "tokenizers-0.20.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c8da0fba4d179ddf2607821575998df3c294aa59aa8df5a6646dc64bc7352bce"},
|
||||||
{file = "tokenizers-0.19.1-cp37-none-win32.whl", hash = "sha256:72791f9bb1ca78e3ae525d4782e85272c63faaef9940d92142aa3eb79f3407a3"},
|
{file = "tokenizers-0.20.0-cp37-none-win32.whl", hash = "sha256:fada996d6da8cf213f6e3c91c12297ad4f6cdf7a85c2fadcd05ec32fa6846fcd"},
|
||||||
{file = "tokenizers-0.19.1-cp37-none-win_amd64.whl", hash = "sha256:f3bbb7a0c5fcb692950b041ae11067ac54826204318922da754f908d95619fbc"},
|
{file = "tokenizers-0.20.0-cp37-none-win_amd64.whl", hash = "sha256:7d29aad702279e0760c265fcae832e89349078e3418dd329732d4503259fd6bd"},
|
||||||
{file = "tokenizers-0.19.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:07f9295349bbbcedae8cefdbcfa7f686aa420be8aca5d4f7d1ae6016c128c0c5"},
|
{file = "tokenizers-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:099c68207f3ef0227ecb6f80ab98ea74de559f7b124adc7b17778af0250ee90a"},
|
||||||
{file = "tokenizers-0.19.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:10a707cc6c4b6b183ec5dbfc5c34f3064e18cf62b4a938cb41699e33a99e03c1"},
|
{file = "tokenizers-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:68012d8a8cddb2eab3880870d7e2086cb359c7f7a2b03f5795044f5abff4e850"},
|
||||||
{file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6309271f57b397aa0aff0cbbe632ca9d70430839ca3178bf0f06f825924eca22"},
|
{file = "tokenizers-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9253bdd209c6aee168deca7d0e780581bf303e0058f268f9bb06859379de19b6"},
|
||||||
{file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ad23d37d68cf00d54af184586d79b84075ada495e7c5c0f601f051b162112dc"},
|
{file = "tokenizers-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f868600ddbcb0545905ed075eb7218a0756bf6c09dae7528ea2f8436ebd2c93"},
|
||||||
{file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:427c4f0f3df9109314d4f75b8d1f65d9477033e67ffaec4bca53293d3aca286d"},
|
{file = "tokenizers-0.20.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9643d9c8c5f99b6aba43fd10034f77cc6c22c31f496d2f0ee183047d948fa0"},
|
||||||
{file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e83a31c9cf181a0a3ef0abad2b5f6b43399faf5da7e696196ddd110d332519ee"},
|
{file = "tokenizers-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c375c6a889aeab44734028bc65cc070acf93ccb0f9368be42b67a98e1063d3f6"},
|
||||||
{file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c27b99889bd58b7e301468c0838c5ed75e60c66df0d4db80c08f43462f82e0d3"},
|
{file = "tokenizers-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e359f852328e254f070bbd09a19a568421d23388f04aad9f2fb7da7704c7228d"},
|
||||||
{file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bac0b0eb952412b0b196ca7a40e7dce4ed6f6926489313414010f2e6b9ec2adf"},
|
{file = "tokenizers-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d98b01a309d4387f3b1c1dd68a8b8136af50376cf146c1b7e8d8ead217a5be4b"},
|
||||||
{file = "tokenizers-0.19.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8a6298bde623725ca31c9035a04bf2ef63208d266acd2bed8c2cb7d2b7d53ce6"},
|
{file = "tokenizers-0.20.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:459f7537119554c2899067dec1ac74a00d02beef6558f4ee2e99513bf6d568af"},
|
||||||
{file = "tokenizers-0.19.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:08a44864e42fa6d7d76d7be4bec62c9982f6f6248b4aa42f7302aa01e0abfd26"},
|
{file = "tokenizers-0.20.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:392b87ec89452628c045c9f2a88bc2a827f4c79e7d84bc3b72752b74c2581f70"},
|
||||||
{file = "tokenizers-0.19.1-cp38-none-win32.whl", hash = "sha256:1de5bc8652252d9357a666e609cb1453d4f8e160eb1fb2830ee369dd658e8975"},
|
{file = "tokenizers-0.20.0-cp38-none-win32.whl", hash = "sha256:55a393f893d2ed4dd95a1553c2e42d4d4086878266f437b03590d3f81984c4fe"},
|
||||||
{file = "tokenizers-0.19.1-cp38-none-win_amd64.whl", hash = "sha256:0bcce02bf1ad9882345b34d5bd25ed4949a480cf0e656bbd468f4d8986f7a3f1"},
|
{file = "tokenizers-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:30ffe33c5c2f2aab8e9a3340d0110dd9f7ace7eec7362e20a697802306bd8068"},
|
||||||
{file = "tokenizers-0.19.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0b9394bd204842a2a1fd37fe29935353742be4a3460b6ccbaefa93f58a8df43d"},
|
{file = "tokenizers-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:aa2d4a6fed2a7e3f860c7fc9d48764bb30f2649d83915d66150d6340e06742b8"},
|
||||||
{file = "tokenizers-0.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4692ab92f91b87769d950ca14dbb61f8a9ef36a62f94bad6c82cc84a51f76f6a"},
|
{file = "tokenizers-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b5ef0f814084a897e9071fc4a868595f018c5c92889197bdc4bf19018769b148"},
|
||||||
{file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6258c2ef6f06259f70a682491c78561d492e885adeaf9f64f5389f78aa49a051"},
|
{file = "tokenizers-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1e1b791e8c3bf4c4f265f180dadaff1c957bf27129e16fdd5e5d43c2d3762c"},
|
||||||
{file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c85cf76561fbd01e0d9ea2d1cbe711a65400092bc52b5242b16cfd22e51f0c58"},
|
{file = "tokenizers-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b69e55e481459c07885263743a0d3c18d52db19bae8226a19bcca4aaa213fff"},
|
||||||
{file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:670b802d4d82bbbb832ddb0d41df7015b3e549714c0e77f9bed3e74d42400fbe"},
|
{file = "tokenizers-0.20.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4806b4d82e27a2512bc23057b2986bc8b85824914286975b84d8105ff40d03d9"},
|
||||||
{file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85aa3ab4b03d5e99fdd31660872249df5e855334b6c333e0bc13032ff4469c4a"},
|
{file = "tokenizers-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9859e9ef13adf5a473ccab39d31bff9c550606ae3c784bf772b40f615742a24f"},
|
||||||
{file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbf001afbbed111a79ca47d75941e9e5361297a87d186cbfc11ed45e30b5daba"},
|
{file = "tokenizers-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef703efedf4c20488a8eb17637b55973745b27997ff87bad88ed499b397d1144"},
|
||||||
{file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c89aa46c269e4e70c4d4f9d6bc644fcc39bb409cb2a81227923404dd6f5227"},
|
{file = "tokenizers-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eec0061bab94b1841ab87d10831fdf1b48ebaed60e6d66d66dbe1d873f92bf5"},
|
||||||
{file = "tokenizers-0.19.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:39c1ec76ea1027438fafe16ecb0fb84795e62e9d643444c1090179e63808c69d"},
|
{file = "tokenizers-0.20.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:980f3d0d7e73f845b69087f29a63c11c7eb924c4ad6b358da60f3db4cf24bdb4"},
|
||||||
{file = "tokenizers-0.19.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c2a0d47a89b48d7daa241e004e71fb5a50533718897a4cd6235cb846d511a478"},
|
{file = "tokenizers-0.20.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7c157550a2f3851b29d7fdc9dc059fcf81ff0c0fc49a1e5173a89d533ed043fa"},
|
||||||
{file = "tokenizers-0.19.1-cp39-none-win32.whl", hash = "sha256:61b7fe8886f2e104d4caf9218b157b106207e0f2a4905c9c7ac98890688aabeb"},
|
{file = "tokenizers-0.20.0-cp39-none-win32.whl", hash = "sha256:8a3d2f4d08608ec4f9895ec25b4b36a97f05812543190a5f2c3cd19e8f041e5a"},
|
||||||
{file = "tokenizers-0.19.1-cp39-none-win_amd64.whl", hash = "sha256:f97660f6c43efd3e0bfd3f2e3e5615bf215680bad6ee3d469df6454b8c6e8256"},
|
{file = "tokenizers-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:d90188d12afd0c75e537f9a1d92f9c7375650188ee4f48fdc76f9e38afbd2251"},
|
||||||
{file = "tokenizers-0.19.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b11853f17b54c2fe47742c56d8a33bf49ce31caf531e87ac0d7d13d327c9334"},
|
{file = "tokenizers-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d68e15f1815357b059ec266062340c343ea7f98f7f330602df81ffa3474b6122"},
|
||||||
{file = "tokenizers-0.19.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d26194ef6c13302f446d39972aaa36a1dda6450bc8949f5eb4c27f51191375bd"},
|
{file = "tokenizers-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:23f9ecec637b9bc80da5f703808d29ed5329e56b5aa8d791d1088014f48afadc"},
|
||||||
{file = "tokenizers-0.19.1-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e8d1ed93beda54bbd6131a2cb363a576eac746d5c26ba5b7556bc6f964425594"},
|
{file = "tokenizers-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f830b318ee599e3d0665b3e325f85bc75ee2d2ca6285f52e439dc22b64691580"},
|
||||||
{file = "tokenizers-0.19.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca407133536f19bdec44b3da117ef0d12e43f6d4b56ac4c765f37eca501c7bda"},
|
{file = "tokenizers-0.20.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3dc750def789cb1de1b5a37657919545e1d9ffa667658b3fa9cb7862407a1b8"},
|
||||||
{file = "tokenizers-0.19.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce05fde79d2bc2e46ac08aacbc142bead21614d937aac950be88dc79f9db9022"},
|
{file = "tokenizers-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e26e6c755ae884c2ea6135cd215bdd0fccafe4ee62405014b8c3cd19954e3ab9"},
|
||||||
{file = "tokenizers-0.19.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:35583cd46d16f07c054efd18b5d46af4a2f070a2dd0a47914e66f3ff5efb2b1e"},
|
{file = "tokenizers-0.20.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a1158c7174f427182e08baa2a8ded2940f2b4a3e94969a85cc9cfd16004cbcea"},
|
||||||
{file = "tokenizers-0.19.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:43350270bfc16b06ad3f6f07eab21f089adb835544417afda0f83256a8bf8b75"},
|
{file = "tokenizers-0.20.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:6324826287a3fc198898d3dcf758fe4a8479e42d6039f4c59e2cedd3cf92f64e"},
|
||||||
{file = "tokenizers-0.19.1-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b4399b59d1af5645bcee2072a463318114c39b8547437a7c2d6a186a1b5a0e2d"},
|
{file = "tokenizers-0.20.0-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7d8653149405bb0c16feaf9cfee327fdb6aaef9dc2998349fec686f35e81c4e2"},
|
||||||
{file = "tokenizers-0.19.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6852c5b2a853b8b0ddc5993cd4f33bfffdca4fcc5d52f89dd4b8eada99379285"},
|
{file = "tokenizers-0.20.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8a2dc1e402a155e97309287ca085c80eb1b7fab8ae91527d3b729181639fa51"},
|
||||||
{file = "tokenizers-0.19.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd266ae85c3d39df2f7e7d0e07f6c41a55e9a3123bb11f854412952deacd828"},
|
{file = "tokenizers-0.20.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bef67b20aa6e5f7868c42c7c5eae4d24f856274a464ae62e47a0f2cccec3da"},
|
||||||
{file = "tokenizers-0.19.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecb2651956eea2aa0a2d099434134b1b68f1c31f9a5084d6d53f08ed43d45ff2"},
|
{file = "tokenizers-0.20.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da06e397182ff53789c506c7833220c192952c57e1581a53f503d8d953e2d67e"},
|
||||||
{file = "tokenizers-0.19.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:b279ab506ec4445166ac476fb4d3cc383accde1ea152998509a94d82547c8e2a"},
|
{file = "tokenizers-0.20.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:302f7e11a14814028b7fc88c45a41f1bbe9b5b35fd76d6869558d1d1809baa43"},
|
||||||
{file = "tokenizers-0.19.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:89183e55fb86e61d848ff83753f64cded119f5d6e1f553d14ffee3700d0a4a49"},
|
{file = "tokenizers-0.20.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:055ec46e807b875589dfbe3d9259f9a6ee43394fb553b03b3d1e9541662dbf25"},
|
||||||
{file = "tokenizers-0.19.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2edbc75744235eea94d595a8b70fe279dd42f3296f76d5a86dde1d46e35f574"},
|
{file = "tokenizers-0.20.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e3144b8acebfa6ae062e8f45f7ed52e4b50fb6c62f93afc8871b525ab9fdcab3"},
|
||||||
{file = "tokenizers-0.19.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0e64bfde9a723274e9a71630c3e9494ed7b4c0f76a1faacf7fe294cd26f7ae7c"},
|
{file = "tokenizers-0.20.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b52aa3fd14b2a07588c00a19f66511cff5cca8f7266ca3edcdd17f3512ad159f"},
|
||||||
{file = "tokenizers-0.19.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0b5ca92bfa717759c052e345770792d02d1f43b06f9e790ca0a1db62838816f3"},
|
{file = "tokenizers-0.20.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b8cf52779ffc5d4d63a0170fbeb512372bad0dd014ce92bbb9149756c831124"},
|
||||||
{file = "tokenizers-0.19.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f8a20266e695ec9d7a946a019c1d5ca4eddb6613d4f466888eee04f16eedb85"},
|
{file = "tokenizers-0.20.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:983a45dd11a876124378dae71d6d9761822199b68a4c73f32873d8cdaf326a5b"},
|
||||||
{file = "tokenizers-0.19.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63c38f45d8f2a2ec0f3a20073cccb335b9f99f73b3c69483cd52ebc75369d8a1"},
|
{file = "tokenizers-0.20.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df6b819c9a19831ebec581e71a7686a54ab45d90faf3842269a10c11d746de0c"},
|
||||||
{file = "tokenizers-0.19.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dd26e3afe8a7b61422df3176e06664503d3f5973b94f45d5c45987e1cb711876"},
|
{file = "tokenizers-0.20.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e738cfd80795fcafcef89c5731c84b05638a4ab3f412f97d5ed7765466576eb1"},
|
||||||
{file = "tokenizers-0.19.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:eddd5783a4a6309ce23432353cdb36220e25cbb779bfa9122320666508b44b88"},
|
{file = "tokenizers-0.20.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:c8842c7be2fadb9c9edcee233b1b7fe7ade406c99b0973f07439985c1c1d0683"},
|
||||||
{file = "tokenizers-0.19.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:56ae39d4036b753994476a1b935584071093b55c7a72e3b8288e68c313ca26e7"},
|
{file = "tokenizers-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e47a82355511c373a4a430c4909dc1e518e00031207b1fec536c49127388886b"},
|
||||||
{file = "tokenizers-0.19.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f9939ca7e58c2758c01b40324a59c034ce0cebad18e0d4563a9b1beab3018243"},
|
{file = "tokenizers-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9afbf359004551179a5db19424180c81276682773cff2c5d002f6eaaffe17230"},
|
||||||
{file = "tokenizers-0.19.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c330c0eb815d212893c67a032e9dc1b38a803eccb32f3e8172c19cc69fbb439"},
|
{file = "tokenizers-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07eaa8799a92e6af6f472c21a75bf71575de2af3c0284120b7a09297c0de2f3"},
|
||||||
{file = "tokenizers-0.19.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec11802450a2487cdf0e634b750a04cbdc1c4d066b97d94ce7dd2cb51ebb325b"},
|
{file = "tokenizers-0.20.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0994b2e5fc53a301071806bc4303e4bc3bdc3f490e92a21338146a36746b0872"},
|
||||||
{file = "tokenizers-0.19.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b718f316b596f36e1dae097a7d5b91fc5b85e90bf08b01ff139bd8953b25af"},
|
{file = "tokenizers-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6466e0355b603d10e3cc3d282d350b646341b601e50969464a54939f9848d0"},
|
||||||
{file = "tokenizers-0.19.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ed69af290c2b65169f0ba9034d1dc39a5db9459b32f1dd8b5f3f32a3fcf06eab"},
|
{file = "tokenizers-0.20.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:1e86594c2a433cb1ea09cfbe596454448c566e57ee8905bd557e489d93e89986"},
|
||||||
{file = "tokenizers-0.19.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f8a9c828277133af13f3859d1b6bf1c3cb6e9e1637df0e45312e6b7c2e622b1f"},
|
{file = "tokenizers-0.20.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3e14cdef1efa96ecead6ea64a891828432c3ebba128bdc0596e3059fea104ef3"},
|
||||||
{file = "tokenizers-0.19.1.tar.gz", hash = "sha256:ee59e6680ed0fdbe6b724cf38bd70400a0c1dd623b07ac729087270caeac88e3"},
|
{file = "tokenizers-0.20.0.tar.gz", hash = "sha256:39d7acc43f564c274085cafcd1dae9d36f332456de1a31970296a6b8da4eac8d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -3235,13 +3241,13 @@ telegram = ["requests"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.9.0"
|
version = "4.12.2"
|
||||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
|
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||||
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
|
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3262,13 +3268,13 @@ zstd = ["zstandard (>=0.18.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uvicorn"
|
name = "uvicorn"
|
||||||
version = "0.30.5"
|
version = "0.30.6"
|
||||||
description = "The lightning-fast ASGI server."
|
description = "The lightning-fast ASGI server."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "uvicorn-0.30.5-py3-none-any.whl", hash = "sha256:b2d86de274726e9878188fa07576c9ceeff90a839e2b6e25c917fe05f5a6c835"},
|
{file = "uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"},
|
||||||
{file = "uvicorn-0.30.5.tar.gz", hash = "sha256:ac6fdbd4425c5fd17a9fe39daf4d4d075da6fdc80f653e5894cdc2fd98752bee"},
|
{file = "uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "machine-learning"
|
name = "machine-learning"
|
||||||
version = "1.111.0"
|
version = "1.113.0"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ fi
|
|||||||
: "${IMMICH_HOST:=[::]}"
|
: "${IMMICH_HOST:=[::]}"
|
||||||
: "${IMMICH_PORT:=3003}"
|
: "${IMMICH_PORT:=3003}"
|
||||||
: "${MACHINE_LEARNING_WORKERS:=1}"
|
: "${MACHINE_LEARNING_WORKERS:=1}"
|
||||||
|
: "${MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S:=2}"
|
||||||
|
|
||||||
gunicorn app.main:app \
|
gunicorn app.main:app \
|
||||||
-k app.config.CustomUvicornWorker \
|
-k app.config.CustomUvicornWorker \
|
||||||
@@ -20,4 +21,5 @@ gunicorn app.main:app \
|
|||||||
-w "$MACHINE_LEARNING_WORKERS" \
|
-w "$MACHINE_LEARNING_WORKERS" \
|
||||||
-t "$MACHINE_LEARNING_WORKER_TIMEOUT" \
|
-t "$MACHINE_LEARNING_WORKER_TIMEOUT" \
|
||||||
--log-config-json log_conf.json \
|
--log-config-json log_conf.json \
|
||||||
|
--keep-alive "$MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S" \
|
||||||
--graceful-timeout 0
|
--graceful-timeout 0
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"flutter": "3.22.3"
|
"flutter": "3.24.0"
|
||||||
}
|
}
|
||||||
|
|||||||
2
mobile/.vscode/settings.json
vendored
2
mobile/.vscode/settings.json
vendored
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dart.flutterSdkPath": ".fvm/versions/3.22.3",
|
"dart.flutterSdkPath": ".fvm/versions/3.24.0",
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/.fvm": true
|
"**/.fvm": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "app.alextran.immich"
|
applicationId "app.alextran.immich"
|
||||||
minSdkVersion 26
|
minSdkVersion 26
|
||||||
targetSdkVersion 33
|
targetSdkVersion 34
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,37 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.alextran.immich"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.alextran.immich"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="32" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="32" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_MEDIA" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
|
||||||
|
|
||||||
|
<!-- Foreground service permission -->
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
|
|
||||||
<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="true"
|
<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="true"
|
||||||
android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true"
|
android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true"
|
||||||
android:largeHeap="true">
|
android:largeHeap="true" android:enableOnBackInvokedCallback="true">
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
||||||
|
android:directBootAware="false"
|
||||||
|
android:enabled="@bool/enable_system_foreground_service_default"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="dataSync|shortService" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.EnableImpeller"
|
android:name="io.flutter.embedding.android.EnableImpeller"
|
||||||
@@ -40,7 +69,7 @@
|
|||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="app.immich" />
|
<data android:scheme="app.immich" android:pathPrefix="/oauth-callback"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
@@ -51,24 +80,10 @@
|
|||||||
<provider
|
<provider
|
||||||
android:name="androidx.startup.InitializationProvider"
|
android:name="androidx.startup.InitializationProvider"
|
||||||
android:authorities="${applicationId}.androidx-startup"
|
android:authorities="${applicationId}.androidx-startup"
|
||||||
tools:node="remove"></provider>
|
tools:node="remove" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
|
||||||
android:maxSdkVersion="32" />
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
|
||||||
<uses-permission android:name="android.permission.MANAGE_MEDIA" />
|
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
@@ -79,4 +94,4 @@
|
|||||||
<data android:scheme="geo" />
|
<data android:scheme="geo" />
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package app.alextran.immich
|
package app.alextran.immich
|
||||||
|
|
||||||
import com.bumptech.glide.annotation.GlideModule
|
import com.bumptech.glide.annotation.GlideModule
|
||||||
import com.bumptech.glide.module.AppGlideModule
|
import com.bumptech.glide.module.AppGlideModule
|
||||||
|
|
||||||
@GlideModule
|
@GlideModule
|
||||||
class AppGlideModule : AppGlideModule()
|
class AppGlideModule : AppGlideModule()
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package app.alextran.immich
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||||
|
import io.flutter.plugin.common.BinaryMessenger
|
||||||
|
import io.flutter.plugin.common.MethodCall
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android plugin for Dart `BackgroundService`
|
||||||
|
*
|
||||||
|
* Receives messages/method calls from the foreground Dart side to manage
|
||||||
|
* the background service, e.g. start (enqueue), stop (cancel)
|
||||||
|
*/
|
||||||
|
class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||||
|
|
||||||
|
private var methodChannel: MethodChannel? = null
|
||||||
|
private var context: Context? = null
|
||||||
|
|
||||||
|
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
|
onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onAttachedToEngine(ctx: Context, messenger: BinaryMessenger) {
|
||||||
|
context = ctx
|
||||||
|
methodChannel = MethodChannel(messenger, "immich/foregroundChannel")
|
||||||
|
methodChannel?.setMethodCallHandler(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
|
onDetachedFromEngine()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onDetachedFromEngine() {
|
||||||
|
methodChannel?.setMethodCallHandler(null)
|
||||||
|
methodChannel = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
val ctx = context!!
|
||||||
|
when (call.method) {
|
||||||
|
"enable" -> {
|
||||||
|
val args = call.arguments<ArrayList<*>>()!!
|
||||||
|
ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
.edit()
|
||||||
|
.putBoolean(ContentObserverWorker.SHARED_PREF_SERVICE_ENABLED, true)
|
||||||
|
.putLong(BackupWorker.SHARED_PREF_CALLBACK_KEY, args[0] as Long)
|
||||||
|
.putString(BackupWorker.SHARED_PREF_NOTIFICATION_TITLE, args[1] as String)
|
||||||
|
.apply()
|
||||||
|
ContentObserverWorker.enable(ctx, immediate = args[2] as Boolean)
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
"configure" -> {
|
||||||
|
val args = call.arguments<ArrayList<*>>()!!
|
||||||
|
val requireUnmeteredNetwork = args[0] as Boolean
|
||||||
|
val requireCharging = args[1] as Boolean
|
||||||
|
val triggerUpdateDelay = (args[2] as Number).toLong()
|
||||||
|
val triggerMaxDelay = (args[3] as Number).toLong()
|
||||||
|
ContentObserverWorker.configureWork(
|
||||||
|
ctx,
|
||||||
|
requireUnmeteredNetwork,
|
||||||
|
requireCharging,
|
||||||
|
triggerUpdateDelay,
|
||||||
|
triggerMaxDelay
|
||||||
|
)
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
"disable" -> {
|
||||||
|
ContentObserverWorker.disable(ctx)
|
||||||
|
BackupWorker.stopWork(ctx)
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
"isEnabled" -> {
|
||||||
|
result.success(ContentObserverWorker.isEnabled(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
"isIgnoringBatteryOptimizations" -> {
|
||||||
|
result.success(BackupWorker.isIgnoringBatteryOptimizations(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
"digestFiles" -> {
|
||||||
|
val args = call.arguments<ArrayList<String>>()!!
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
val buf = ByteArray(BUFFER_SIZE)
|
||||||
|
val digest: MessageDigest = MessageDigest.getInstance("SHA-1")
|
||||||
|
val hashes = arrayOfNulls<ByteArray>(args.size)
|
||||||
|
for (i in args.indices) {
|
||||||
|
val path = args[i]
|
||||||
|
var len = 0
|
||||||
|
try {
|
||||||
|
val file = FileInputStream(path)
|
||||||
|
file.use { assetFile ->
|
||||||
|
while (true) {
|
||||||
|
len = assetFile.read(buf)
|
||||||
|
if (len != BUFFER_SIZE) break
|
||||||
|
digest.update(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
digest.update(buf, 0, len)
|
||||||
|
hashes[i] = digest.digest()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// skip this file
|
||||||
|
Log.w(TAG, "Failed to hash file ${args[i]}: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.success(hashes.asList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val TAG = "BackgroundServicePlugin"
|
||||||
|
private const val BUFFER_SIZE = 2 * 1024 * 1024;
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
package app.alextran.immich
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.concurrent.futures.ResolvableFuture
|
||||||
|
import androidx.work.BackoffPolicy
|
||||||
|
import androidx.work.Constraints
|
||||||
|
import androidx.work.ForegroundInfo
|
||||||
|
import androidx.work.ListenableWorker
|
||||||
|
import androidx.work.NetworkType
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import androidx.work.ExistingWorkPolicy
|
||||||
|
import androidx.work.OneTimeWorkRequest
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.WorkInfo
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.embedding.engine.dart.DartExecutor
|
||||||
|
import io.flutter.embedding.engine.loader.FlutterLoader
|
||||||
|
import io.flutter.plugin.common.MethodCall
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import io.flutter.view.FlutterCallbackInformation
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worker executed by Android WorkManager to perform backup in background
|
||||||
|
*
|
||||||
|
* Starts the Dart runtime/engine and calls `_nativeEntry` function in
|
||||||
|
* `background.service.dart` to run the actual backup logic.
|
||||||
|
* Called by Android WorkManager when all constraints for the work are met,
|
||||||
|
* i.e. battery is not low and optionally Wifi and charging are active.
|
||||||
|
*/
|
||||||
|
class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ctx, params),
|
||||||
|
MethodChannel.MethodCallHandler {
|
||||||
|
|
||||||
|
private val resolvableFuture = ResolvableFuture.create<Result>()
|
||||||
|
private var engine: FlutterEngine? = null
|
||||||
|
private lateinit var backgroundChannel: MethodChannel
|
||||||
|
private val notificationManager =
|
||||||
|
ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
private val isIgnoringBatteryOptimizations = isIgnoringBatteryOptimizations(applicationContext)
|
||||||
|
private var timeBackupStarted: Long = 0L
|
||||||
|
private var notificationBuilder: NotificationCompat.Builder? = null
|
||||||
|
private var notificationDetailBuilder: NotificationCompat.Builder? = null
|
||||||
|
private var fgFuture: ListenableFuture<Void>? = null
|
||||||
|
|
||||||
|
override fun startWork(): ListenableFuture<ListenableWorker.Result> {
|
||||||
|
|
||||||
|
Log.d(TAG, "startWork")
|
||||||
|
|
||||||
|
val ctx = applicationContext
|
||||||
|
|
||||||
|
if (!flutterLoader.initialized()) {
|
||||||
|
flutterLoader.startInitialization(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a Notification channel
|
||||||
|
createChannel()
|
||||||
|
|
||||||
|
Log.d(TAG, "isIgnoringBatteryOptimizations $isIgnoringBatteryOptimizations")
|
||||||
|
if (isIgnoringBatteryOptimizations) {
|
||||||
|
// normal background services can only up to 10 minutes
|
||||||
|
// foreground services are allowed to run indefinitely
|
||||||
|
// requires battery optimizations to be disabled (either manually by the user
|
||||||
|
// or by the system learning that immich is important to the user)
|
||||||
|
val title = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
.getString(SHARED_PREF_NOTIFICATION_TITLE, NOTIFICATION_DEFAULT_TITLE)!!
|
||||||
|
showInfo(getInfoBuilder(title, indeterminate = true).build())
|
||||||
|
}
|
||||||
|
|
||||||
|
engine = FlutterEngine(ctx)
|
||||||
|
|
||||||
|
flutterLoader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) {
|
||||||
|
runDart()
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvableFuture
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the Dart runtime/engine and calls `_nativeEntry` function in
|
||||||
|
* `background.service.dart` to run the actual backup logic.
|
||||||
|
*/
|
||||||
|
private fun runDart() {
|
||||||
|
val callbackDispatcherHandle = applicationContext.getSharedPreferences(
|
||||||
|
SHARED_PREF_NAME, Context.MODE_PRIVATE
|
||||||
|
).getLong(SHARED_PREF_CALLBACK_KEY, 0L)
|
||||||
|
val callbackInformation =
|
||||||
|
FlutterCallbackInformation.lookupCallbackInformation(callbackDispatcherHandle)
|
||||||
|
val appBundlePath = flutterLoader.findAppBundlePath()
|
||||||
|
|
||||||
|
engine?.let { engine ->
|
||||||
|
backgroundChannel = MethodChannel(engine.dartExecutor, "immich/backgroundChannel")
|
||||||
|
backgroundChannel.setMethodCallHandler(this@BackupWorker)
|
||||||
|
engine.dartExecutor.executeDartCallback(
|
||||||
|
DartExecutor.DartCallback(
|
||||||
|
applicationContext.assets,
|
||||||
|
appBundlePath,
|
||||||
|
callbackInformation
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopped() {
|
||||||
|
Log.d(TAG, "onStopped")
|
||||||
|
// called when the system has to stop this worker because constraints are
|
||||||
|
// no longer met or the system needs resources for more important tasks
|
||||||
|
Handler(Looper.getMainLooper()).postAtFrontOfQueue {
|
||||||
|
backgroundChannel.invokeMethod("systemStop", null)
|
||||||
|
}
|
||||||
|
waitOnSetForegroundAsync()
|
||||||
|
// cannot await/get(block) on resolvableFuture as its already cancelled (would throw CancellationException)
|
||||||
|
// instead, wait for 5 seconds until forcefully stopping backup work
|
||||||
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
stopEngine(null)
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun waitOnSetForegroundAsync() {
|
||||||
|
val fgFuture = this.fgFuture
|
||||||
|
if (fgFuture != null && !fgFuture.isCancelled && !fgFuture.isDone) {
|
||||||
|
try {
|
||||||
|
fgFuture.get(500, TimeUnit.MILLISECONDS)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// ignored, there is nothing to be done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopEngine(result: Result?) {
|
||||||
|
clearBackgroundNotification()
|
||||||
|
engine?.destroy()
|
||||||
|
engine = null
|
||||||
|
if (result != null) {
|
||||||
|
Log.d(TAG, "stopEngine result=${result}")
|
||||||
|
resolvableFuture.set(result)
|
||||||
|
}
|
||||||
|
waitOnSetForegroundAsync()
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||||
|
override fun onMethodCall(call: MethodCall, r: MethodChannel.Result) {
|
||||||
|
when (call.method) {
|
||||||
|
"initialized" -> {
|
||||||
|
timeBackupStarted = SystemClock.uptimeMillis()
|
||||||
|
backgroundChannel.invokeMethod(
|
||||||
|
"onAssetsChanged",
|
||||||
|
null,
|
||||||
|
object : MethodChannel.Result {
|
||||||
|
override fun notImplemented() {
|
||||||
|
stopEngine(Result.failure())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
|
||||||
|
stopEngine(Result.failure())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun success(receivedResult: Any?) {
|
||||||
|
val success = receivedResult as Boolean
|
||||||
|
stopEngine(if (success) Result.success() else Result.retry())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"updateNotification" -> {
|
||||||
|
val args = call.arguments<ArrayList<*>>()!!
|
||||||
|
val title = args[0] as String?
|
||||||
|
val content = args[1] as String?
|
||||||
|
val progress = args[2] as Int
|
||||||
|
val max = args[3] as Int
|
||||||
|
val indeterminate = args[4] as Boolean
|
||||||
|
val isDetail = args[5] as Boolean
|
||||||
|
val onlyIfFG = args[6] as Boolean
|
||||||
|
if (!onlyIfFG || isIgnoringBatteryOptimizations) {
|
||||||
|
showInfo(
|
||||||
|
getInfoBuilder(title, content, isDetail, progress, max, indeterminate).build(),
|
||||||
|
isDetail
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"showError" -> {
|
||||||
|
val args = call.arguments<ArrayList<*>>()!!
|
||||||
|
val title = args[0] as String
|
||||||
|
val content = args[1] as String?
|
||||||
|
val individualTag = args[2] as String?
|
||||||
|
showError(title, content, individualTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
"clearErrorNotifications" -> clearErrorNotifications()
|
||||||
|
"hasContentChanged" -> {
|
||||||
|
val lastChange = applicationContext
|
||||||
|
.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
.getLong(SHARED_PREF_LAST_CHANGE, timeBackupStarted)
|
||||||
|
val hasContentChanged = lastChange > timeBackupStarted;
|
||||||
|
timeBackupStarted = SystemClock.uptimeMillis()
|
||||||
|
r.success(hasContentChanged)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> r.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showError(title: String, content: String?, individualTag: String?) {
|
||||||
|
val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ERROR_ID)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setTicker(title)
|
||||||
|
.setContentText(content)
|
||||||
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
|
.build()
|
||||||
|
notificationManager.notify(individualTag, NOTIFICATION_ERROR_ID, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearErrorNotifications() {
|
||||||
|
notificationManager.cancel(NOTIFICATION_ERROR_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearBackgroundNotification() {
|
||||||
|
notificationManager.cancel(NOTIFICATION_ID)
|
||||||
|
notificationManager.cancel(NOTIFICATION_DETAIL_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showInfo(notification: Notification, isDetail: Boolean = false) {
|
||||||
|
val id = if (isDetail) NOTIFICATION_DETAIL_ID else NOTIFICATION_ID
|
||||||
|
|
||||||
|
if (isIgnoringBatteryOptimizations && !isDetail) {
|
||||||
|
fgFuture = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
setForegroundAsync(ForegroundInfo(id, notification, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE))
|
||||||
|
} else {
|
||||||
|
setForegroundAsync(ForegroundInfo(id, notification))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
notificationManager.notify(id, notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInfoBuilder(
|
||||||
|
title: String? = null,
|
||||||
|
content: String? = null,
|
||||||
|
isDetail: Boolean = false,
|
||||||
|
progress: Int = 0,
|
||||||
|
max: Int = 0,
|
||||||
|
indeterminate: Boolean = false,
|
||||||
|
): NotificationCompat.Builder {
|
||||||
|
var builder = if (isDetail) notificationDetailBuilder else notificationBuilder
|
||||||
|
if (builder == null) {
|
||||||
|
builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
|
||||||
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
|
.setOnlyAlertOnce(true)
|
||||||
|
.setOngoing(true)
|
||||||
|
if (isDetail) {
|
||||||
|
notificationDetailBuilder = builder
|
||||||
|
} else {
|
||||||
|
notificationBuilder = builder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (title != null) {
|
||||||
|
builder.setTicker(title).setContentTitle(title)
|
||||||
|
}
|
||||||
|
if (content != null) {
|
||||||
|
builder.setContentText(content)
|
||||||
|
}
|
||||||
|
return builder.setProgress(max, progress, indeterminate)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createChannel() {
|
||||||
|
val foreground = NotificationChannel(
|
||||||
|
NOTIFICATION_CHANNEL_ID,
|
||||||
|
NOTIFICATION_CHANNEL_ID,
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
)
|
||||||
|
notificationManager.createNotificationChannel(foreground)
|
||||||
|
val error = NotificationChannel(
|
||||||
|
NOTIFICATION_CHANNEL_ERROR_ID,
|
||||||
|
NOTIFICATION_CHANNEL_ERROR_ID,
|
||||||
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
|
)
|
||||||
|
notificationManager.createNotificationChannel(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SHARED_PREF_NAME = "immichBackgroundService"
|
||||||
|
const val SHARED_PREF_CALLBACK_KEY = "callbackDispatcherHandle"
|
||||||
|
const val SHARED_PREF_NOTIFICATION_TITLE = "notificationTitle"
|
||||||
|
const val SHARED_PREF_LAST_CHANGE = "lastChange"
|
||||||
|
|
||||||
|
private const val TASK_NAME_BACKUP = "immich/BackupWorker"
|
||||||
|
private const val NOTIFICATION_CHANNEL_ID = "immich/backgroundService"
|
||||||
|
private const val NOTIFICATION_CHANNEL_ERROR_ID = "immich/backgroundServiceError"
|
||||||
|
private const val NOTIFICATION_DEFAULT_TITLE = "Immich"
|
||||||
|
private const val NOTIFICATION_ID = 1
|
||||||
|
private const val NOTIFICATION_ERROR_ID = 2
|
||||||
|
private const val NOTIFICATION_DETAIL_ID = 3
|
||||||
|
private const val ONE_MINUTE = 60000L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueues the BackupWorker to run once the constraints are met
|
||||||
|
*/
|
||||||
|
fun enqueueBackupWorker(
|
||||||
|
context: Context,
|
||||||
|
requireWifi: Boolean = false,
|
||||||
|
requireCharging: Boolean = false,
|
||||||
|
delayMilliseconds: Long = 0L
|
||||||
|
) {
|
||||||
|
val workRequest = buildWorkRequest(requireWifi, requireCharging, delayMilliseconds)
|
||||||
|
WorkManager.getInstance(context)
|
||||||
|
.enqueueUniqueWork(TASK_NAME_BACKUP, ExistingWorkPolicy.KEEP, workRequest)
|
||||||
|
Log.d(TAG, "enqueueBackupWorker: BackupWorker enqueued")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the constraints of an already enqueued BackupWorker
|
||||||
|
*/
|
||||||
|
fun updateBackupWorker(
|
||||||
|
context: Context,
|
||||||
|
requireWifi: Boolean = false,
|
||||||
|
requireCharging: Boolean = false
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
val wm = WorkManager.getInstance(context)
|
||||||
|
val workInfoFuture = wm.getWorkInfosForUniqueWork(TASK_NAME_BACKUP)
|
||||||
|
val workInfoList = workInfoFuture.get(1000, TimeUnit.MILLISECONDS)
|
||||||
|
if (workInfoList != null) {
|
||||||
|
for (workInfo in workInfoList) {
|
||||||
|
if (workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||||
|
val workRequest = buildWorkRequest(requireWifi, requireCharging)
|
||||||
|
wm.enqueueUniqueWork(TASK_NAME_BACKUP, ExistingWorkPolicy.REPLACE, workRequest)
|
||||||
|
Log.d(TAG, "updateBackupWorker updated BackupWorker constraints")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(TAG, "updateBackupWorker: BackupWorker not enqueued")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(TAG, "updateBackupWorker failed: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the currently running worker (if any) and removes it from the work queue
|
||||||
|
*/
|
||||||
|
fun stopWork(context: Context) {
|
||||||
|
WorkManager.getInstance(context).cancelUniqueWork(TASK_NAME_BACKUP)
|
||||||
|
Log.d(TAG, "stopWork: BackupWorker cancelled")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if the app is ignoring battery optimizations
|
||||||
|
*/
|
||||||
|
fun isIgnoringBatteryOptimizations(ctx: Context): Boolean {
|
||||||
|
val powerManager = ctx.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
return powerManager.isIgnoringBatteryOptimizations(ctx.packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildWorkRequest(
|
||||||
|
requireWifi: Boolean = false,
|
||||||
|
requireCharging: Boolean = false,
|
||||||
|
delayMilliseconds: Long = 0L
|
||||||
|
): OneTimeWorkRequest {
|
||||||
|
val constraints = Constraints.Builder()
|
||||||
|
.setRequiredNetworkType(if (requireWifi) NetworkType.UNMETERED else NetworkType.CONNECTED)
|
||||||
|
.setRequiresBatteryNotLow(true)
|
||||||
|
.setRequiresCharging(requireCharging)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
val work = OneTimeWorkRequest.Builder(BackupWorker::class.java)
|
||||||
|
.setConstraints(constraints)
|
||||||
|
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, ONE_MINUTE, TimeUnit.MILLISECONDS)
|
||||||
|
.setInitialDelay(delayMilliseconds, TimeUnit.MILLISECONDS)
|
||||||
|
.build()
|
||||||
|
return work
|
||||||
|
}
|
||||||
|
|
||||||
|
private val flutterLoader = FlutterLoader()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val TAG = "BackupWorker"
|
||||||
@@ -1,144 +1,144 @@
|
|||||||
package app.alextran.immich
|
package app.alextran.immich
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.work.Constraints
|
import androidx.work.Constraints
|
||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.OneTimeWorkRequest
|
import androidx.work.OneTimeWorkRequest
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import androidx.work.Operation
|
import androidx.work.Operation
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Worker executed by Android WorkManager observing content changes (new photos/videos)
|
* Worker executed by Android WorkManager observing content changes (new photos/videos)
|
||||||
*
|
*
|
||||||
* Immediately enqueues the BackupWorker when running.
|
* Immediately enqueues the BackupWorker when running.
|
||||||
* As this work is not triggered periodically, but on content change, the
|
* As this work is not triggered periodically, but on content change, the
|
||||||
* worker enqueues itself again after each run.
|
* worker enqueues itself again after each run.
|
||||||
*/
|
*/
|
||||||
class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
|
class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
|
||||||
|
|
||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
if (!isEnabled(applicationContext)) {
|
if (!isEnabled(applicationContext)) {
|
||||||
return Result.failure()
|
return Result.failure()
|
||||||
}
|
}
|
||||||
if (getTriggeredContentUris().size > 0) {
|
if (triggeredContentUris.size > 0) {
|
||||||
startBackupWorker(applicationContext, delayMilliseconds = 0)
|
startBackupWorker(applicationContext, delayMilliseconds = 0)
|
||||||
}
|
}
|
||||||
enqueueObserverWorker(applicationContext, ExistingWorkPolicy.REPLACE)
|
enqueueObserverWorker(applicationContext, ExistingWorkPolicy.REPLACE)
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val SHARED_PREF_SERVICE_ENABLED = "serviceEnabled"
|
const val SHARED_PREF_SERVICE_ENABLED = "serviceEnabled"
|
||||||
const val SHARED_PREF_REQUIRE_WIFI = "requireWifi"
|
private const val SHARED_PREF_REQUIRE_WIFI = "requireWifi"
|
||||||
const val SHARED_PREF_REQUIRE_CHARGING = "requireCharging"
|
private const val SHARED_PREF_REQUIRE_CHARGING = "requireCharging"
|
||||||
const val SHARED_PREF_TRIGGER_UPDATE_DELAY = "triggerUpdateDelay"
|
private const val SHARED_PREF_TRIGGER_UPDATE_DELAY = "triggerUpdateDelay"
|
||||||
const val SHARED_PREF_TRIGGER_MAX_DELAY = "triggerMaxDelay"
|
private const val SHARED_PREF_TRIGGER_MAX_DELAY = "triggerMaxDelay"
|
||||||
|
|
||||||
private const val TASK_NAME_OBSERVER = "immich/ContentObserver"
|
private const val TASK_NAME_OBSERVER = "immich/ContentObserver"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enqueues the `ContentObserverWorker`.
|
* Enqueues the `ContentObserverWorker`.
|
||||||
*
|
*
|
||||||
* @param context Android Context
|
* @param context Android Context
|
||||||
*/
|
*/
|
||||||
fun enable(context: Context, immediate: Boolean = false) {
|
fun enable(context: Context, immediate: Boolean = false) {
|
||||||
enqueueObserverWorker(context, ExistingWorkPolicy.KEEP)
|
enqueueObserverWorker(context, ExistingWorkPolicy.KEEP)
|
||||||
Log.d(TAG, "enabled ContentObserverWorker")
|
Log.d(TAG, "enabled ContentObserverWorker")
|
||||||
if (immediate) {
|
if (immediate) {
|
||||||
startBackupWorker(context, delayMilliseconds = 5000)
|
startBackupWorker(context, delayMilliseconds = 5000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the `BackupWorker` to run when all constraints are met.
|
* Configures the `BackupWorker` to run when all constraints are met.
|
||||||
*
|
*
|
||||||
* @param context Android Context
|
* @param context Android Context
|
||||||
* @param requireWifi if true, task only runs if connected to wifi
|
* @param requireWifi if true, task only runs if connected to wifi
|
||||||
* @param requireCharging if true, task only runs if device is charging
|
* @param requireCharging if true, task only runs if device is charging
|
||||||
*/
|
*/
|
||||||
fun configureWork(context: Context,
|
fun configureWork(context: Context,
|
||||||
requireWifi: Boolean = false,
|
requireWifi: Boolean = false,
|
||||||
requireCharging: Boolean = false,
|
requireCharging: Boolean = false,
|
||||||
triggerUpdateDelay: Long = 5000,
|
triggerUpdateDelay: Long = 5000,
|
||||||
triggerMaxDelay: Long = 50000) {
|
triggerMaxDelay: Long = 50000) {
|
||||||
context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
.edit()
|
.edit()
|
||||||
.putBoolean(SHARED_PREF_SERVICE_ENABLED, true)
|
.putBoolean(SHARED_PREF_SERVICE_ENABLED, true)
|
||||||
.putBoolean(SHARED_PREF_REQUIRE_WIFI, requireWifi)
|
.putBoolean(SHARED_PREF_REQUIRE_WIFI, requireWifi)
|
||||||
.putBoolean(SHARED_PREF_REQUIRE_CHARGING, requireCharging)
|
.putBoolean(SHARED_PREF_REQUIRE_CHARGING, requireCharging)
|
||||||
.putLong(SHARED_PREF_TRIGGER_UPDATE_DELAY, triggerUpdateDelay)
|
.putLong(SHARED_PREF_TRIGGER_UPDATE_DELAY, triggerUpdateDelay)
|
||||||
.putLong(SHARED_PREF_TRIGGER_MAX_DELAY, triggerMaxDelay)
|
.putLong(SHARED_PREF_TRIGGER_MAX_DELAY, triggerMaxDelay)
|
||||||
.apply()
|
.apply()
|
||||||
BackupWorker.updateBackupWorker(context, requireWifi, requireCharging)
|
BackupWorker.updateBackupWorker(context, requireWifi, requireCharging)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the currently running worker (if any) and removes it from the work queue
|
* Stops the currently running worker (if any) and removes it from the work queue
|
||||||
*/
|
*/
|
||||||
fun disable(context: Context) {
|
fun disable(context: Context) {
|
||||||
context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
.edit().putBoolean(SHARED_PREF_SERVICE_ENABLED, false).apply()
|
.edit().putBoolean(SHARED_PREF_SERVICE_ENABLED, false).apply()
|
||||||
WorkManager.getInstance(context).cancelUniqueWork(TASK_NAME_OBSERVER)
|
WorkManager.getInstance(context).cancelUniqueWork(TASK_NAME_OBSERVER)
|
||||||
Log.d(TAG, "disabled ContentObserverWorker")
|
Log.d(TAG, "disabled ContentObserverWorker")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the user has enabled the background backup service
|
* Return true if the user has enabled the background backup service
|
||||||
*/
|
*/
|
||||||
fun isEnabled(ctx: Context): Boolean {
|
fun isEnabled(ctx: Context): Boolean {
|
||||||
return ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
return ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
.getBoolean(SHARED_PREF_SERVICE_ENABLED, false)
|
.getBoolean(SHARED_PREF_SERVICE_ENABLED, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enqueue and replace the worker without the content trigger but with a short delay
|
* Enqueue and replace the worker without the content trigger but with a short delay
|
||||||
*/
|
*/
|
||||||
fun workManagerAppClearedWorkaround(context: Context) {
|
fun workManagerAppClearedWorkaround(context: Context) {
|
||||||
val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java)
|
val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java)
|
||||||
.setInitialDelay(500, TimeUnit.MILLISECONDS)
|
.setInitialDelay(500, TimeUnit.MILLISECONDS)
|
||||||
.build()
|
.build()
|
||||||
WorkManager
|
WorkManager
|
||||||
.getInstance(context)
|
.getInstance(context)
|
||||||
.enqueueUniqueWork(TASK_NAME_OBSERVER, ExistingWorkPolicy.REPLACE, work)
|
.enqueueUniqueWork(TASK_NAME_OBSERVER, ExistingWorkPolicy.REPLACE, work)
|
||||||
.getResult()
|
.result
|
||||||
.get()
|
.get()
|
||||||
Log.d(TAG, "workManagerAppClearedWorkaround")
|
Log.d(TAG, "workManagerAppClearedWorkaround")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enqueueObserverWorker(context: Context, policy: ExistingWorkPolicy) {
|
private fun enqueueObserverWorker(context: Context, policy: ExistingWorkPolicy) {
|
||||||
val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
val constraints = Constraints.Builder()
|
val constraints = Constraints.Builder()
|
||||||
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
|
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
|
||||||
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
|
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
|
||||||
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
|
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
|
||||||
.addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true)
|
.addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true)
|
||||||
.setTriggerContentUpdateDelay(sp.getLong(SHARED_PREF_TRIGGER_UPDATE_DELAY, 5000), TimeUnit.MILLISECONDS)
|
.setTriggerContentUpdateDelay(sp.getLong(SHARED_PREF_TRIGGER_UPDATE_DELAY, 5000), TimeUnit.MILLISECONDS)
|
||||||
.setTriggerContentMaxDelay(sp.getLong(SHARED_PREF_TRIGGER_MAX_DELAY, 50000), TimeUnit.MILLISECONDS)
|
.setTriggerContentMaxDelay(sp.getLong(SHARED_PREF_TRIGGER_MAX_DELAY, 50000), TimeUnit.MILLISECONDS)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java)
|
val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java)
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
.build()
|
.build()
|
||||||
WorkManager.getInstance(context).enqueueUniqueWork(TASK_NAME_OBSERVER, policy, work)
|
WorkManager.getInstance(context).enqueueUniqueWork(TASK_NAME_OBSERVER, policy, work)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startBackupWorker(context: Context, delayMilliseconds: Long) {
|
fun startBackupWorker(context: Context, delayMilliseconds: Long) {
|
||||||
val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
if (!sp.getBoolean(SHARED_PREF_SERVICE_ENABLED, false))
|
if (!sp.getBoolean(SHARED_PREF_SERVICE_ENABLED, false))
|
||||||
return
|
return
|
||||||
val requireWifi = sp.getBoolean(SHARED_PREF_REQUIRE_WIFI, true)
|
val requireWifi = sp.getBoolean(SHARED_PREF_REQUIRE_WIFI, true)
|
||||||
val requireCharging = sp.getBoolean(SHARED_PREF_REQUIRE_CHARGING, false)
|
val requireCharging = sp.getBoolean(SHARED_PREF_REQUIRE_CHARGING, false)
|
||||||
BackupWorker.enqueueBackupWorker(context, requireWifi, requireCharging, delayMilliseconds)
|
BackupWorker.enqueueBackupWorker(context, requireWifi, requireCharging, delayMilliseconds)
|
||||||
sp.edit().putLong(BackupWorker.SHARED_PREF_LAST_CHANGE, SystemClock.uptimeMillis()).apply()
|
sp.edit().putLong(BackupWorker.SHARED_PREF_LAST_CHANGE, SystemClock.uptimeMillis()).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val TAG = "ContentObserverWorker"
|
private const val TAG = "ContentObserverWorker"
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
package app.alextran.immich
|
package app.alextran.immich
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.work.Configuration
|
import androidx.work.Configuration
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
|
|
||||||
class ImmichApp : Application() {
|
class ImmichApp : Application() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
val config = Configuration.Builder().build()
|
val config = Configuration.Builder().build()
|
||||||
WorkManager.initialize(this, config)
|
WorkManager.initialize(this, config)
|
||||||
// always start BackupWorker after WorkManager init; this fixes the following bug:
|
// always start BackupWorker after WorkManager init; this fixes the following bug:
|
||||||
// After the process is killed (by user or system), the first trigger (taking a new picture) is lost.
|
// After the process is killed (by user or system), the first trigger (taking a new picture) is lost.
|
||||||
// Thus, the BackupWorker is not started. If the system kills the process after each initialization
|
// Thus, the BackupWorker is not started. If the system kills the process after each initialization
|
||||||
// (because of low memory etc.), the backup is never performed.
|
// (because of low memory etc.), the backup is never performed.
|
||||||
// As a workaround, we also run a backup check when initializing the application
|
// As a workaround, we also run a backup check when initializing the application
|
||||||
ContentObserverWorker.startBackupWorker(context = this, delayMilliseconds = 0)
|
ContentObserverWorker.startBackupWorker(context = this, delayMilliseconds = 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
package app.alextran.immich
|
package app.alextran.immich
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
|
||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
flutterEngine.plugins.add(BackgroundServicePlugin())
|
flutterEngine.plugins.add(BackgroundServicePlugin())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package app.alextran.immich
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
|
||||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
|
||||||
import io.flutter.plugin.common.BinaryMessenger
|
|
||||||
import io.flutter.plugin.common.MethodCall
|
|
||||||
import io.flutter.plugin.common.MethodChannel
|
|
||||||
import java.security.MessageDigest
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Android plugin for Dart `BackgroundService`
|
|
||||||
*
|
|
||||||
* Receives messages/method calls from the foreground Dart side to manage
|
|
||||||
* the background service, e.g. start (enqueue), stop (cancel)
|
|
||||||
*/
|
|
||||||
class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
|
||||||
|
|
||||||
private var methodChannel: MethodChannel? = null
|
|
||||||
private var context: Context? = null
|
|
||||||
private val sha1: MessageDigest = MessageDigest.getInstance("SHA-1")
|
|
||||||
|
|
||||||
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
|
||||||
onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onAttachedToEngine(ctx: Context, messenger: BinaryMessenger) {
|
|
||||||
context = ctx
|
|
||||||
methodChannel = MethodChannel(messenger, "immich/foregroundChannel")
|
|
||||||
methodChannel?.setMethodCallHandler(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
|
||||||
onDetachedFromEngine()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onDetachedFromEngine() {
|
|
||||||
methodChannel?.setMethodCallHandler(null)
|
|
||||||
methodChannel = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
|
||||||
val ctx = context!!
|
|
||||||
when (call.method) {
|
|
||||||
"enable" -> {
|
|
||||||
val args = call.arguments<ArrayList<*>>()!!
|
|
||||||
ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
|
||||||
.edit()
|
|
||||||
.putBoolean(ContentObserverWorker.SHARED_PREF_SERVICE_ENABLED, true)
|
|
||||||
.putLong(BackupWorker.SHARED_PREF_CALLBACK_KEY, args.get(0) as Long)
|
|
||||||
.putString(BackupWorker.SHARED_PREF_NOTIFICATION_TITLE, args.get(1) as String)
|
|
||||||
.apply()
|
|
||||||
ContentObserverWorker.enable(ctx, immediate = args.get(2) as Boolean)
|
|
||||||
result.success(true)
|
|
||||||
}
|
|
||||||
"configure" -> {
|
|
||||||
val args = call.arguments<ArrayList<*>>()!!
|
|
||||||
val requireUnmeteredNetwork = args.get(0) as Boolean
|
|
||||||
val requireCharging = args.get(1) as Boolean
|
|
||||||
val triggerUpdateDelay = (args.get(2) as Number).toLong()
|
|
||||||
val triggerMaxDelay = (args.get(3) as Number).toLong()
|
|
||||||
ContentObserverWorker.configureWork(ctx, requireUnmeteredNetwork, requireCharging, triggerUpdateDelay, triggerMaxDelay)
|
|
||||||
result.success(true)
|
|
||||||
}
|
|
||||||
"disable" -> {
|
|
||||||
ContentObserverWorker.disable(ctx)
|
|
||||||
BackupWorker.stopWork(ctx)
|
|
||||||
result.success(true)
|
|
||||||
}
|
|
||||||
"isEnabled" -> {
|
|
||||||
result.success(ContentObserverWorker.isEnabled(ctx))
|
|
||||||
}
|
|
||||||
"isIgnoringBatteryOptimizations" -> {
|
|
||||||
result.success(BackupWorker.isIgnoringBatteryOptimizations(ctx))
|
|
||||||
}
|
|
||||||
"digestFiles" -> {
|
|
||||||
val args = call.arguments<ArrayList<String>>()!!
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
|
||||||
val buf = ByteArray(BUFSIZE)
|
|
||||||
val digest: MessageDigest = MessageDigest.getInstance("SHA-1")
|
|
||||||
val hashes = arrayOfNulls<ByteArray>(args.size)
|
|
||||||
for (i in args.indices) {
|
|
||||||
val path = args[i]
|
|
||||||
var len = 0
|
|
||||||
try {
|
|
||||||
val file = FileInputStream(path)
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
len = file.read(buf)
|
|
||||||
if (len != BUFSIZE) break
|
|
||||||
digest.update(buf)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
file.close()
|
|
||||||
}
|
|
||||||
digest.update(buf, 0, len)
|
|
||||||
hashes[i] = digest.digest()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// skip this file
|
|
||||||
Log.w(TAG, "Failed to hash file ${args[i]}: $e")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.success(hashes.asList())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> result.notImplemented()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val TAG = "BackgroundServicePlugin"
|
|
||||||
private const val BUFSIZE = 2*1024*1024;
|
|
||||||
@@ -1,362 +0,0 @@
|
|||||||
package app.alextran.immich
|
|
||||||
|
|
||||||
import android.app.Notification
|
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.os.PowerManager
|
|
||||||
import android.os.SystemClock
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.concurrent.futures.ResolvableFuture
|
|
||||||
import androidx.work.BackoffPolicy
|
|
||||||
import androidx.work.Constraints
|
|
||||||
import androidx.work.ForegroundInfo
|
|
||||||
import androidx.work.ListenableWorker
|
|
||||||
import androidx.work.NetworkType
|
|
||||||
import androidx.work.WorkerParameters
|
|
||||||
import androidx.work.ExistingWorkPolicy
|
|
||||||
import androidx.work.OneTimeWorkRequest
|
|
||||||
import androidx.work.WorkManager
|
|
||||||
import androidx.work.WorkInfo
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
|
||||||
import io.flutter.embedding.engine.dart.DartExecutor
|
|
||||||
import io.flutter.embedding.engine.loader.FlutterLoader
|
|
||||||
import io.flutter.plugin.common.MethodCall
|
|
||||||
import io.flutter.plugin.common.MethodChannel
|
|
||||||
import io.flutter.view.FlutterCallbackInformation
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Worker executed by Android WorkManager to perform backup in background
|
|
||||||
*
|
|
||||||
* Starts the Dart runtime/engine and calls `_nativeEntry` function in
|
|
||||||
* `background.service.dart` to run the actual backup logic.
|
|
||||||
* Called by Android WorkManager when all constraints for the work are met,
|
|
||||||
* i.e. battery is not low and optionally Wifi and charging are active.
|
|
||||||
*/
|
|
||||||
class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ctx, params), MethodChannel.MethodCallHandler {
|
|
||||||
|
|
||||||
private val resolvableFuture = ResolvableFuture.create<Result>()
|
|
||||||
private var engine: FlutterEngine? = null
|
|
||||||
private lateinit var backgroundChannel: MethodChannel
|
|
||||||
private val notificationManager = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
||||||
private val isIgnoringBatteryOptimizations = isIgnoringBatteryOptimizations(applicationContext)
|
|
||||||
private var timeBackupStarted: Long = 0L
|
|
||||||
private var notificationBuilder: NotificationCompat.Builder? = null
|
|
||||||
private var notificationDetailBuilder: NotificationCompat.Builder? = null
|
|
||||||
private var fgFuture: ListenableFuture<Void>? = null
|
|
||||||
|
|
||||||
override fun startWork(): ListenableFuture<ListenableWorker.Result> {
|
|
||||||
|
|
||||||
Log.d(TAG, "startWork")
|
|
||||||
|
|
||||||
val ctx = applicationContext
|
|
||||||
|
|
||||||
if (!flutterLoader.initialized()) {
|
|
||||||
flutterLoader.startInitialization(ctx)
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
// Create a Notification channel if necessary
|
|
||||||
createChannel()
|
|
||||||
}
|
|
||||||
if (isIgnoringBatteryOptimizations) {
|
|
||||||
// normal background services can only up to 10 minutes
|
|
||||||
// foreground services are allowed to run indefinitely
|
|
||||||
// requires battery optimizations to be disabled (either manually by the user
|
|
||||||
// or by the system learning that immich is important to the user)
|
|
||||||
val title = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
|
||||||
.getString(SHARED_PREF_NOTIFICATION_TITLE, NOTIFICATION_DEFAULT_TITLE)!!
|
|
||||||
showInfo(getInfoBuilder(title, indeterminate=true).build())
|
|
||||||
}
|
|
||||||
engine = FlutterEngine(ctx)
|
|
||||||
|
|
||||||
flutterLoader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) {
|
|
||||||
runDart()
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolvableFuture
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the Dart runtime/engine and calls `_nativeEntry` function in
|
|
||||||
* `background.service.dart` to run the actual backup logic.
|
|
||||||
*/
|
|
||||||
private fun runDart() {
|
|
||||||
val callbackDispatcherHandle = applicationContext.getSharedPreferences(
|
|
||||||
SHARED_PREF_NAME, Context.MODE_PRIVATE).getLong(SHARED_PREF_CALLBACK_KEY, 0L)
|
|
||||||
val callbackInformation = FlutterCallbackInformation.lookupCallbackInformation(callbackDispatcherHandle)
|
|
||||||
val appBundlePath = flutterLoader.findAppBundlePath()
|
|
||||||
|
|
||||||
engine?.let { engine ->
|
|
||||||
backgroundChannel = MethodChannel(engine.dartExecutor, "immich/backgroundChannel")
|
|
||||||
backgroundChannel.setMethodCallHandler(this@BackupWorker)
|
|
||||||
engine.dartExecutor.executeDartCallback(
|
|
||||||
DartExecutor.DartCallback(
|
|
||||||
applicationContext.assets,
|
|
||||||
appBundlePath,
|
|
||||||
callbackInformation
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStopped() {
|
|
||||||
Log.d(TAG, "onStopped")
|
|
||||||
// called when the system has to stop this worker because constraints are
|
|
||||||
// no longer met or the system needs resources for more important tasks
|
|
||||||
Handler(Looper.getMainLooper()).postAtFrontOfQueue {
|
|
||||||
backgroundChannel.invokeMethod("systemStop", null)
|
|
||||||
}
|
|
||||||
waitOnSetForegroundAsync()
|
|
||||||
// cannot await/get(block) on resolvableFuture as its already cancelled (would throw CancellationException)
|
|
||||||
// instead, wait for 5 seconds until forcefully stopping backup work
|
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
|
||||||
stopEngine(null)
|
|
||||||
}, 5000)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun waitOnSetForegroundAsync() {
|
|
||||||
val fgFuture = this.fgFuture
|
|
||||||
if (fgFuture != null && !fgFuture.isCancelled() && !fgFuture.isDone()) {
|
|
||||||
try {
|
|
||||||
fgFuture.get(500, TimeUnit.MILLISECONDS)
|
|
||||||
}
|
|
||||||
catch (e: Exception) {
|
|
||||||
// ignored, there is nothing to be done
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stopEngine(result: Result?) {
|
|
||||||
clearBackgroundNotification()
|
|
||||||
engine?.destroy()
|
|
||||||
engine = null
|
|
||||||
if (result != null) {
|
|
||||||
Log.d(TAG, "stopEngine result=${result}")
|
|
||||||
resolvableFuture.set(result)
|
|
||||||
}
|
|
||||||
waitOnSetForegroundAsync()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, r: MethodChannel.Result) {
|
|
||||||
when (call.method) {
|
|
||||||
"initialized" -> {
|
|
||||||
timeBackupStarted = SystemClock.uptimeMillis()
|
|
||||||
backgroundChannel.invokeMethod(
|
|
||||||
"onAssetsChanged",
|
|
||||||
null,
|
|
||||||
object : MethodChannel.Result {
|
|
||||||
override fun notImplemented() {
|
|
||||||
stopEngine(Result.failure())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
|
|
||||||
stopEngine(Result.failure())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun success(receivedResult: Any?) {
|
|
||||||
val success = receivedResult as Boolean
|
|
||||||
stopEngine(if(success) Result.success() else Result.retry())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
"updateNotification" -> {
|
|
||||||
val args = call.arguments<ArrayList<*>>()!!
|
|
||||||
val title = args.get(0) as String?
|
|
||||||
val content = args.get(1) as String?
|
|
||||||
val progress = args.get(2) as Int
|
|
||||||
val max = args.get(3) as Int
|
|
||||||
val indeterminate = args.get(4) as Boolean
|
|
||||||
val isDetail = args.get(5) as Boolean
|
|
||||||
val onlyIfFG = args.get(6) as Boolean
|
|
||||||
if (!onlyIfFG || isIgnoringBatteryOptimizations) {
|
|
||||||
showInfo(getInfoBuilder(title, content, isDetail, progress, max, indeterminate).build(), isDetail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"showError" -> {
|
|
||||||
val args = call.arguments<ArrayList<*>>()!!
|
|
||||||
val title = args.get(0) as String
|
|
||||||
val content = args.get(1) as String?
|
|
||||||
val individualTag = args.get(2) as String?
|
|
||||||
showError(title, content, individualTag)
|
|
||||||
}
|
|
||||||
"clearErrorNotifications" -> clearErrorNotifications()
|
|
||||||
"hasContentChanged" -> {
|
|
||||||
val lastChange = applicationContext
|
|
||||||
.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
|
||||||
.getLong(SHARED_PREF_LAST_CHANGE, timeBackupStarted)
|
|
||||||
val hasContentChanged = lastChange > timeBackupStarted;
|
|
||||||
timeBackupStarted = SystemClock.uptimeMillis()
|
|
||||||
r.success(hasContentChanged)
|
|
||||||
}
|
|
||||||
else -> r.notImplemented()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showError(title: String, content: String?, individualTag: String?) {
|
|
||||||
val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ERROR_ID)
|
|
||||||
.setContentTitle(title)
|
|
||||||
.setTicker(title)
|
|
||||||
.setContentText(content)
|
|
||||||
.setSmallIcon(R.mipmap.ic_launcher)
|
|
||||||
.build()
|
|
||||||
notificationManager.notify(individualTag, NOTIFICATION_ERROR_ID, notification)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearErrorNotifications() {
|
|
||||||
notificationManager.cancel(NOTIFICATION_ERROR_ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearBackgroundNotification() {
|
|
||||||
notificationManager.cancel(NOTIFICATION_ID)
|
|
||||||
notificationManager.cancel(NOTIFICATION_DETAIL_ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showInfo(notification: Notification, isDetail: Boolean = false) {
|
|
||||||
val id = if(isDetail) NOTIFICATION_DETAIL_ID else NOTIFICATION_ID
|
|
||||||
if (isIgnoringBatteryOptimizations && !isDetail) {
|
|
||||||
fgFuture = setForegroundAsync(ForegroundInfo(id, notification))
|
|
||||||
} else {
|
|
||||||
notificationManager.notify(id, notification)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getInfoBuilder(
|
|
||||||
title: String? = null,
|
|
||||||
content: String? = null,
|
|
||||||
isDetail: Boolean = false,
|
|
||||||
progress: Int = 0,
|
|
||||||
max: Int = 0,
|
|
||||||
indeterminate: Boolean = false,
|
|
||||||
): NotificationCompat.Builder {
|
|
||||||
var builder = if(isDetail) notificationDetailBuilder else notificationBuilder
|
|
||||||
if (builder == null) {
|
|
||||||
builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
|
|
||||||
.setSmallIcon(R.mipmap.ic_launcher)
|
|
||||||
.setOnlyAlertOnce(true)
|
|
||||||
.setOngoing(true)
|
|
||||||
if (isDetail) {
|
|
||||||
notificationDetailBuilder = builder
|
|
||||||
} else {
|
|
||||||
notificationBuilder = builder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (title != null) {
|
|
||||||
builder.setTicker(title).setContentTitle(title)
|
|
||||||
}
|
|
||||||
if (content != null) {
|
|
||||||
builder.setContentText(content)
|
|
||||||
}
|
|
||||||
return builder.setProgress(max, progress, indeterminate)
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
private fun createChannel() {
|
|
||||||
val foreground = NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW)
|
|
||||||
notificationManager.createNotificationChannel(foreground)
|
|
||||||
val error = NotificationChannel(NOTIFICATION_CHANNEL_ERROR_ID, NOTIFICATION_CHANNEL_ERROR_ID, NotificationManager.IMPORTANCE_HIGH)
|
|
||||||
notificationManager.createNotificationChannel(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val SHARED_PREF_NAME = "immichBackgroundService"
|
|
||||||
const val SHARED_PREF_CALLBACK_KEY = "callbackDispatcherHandle"
|
|
||||||
const val SHARED_PREF_NOTIFICATION_TITLE = "notificationTitle"
|
|
||||||
const val SHARED_PREF_LAST_CHANGE = "lastChange"
|
|
||||||
|
|
||||||
private const val TASK_NAME_BACKUP = "immich/BackupWorker"
|
|
||||||
private const val NOTIFICATION_CHANNEL_ID = "immich/backgroundService"
|
|
||||||
private const val NOTIFICATION_CHANNEL_ERROR_ID = "immich/backgroundServiceError"
|
|
||||||
private const val NOTIFICATION_DEFAULT_TITLE = "Immich"
|
|
||||||
private const val NOTIFICATION_ID = 1
|
|
||||||
private const val NOTIFICATION_ERROR_ID = 2
|
|
||||||
private const val NOTIFICATION_DETAIL_ID = 3
|
|
||||||
private const val ONE_MINUTE = 60000L
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enqueues the BackupWorker to run once the constraints are met
|
|
||||||
*/
|
|
||||||
fun enqueueBackupWorker(context: Context,
|
|
||||||
requireWifi: Boolean = false,
|
|
||||||
requireCharging: Boolean = false,
|
|
||||||
delayMilliseconds: Long = 0L) {
|
|
||||||
val workRequest = buildWorkRequest(requireWifi, requireCharging, delayMilliseconds)
|
|
||||||
WorkManager.getInstance(context).enqueueUniqueWork(TASK_NAME_BACKUP, ExistingWorkPolicy.KEEP, workRequest)
|
|
||||||
Log.d(TAG, "enqueueBackupWorker: BackupWorker enqueued")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the constraints of an already enqueued BackupWorker
|
|
||||||
*/
|
|
||||||
fun updateBackupWorker(context: Context,
|
|
||||||
requireWifi: Boolean = false,
|
|
||||||
requireCharging: Boolean = false) {
|
|
||||||
try {
|
|
||||||
val wm = WorkManager.getInstance(context)
|
|
||||||
val workInfoFuture = wm.getWorkInfosForUniqueWork(TASK_NAME_BACKUP)
|
|
||||||
val workInfoList = workInfoFuture.get(1000, TimeUnit.MILLISECONDS)
|
|
||||||
if (workInfoList != null) {
|
|
||||||
for (workInfo in workInfoList) {
|
|
||||||
if (workInfo.state == WorkInfo.State.ENQUEUED) {
|
|
||||||
val workRequest = buildWorkRequest(requireWifi, requireCharging)
|
|
||||||
wm.enqueueUniqueWork(TASK_NAME_BACKUP, ExistingWorkPolicy.REPLACE, workRequest)
|
|
||||||
Log.d(TAG, "updateBackupWorker updated BackupWorker constraints")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.d(TAG, "updateBackupWorker: BackupWorker not enqueued")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d(TAG, "updateBackupWorker failed: ${e}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the currently running worker (if any) and removes it from the work queue
|
|
||||||
*/
|
|
||||||
fun stopWork(context: Context) {
|
|
||||||
WorkManager.getInstance(context).cancelUniqueWork(TASK_NAME_BACKUP)
|
|
||||||
Log.d(TAG, "stopWork: BackupWorker cancelled")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns `true` if the app is ignoring battery optimizations
|
|
||||||
*/
|
|
||||||
fun isIgnoringBatteryOptimizations(ctx: Context): Boolean {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
val pwrm = ctx.getSystemService(Context.POWER_SERVICE) as PowerManager
|
|
||||||
val name = ctx.packageName
|
|
||||||
return pwrm.isIgnoringBatteryOptimizations(name)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildWorkRequest(requireWifi: Boolean = false,
|
|
||||||
requireCharging: Boolean = false,
|
|
||||||
delayMilliseconds: Long = 0L): OneTimeWorkRequest {
|
|
||||||
val constraints = Constraints.Builder()
|
|
||||||
.setRequiredNetworkType(if (requireWifi) NetworkType.UNMETERED else NetworkType.CONNECTED)
|
|
||||||
.setRequiresBatteryNotLow(true)
|
|
||||||
.setRequiresCharging(requireCharging)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
val work = OneTimeWorkRequest.Builder(BackupWorker::class.java)
|
|
||||||
.setConstraints(constraints)
|
|
||||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, ONE_MINUTE, TimeUnit.MILLISECONDS)
|
|
||||||
.setInitialDelay(delayMilliseconds, TimeUnit.MILLISECONDS)
|
|
||||||
.build()
|
|
||||||
return work
|
|
||||||
}
|
|
||||||
|
|
||||||
private val flutterLoader = FlutterLoader()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val TAG = "BackupWorker"
|
|
||||||
@@ -10,6 +10,18 @@ allprojects {
|
|||||||
rootProject.buildDir = '../build'
|
rootProject.buildDir = '../build'
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
|
// fix for verifyReleaseResources
|
||||||
|
// ============
|
||||||
|
afterEvaluate { project ->
|
||||||
|
if (project.plugins.hasPlugin("com.android.application") ||
|
||||||
|
project.plugins.hasPlugin("com.android.library")) {
|
||||||
|
project.android {
|
||||||
|
compileSdkVersion 34
|
||||||
|
buildToolsVersion "34.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ============
|
||||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,4 +35,5 @@ tasks.register("clean", Delete) {
|
|||||||
|
|
||||||
tasks.named('wrapper') {
|
tasks.named('wrapper') {
|
||||||
distributionType = Wrapper.DistributionType.ALL
|
distributionType = Wrapper.DistributionType.ALL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 152,
|
"android.injected.version.code" => 155,
|
||||||
"android.injected.version.name" => "1.111.0",
|
"android.injected.version.name" => "1.113.0",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||||
|
|||||||
@@ -54,6 +54,13 @@
|
|||||||
"asset_list_layout_sub_title": "تصميم",
|
"asset_list_layout_sub_title": "تصميم",
|
||||||
"asset_list_settings_subtitle": "إعدادات تخطيط شبكة الصور",
|
"asset_list_settings_subtitle": "إعدادات تخطيط شبكة الصور",
|
||||||
"asset_list_settings_title": "شبكة الصور",
|
"asset_list_settings_title": "شبكة الصور",
|
||||||
|
"asset_restored_successfully": "Asset restored successfully",
|
||||||
|
"assets_deleted_permanently": "{} asset(s) deleted permanently",
|
||||||
|
"assets_deleted_permanently_from_server": "{} asset(s) deleted permanently from the Immich server",
|
||||||
|
"assets_removed_permanently_from_device": "{} asset(s) removed permanently from your device",
|
||||||
|
"assets_restored_successfully": "{} asset(s) restored successfully",
|
||||||
|
"assets_trashed": "{} asset(s) trashed",
|
||||||
|
"assets_trashed_from_server": "{} asset(s) trashed from the Immich server",
|
||||||
"asset_viewer_settings_title": "عارض الأصول",
|
"asset_viewer_settings_title": "عارض الأصول",
|
||||||
"backup_album_selection_page_albums_device": "Albums on device ({})",
|
"backup_album_selection_page_albums_device": "Albums on device ({})",
|
||||||
"backup_album_selection_page_albums_tap": "انقر للتضمين، وانقر نقرًا مزدوجًا للاستثناء",
|
"backup_album_selection_page_albums_tap": "انقر للتضمين، وانقر نقرًا مزدوجًا للاستثناء",
|
||||||
@@ -166,6 +173,7 @@
|
|||||||
"control_bottom_app_bar_delete": "يمسح",
|
"control_bottom_app_bar_delete": "يمسح",
|
||||||
"control_bottom_app_bar_delete_from_immich": " حذف منال تطبيق",
|
"control_bottom_app_bar_delete_from_immich": " حذف منال تطبيق",
|
||||||
"control_bottom_app_bar_delete_from_local": "حذف من الجهاز",
|
"control_bottom_app_bar_delete_from_local": "حذف من الجهاز",
|
||||||
|
"control_bottom_app_bar_download": "Download",
|
||||||
"control_bottom_app_bar_edit": "Edit",
|
"control_bottom_app_bar_edit": "Edit",
|
||||||
"control_bottom_app_bar_edit_location": "تحديد الوجهة",
|
"control_bottom_app_bar_edit_location": "تحديد الوجهة",
|
||||||
"control_bottom_app_bar_edit_time": "تحرير التاريخ والوقت",
|
"control_bottom_app_bar_edit_time": "تحرير التاريخ والوقت",
|
||||||
@@ -182,6 +190,7 @@
|
|||||||
"create_shared_album_page_share": "يشارك",
|
"create_shared_album_page_share": "يشارك",
|
||||||
"create_shared_album_page_share_add_assets": "إضافة الأصول",
|
"create_shared_album_page_share_add_assets": "إضافة الأصول",
|
||||||
"create_shared_album_page_share_select_photos": "حدد الصور",
|
"create_shared_album_page_share_select_photos": "حدد الصور",
|
||||||
|
"crop": "Crop",
|
||||||
"curated_location_page_title": "أماكن",
|
"curated_location_page_title": "أماكن",
|
||||||
"curated_object_page_title": "أشياء",
|
"curated_object_page_title": "أشياء",
|
||||||
"daily_title_text_date": "E ، MMM DD",
|
"daily_title_text_date": "E ، MMM DD",
|
||||||
@@ -203,7 +212,9 @@
|
|||||||
"description_input_submit_error": "خطأ تحديث الوصف ، تحقق من السجل لمزيد من التفاصيل",
|
"description_input_submit_error": "خطأ تحديث الوصف ، تحقق من السجل لمزيد من التفاصيل",
|
||||||
"edit_date_time_dialog_date_time": "التاريخ و الوقت",
|
"edit_date_time_dialog_date_time": "التاريخ و الوقت",
|
||||||
"edit_date_time_dialog_timezone": "وحدة زمنية",
|
"edit_date_time_dialog_timezone": "وحدة زمنية",
|
||||||
|
"edit_image_title": "Edit",
|
||||||
"edit_location_dialog_title": "موقع",
|
"edit_location_dialog_title": "موقع",
|
||||||
|
"error_saving_image": "Error: {}",
|
||||||
"exif_bottom_sheet_description": "اضف وصفا...",
|
"exif_bottom_sheet_description": "اضف وصفا...",
|
||||||
"exif_bottom_sheet_details": "تفاصيل",
|
"exif_bottom_sheet_details": "تفاصيل",
|
||||||
"exif_bottom_sheet_location": "موقع",
|
"exif_bottom_sheet_location": "موقع",
|
||||||
@@ -240,6 +251,7 @@
|
|||||||
"home_page_first_time_notice": "إذا كانت هذه هي المرة الأولى التي تستخدم فيها التطبيق، فيرجى التأكد من اختيار ألبوم (ألبومات) احتياطية حتى يتمكن المخطط الزمني من ملء الصور ومقاطع الفيديو في الألبوم (الألبومات).",
|
"home_page_first_time_notice": "إذا كانت هذه هي المرة الأولى التي تستخدم فيها التطبيق، فيرجى التأكد من اختيار ألبوم (ألبومات) احتياطية حتى يتمكن المخطط الزمني من ملء الصور ومقاطع الفيديو في الألبوم (الألبومات).",
|
||||||
"home_page_share_err_local": "لا يمكن مشاركة الأصول المحلية عبر الرابط ، سوف يتخطى",
|
"home_page_share_err_local": "لا يمكن مشاركة الأصول المحلية عبر الرابط ، سوف يتخطى",
|
||||||
"home_page_upload_err_limit": "لا يمكن إلا تحميل 30 أحد الأصول في وقت واحد ، سوف يتخطى",
|
"home_page_upload_err_limit": "لا يمكن إلا تحميل 30 أحد الأصول في وقت واحد ، سوف يتخطى",
|
||||||
|
"image_saved_successfully": "Image saved",
|
||||||
"image_viewer_page_state_provider_download_error": "خطا في التحميل",
|
"image_viewer_page_state_provider_download_error": "خطا في التحميل",
|
||||||
"image_viewer_page_state_provider_download_started": "بدأ التنزيل",
|
"image_viewer_page_state_provider_download_started": "بدأ التنزيل",
|
||||||
"image_viewer_page_state_provider_download_success": "تم التنزيل بنجاح",
|
"image_viewer_page_state_provider_download_success": "تم التنزيل بنجاح",
|
||||||
@@ -368,6 +380,7 @@
|
|||||||
"profile_drawer_sign_out": "خروج",
|
"profile_drawer_sign_out": "خروج",
|
||||||
"profile_drawer_trash": "نفايات",
|
"profile_drawer_trash": "نفايات",
|
||||||
"recently_added_page_title": "أضيف مؤخرا",
|
"recently_added_page_title": "أضيف مؤخرا",
|
||||||
|
"save_to_gallery": "Save to gallery",
|
||||||
"scaffold_body_error_occurred": "حدث خطأ",
|
"scaffold_body_error_occurred": "حدث خطأ",
|
||||||
"search_bar_hint": "ابحث عن صورك",
|
"search_bar_hint": "ابحث عن صورك",
|
||||||
"search_filter_apply": "اختار الفلتر ",
|
"search_filter_apply": "اختار الفلتر ",
|
||||||
@@ -522,21 +535,31 @@
|
|||||||
"sharing_silver_appbar_create_shared_album": "ألبوم مشترك جديد",
|
"sharing_silver_appbar_create_shared_album": "ألبوم مشترك جديد",
|
||||||
"sharing_silver_appbar_shared_links": "روابط مشتركة",
|
"sharing_silver_appbar_shared_links": "روابط مشتركة",
|
||||||
"sharing_silver_appbar_share_partner": "شارك مع الشريك",
|
"sharing_silver_appbar_share_partner": "شارك مع الشريك",
|
||||||
|
"sync": "Sync",
|
||||||
|
"sync_albums": "Sync albums",
|
||||||
|
"sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums",
|
||||||
|
"sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich",
|
||||||
"tab_controller_nav_library": "مكتبة",
|
"tab_controller_nav_library": "مكتبة",
|
||||||
"tab_controller_nav_photos": "الصور",
|
"tab_controller_nav_photos": "الصور",
|
||||||
"tab_controller_nav_search": "يبحث",
|
"tab_controller_nav_search": "يبحث",
|
||||||
"tab_controller_nav_sharing": "مشاركة",
|
"tab_controller_nav_sharing": "مشاركة",
|
||||||
"theme_setting_asset_list_storage_indicator_title": "عرض مؤشر التخزين على بلاط الأصول",
|
"theme_setting_asset_list_storage_indicator_title": "عرض مؤشر التخزين على بلاط الأصول",
|
||||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||||
|
"theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.",
|
||||||
|
"theme_setting_colorful_interface_title": "Colorful interface",
|
||||||
"theme_setting_dark_mode_switch": "الوضع المظلم",
|
"theme_setting_dark_mode_switch": "الوضع المظلم",
|
||||||
"theme_setting_image_viewer_quality_subtitle": "اضبط جودة عارض الصورة التفصيلية",
|
"theme_setting_image_viewer_quality_subtitle": "اضبط جودة عارض الصورة التفصيلية",
|
||||||
"theme_setting_image_viewer_quality_title": "جودة عارض الصورة",
|
"theme_setting_image_viewer_quality_title": "جودة عارض الصورة",
|
||||||
|
"theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.",
|
||||||
|
"theme_setting_primary_color_title": "Primary color",
|
||||||
|
"theme_setting_system_primary_color_title": "Use system color",
|
||||||
"theme_setting_system_theme_switch": "تلقائي (اتبع إعداد النظام)",
|
"theme_setting_system_theme_switch": "تلقائي (اتبع إعداد النظام)",
|
||||||
"theme_setting_theme_subtitle": "اختر إعدادات مظهر التطبيق",
|
"theme_setting_theme_subtitle": "اختر إعدادات مظهر التطبيق",
|
||||||
"theme_setting_theme_title": "مظهر",
|
"theme_setting_theme_title": "مظهر",
|
||||||
"theme_setting_three_stage_loading_subtitle": "قد يزيد التحميل من ثلاث مراحل من أداء التحميل ولكنه يسبب تحميل شبكة أعلى بكثير",
|
"theme_setting_three_stage_loading_subtitle": "قد يزيد التحميل من ثلاث مراحل من أداء التحميل ولكنه يسبب تحميل شبكة أعلى بكثير",
|
||||||
"theme_setting_three_stage_loading_title": "تمكين تحميل ثلاث مراحل",
|
"theme_setting_three_stage_loading_title": "تمكين تحميل ثلاث مراحل",
|
||||||
"translated_text_options": "خيارات",
|
"translated_text_options": "خيارات",
|
||||||
|
"trash_emptied": "Emptied trash",
|
||||||
"trash_page_delete": "مسح",
|
"trash_page_delete": "مسح",
|
||||||
"trash_page_delete_all": "حذف الكل",
|
"trash_page_delete_all": "حذف الكل",
|
||||||
"trash_page_empty_trash_btn": "افرغ سله المهملات ",
|
"trash_page_empty_trash_btn": "افرغ سله المهملات ",
|
||||||
|
|||||||
@@ -54,6 +54,13 @@
|
|||||||
"asset_list_layout_sub_title": "Rozložení",
|
"asset_list_layout_sub_title": "Rozložení",
|
||||||
"asset_list_settings_subtitle": "Nastavení rozložení mřížky fotografií",
|
"asset_list_settings_subtitle": "Nastavení rozložení mřížky fotografií",
|
||||||
"asset_list_settings_title": "Fotografická mřížka",
|
"asset_list_settings_title": "Fotografická mřížka",
|
||||||
|
"asset_restored_successfully": "Položka úspěšně obnovena",
|
||||||
|
"assets_deleted_permanently": "{} položek trvale odstraněno",
|
||||||
|
"assets_deleted_permanently_from_server": "{} položek trvale odstraněno z Immich serveru",
|
||||||
|
"assets_removed_permanently_from_device": "{} položek trvale odstraněno z vašeho zařízení",
|
||||||
|
"assets_restored_successfully": "{} položek úspěšně obnoveno",
|
||||||
|
"assets_trashed": "{} položek vyhozeno do koše",
|
||||||
|
"assets_trashed_from_server": "{} položek vyhozeno do koše na Immich serveru",
|
||||||
"asset_viewer_settings_title": "Prohlížeč",
|
"asset_viewer_settings_title": "Prohlížeč",
|
||||||
"backup_album_selection_page_albums_device": "Alba v zařízení ({})",
|
"backup_album_selection_page_albums_device": "Alba v zařízení ({})",
|
||||||
"backup_album_selection_page_albums_tap": "Klepnutím na položku ji zahrnete, dvojím klepnutím ji vyloučíte",
|
"backup_album_selection_page_albums_tap": "Klepnutím na položku ji zahrnete, dvojím klepnutím ji vyloučíte",
|
||||||
@@ -166,6 +173,7 @@
|
|||||||
"control_bottom_app_bar_delete": "Smazat",
|
"control_bottom_app_bar_delete": "Smazat",
|
||||||
"control_bottom_app_bar_delete_from_immich": "Smazat ze serveru Immich",
|
"control_bottom_app_bar_delete_from_immich": "Smazat ze serveru Immich",
|
||||||
"control_bottom_app_bar_delete_from_local": "Smazat ze zařízení",
|
"control_bottom_app_bar_delete_from_local": "Smazat ze zařízení",
|
||||||
|
"control_bottom_app_bar_download": "Stáhnout",
|
||||||
"control_bottom_app_bar_edit": "Upravit",
|
"control_bottom_app_bar_edit": "Upravit",
|
||||||
"control_bottom_app_bar_edit_location": "Upravit polohu",
|
"control_bottom_app_bar_edit_location": "Upravit polohu",
|
||||||
"control_bottom_app_bar_edit_time": "Upravit datum a čas",
|
"control_bottom_app_bar_edit_time": "Upravit datum a čas",
|
||||||
@@ -182,6 +190,7 @@
|
|||||||
"create_shared_album_page_share": "Sdílet",
|
"create_shared_album_page_share": "Sdílet",
|
||||||
"create_shared_album_page_share_add_assets": "PŘIDAT POLOŽKY",
|
"create_shared_album_page_share_add_assets": "PŘIDAT POLOŽKY",
|
||||||
"create_shared_album_page_share_select_photos": "Vybrat fotografie",
|
"create_shared_album_page_share_select_photos": "Vybrat fotografie",
|
||||||
|
"crop": "Oříznout",
|
||||||
"curated_location_page_title": "Místa",
|
"curated_location_page_title": "Místa",
|
||||||
"curated_object_page_title": "Věci",
|
"curated_object_page_title": "Věci",
|
||||||
"daily_title_text_date": "EEEE, d. MMMM",
|
"daily_title_text_date": "EEEE, d. MMMM",
|
||||||
@@ -203,7 +212,9 @@
|
|||||||
"description_input_submit_error": "Chyba aktualizace popisu, další podrobnosti najdete v logu",
|
"description_input_submit_error": "Chyba aktualizace popisu, další podrobnosti najdete v logu",
|
||||||
"edit_date_time_dialog_date_time": "Datum a čas",
|
"edit_date_time_dialog_date_time": "Datum a čas",
|
||||||
"edit_date_time_dialog_timezone": "Časové pásmo",
|
"edit_date_time_dialog_timezone": "Časové pásmo",
|
||||||
|
"edit_image_title": "Upravit",
|
||||||
"edit_location_dialog_title": "Poloha",
|
"edit_location_dialog_title": "Poloha",
|
||||||
|
"error_saving_image": "Chyba: {}",
|
||||||
"exif_bottom_sheet_description": "Přidat popis...",
|
"exif_bottom_sheet_description": "Přidat popis...",
|
||||||
"exif_bottom_sheet_details": "PODROBNOSTI",
|
"exif_bottom_sheet_details": "PODROBNOSTI",
|
||||||
"exif_bottom_sheet_location": "POLOHA",
|
"exif_bottom_sheet_location": "POLOHA",
|
||||||
@@ -240,6 +251,7 @@
|
|||||||
"home_page_first_time_notice": "Pokud aplikaci používáte poprvé, nezapomeňte si vybrat zálohovaná alba, aby se na časové ose mohly nacházet fotografie a videa z vybraných alb.",
|
"home_page_first_time_notice": "Pokud aplikaci používáte poprvé, nezapomeňte si vybrat zálohovaná alba, aby se na časové ose mohly nacházet fotografie a videa z vybraných alb.",
|
||||||
"home_page_share_err_local": "Nelze sdílet místní položky prostřednictvím odkazu, přeskakuji",
|
"home_page_share_err_local": "Nelze sdílet místní položky prostřednictvím odkazu, přeskakuji",
|
||||||
"home_page_upload_err_limit": "Lze nahrát nejvýše 30 položek najednou, přeskakuji",
|
"home_page_upload_err_limit": "Lze nahrát nejvýše 30 položek najednou, přeskakuji",
|
||||||
|
"image_saved_successfully": "Obrázek uložen",
|
||||||
"image_viewer_page_state_provider_download_error": "Chyba stahování",
|
"image_viewer_page_state_provider_download_error": "Chyba stahování",
|
||||||
"image_viewer_page_state_provider_download_started": "Stahování zahájeno",
|
"image_viewer_page_state_provider_download_started": "Stahování zahájeno",
|
||||||
"image_viewer_page_state_provider_download_success": "Stahování bylo úspěšné",
|
"image_viewer_page_state_provider_download_success": "Stahování bylo úspěšné",
|
||||||
@@ -368,6 +380,7 @@
|
|||||||
"profile_drawer_sign_out": "Odhlásit se",
|
"profile_drawer_sign_out": "Odhlásit se",
|
||||||
"profile_drawer_trash": "Vyhodit",
|
"profile_drawer_trash": "Vyhodit",
|
||||||
"recently_added_page_title": "Nedávno přidané",
|
"recently_added_page_title": "Nedávno přidané",
|
||||||
|
"save_to_gallery": "Uložit do galerie",
|
||||||
"scaffold_body_error_occurred": "Došlo k chybě",
|
"scaffold_body_error_occurred": "Došlo k chybě",
|
||||||
"search_bar_hint": "Prohledejte své fotky",
|
"search_bar_hint": "Prohledejte své fotky",
|
||||||
"search_filter_apply": "Použít filtr",
|
"search_filter_apply": "Použít filtr",
|
||||||
@@ -522,21 +535,31 @@
|
|||||||
"sharing_silver_appbar_create_shared_album": "Vytvořit sdílené album",
|
"sharing_silver_appbar_create_shared_album": "Vytvořit sdílené album",
|
||||||
"sharing_silver_appbar_shared_links": "Sdílené odkazy",
|
"sharing_silver_appbar_shared_links": "Sdílené odkazy",
|
||||||
"sharing_silver_appbar_share_partner": "Sdílet s partnerem",
|
"sharing_silver_appbar_share_partner": "Sdílet s partnerem",
|
||||||
|
"sync": "Synchronizace",
|
||||||
|
"sync_albums": "Synchronizovat alba",
|
||||||
|
"sync_albums_manual_subtitle": "Synchronizovat všechna nahraná videa a fotografie do vybraných záložních alb",
|
||||||
|
"sync_upload_album_setting_subtitle": "Vytvořit a nahrát fotografie a videa do vybraných alb na Immich",
|
||||||
"tab_controller_nav_library": "Knihovna",
|
"tab_controller_nav_library": "Knihovna",
|
||||||
"tab_controller_nav_photos": "Fotografie",
|
"tab_controller_nav_photos": "Fotografie",
|
||||||
"tab_controller_nav_search": "Vyhledávání",
|
"tab_controller_nav_search": "Vyhledávání",
|
||||||
"tab_controller_nav_sharing": "Sdílení",
|
"tab_controller_nav_sharing": "Sdílení",
|
||||||
"theme_setting_asset_list_storage_indicator_title": "Zobrazit indikátor úložiště na dlaždicích položek",
|
"theme_setting_asset_list_storage_indicator_title": "Zobrazit indikátor úložiště na dlaždicích položek",
|
||||||
"theme_setting_asset_list_tiles_per_row_title": "Počet položek na řádek ({})",
|
"theme_setting_asset_list_tiles_per_row_title": "Počet položek na řádek ({})",
|
||||||
|
"theme_setting_colorful_interface_subtitle": "Použít hlavní barvu na povrchy pozadí.",
|
||||||
|
"theme_setting_colorful_interface_title": "Barevné rozhraní",
|
||||||
"theme_setting_dark_mode_switch": "Tmavé téma",
|
"theme_setting_dark_mode_switch": "Tmavé téma",
|
||||||
"theme_setting_image_viewer_quality_subtitle": "Přizpůsobení kvality detailů prohlížeče obrázků",
|
"theme_setting_image_viewer_quality_subtitle": "Přizpůsobení kvality detailů prohlížeče obrázků",
|
||||||
"theme_setting_image_viewer_quality_title": "Kvalita prohlížeče obrázků",
|
"theme_setting_image_viewer_quality_title": "Kvalita prohlížeče obrázků",
|
||||||
|
"theme_setting_primary_color_subtitle": "Zvolte barvu pro hlavní akce a zvýraznění.",
|
||||||
|
"theme_setting_primary_color_title": "Hlavní barva",
|
||||||
|
"theme_setting_system_primary_color_title": "Použití systémové barvy",
|
||||||
"theme_setting_system_theme_switch": "Automaticky (podle systemového nastavení)",
|
"theme_setting_system_theme_switch": "Automaticky (podle systemového nastavení)",
|
||||||
"theme_setting_theme_subtitle": "Vyberte nastavení tématu aplikace",
|
"theme_setting_theme_subtitle": "Vyberte nastavení tématu aplikace",
|
||||||
"theme_setting_theme_title": "Téma",
|
"theme_setting_theme_title": "Téma",
|
||||||
"theme_setting_three_stage_loading_subtitle": "Třístupňové načítání může zvýšit výkonnost načítání, ale vede k výrazně vyššímu zatížení sítě.",
|
"theme_setting_three_stage_loading_subtitle": "Třístupňové načítání může zvýšit výkonnost načítání, ale vede k výrazně vyššímu zatížení sítě.",
|
||||||
"theme_setting_three_stage_loading_title": "Povolení třístupňového načítání",
|
"theme_setting_three_stage_loading_title": "Povolení třístupňového načítání",
|
||||||
"translated_text_options": "Možnosti",
|
"translated_text_options": "Možnosti",
|
||||||
|
"trash_emptied": "Koš vyprázdněn",
|
||||||
"trash_page_delete": "Smazat",
|
"trash_page_delete": "Smazat",
|
||||||
"trash_page_delete_all": "Smazat všechny",
|
"trash_page_delete_all": "Smazat všechny",
|
||||||
"trash_page_empty_trash_btn": "Vysypat koš",
|
"trash_page_empty_trash_btn": "Vysypat koš",
|
||||||
|
|||||||
@@ -54,6 +54,13 @@
|
|||||||
"asset_list_layout_sub_title": "Layout",
|
"asset_list_layout_sub_title": "Layout",
|
||||||
"asset_list_settings_subtitle": "Indstillinger for billedgitterlayout",
|
"asset_list_settings_subtitle": "Indstillinger for billedgitterlayout",
|
||||||
"asset_list_settings_title": "Billedgitter",
|
"asset_list_settings_title": "Billedgitter",
|
||||||
|
"asset_restored_successfully": "Elementet blev gendannet succesfuldt",
|
||||||
|
"assets_deleted_permanently": "{} element(er) blev fjernet permanent",
|
||||||
|
"assets_deleted_permanently_from_server": "{} element(er) blev fjernet permanent fra serveren",
|
||||||
|
"assets_removed_permanently_from_device": "{} element(er) blev fjernet permanent fra din enhed",
|
||||||
|
"assets_restored_successfully": "{} element(er) blev gendannet succesfuldt",
|
||||||
|
"assets_trashed": "{} element(er) blev smidt i papirkurven",
|
||||||
|
"assets_trashed_from_server": "{} element(er) blev smidt i serverens papirkurv",
|
||||||
"asset_viewer_settings_title": "Billedviser",
|
"asset_viewer_settings_title": "Billedviser",
|
||||||
"backup_album_selection_page_albums_device": "Albummer på enhed ({})",
|
"backup_album_selection_page_albums_device": "Albummer på enhed ({})",
|
||||||
"backup_album_selection_page_albums_tap": "Tryk en gang for at inkludere, tryk to gange for at ekskludere",
|
"backup_album_selection_page_albums_tap": "Tryk en gang for at inkludere, tryk to gange for at ekskludere",
|
||||||
@@ -166,7 +173,8 @@
|
|||||||
"control_bottom_app_bar_delete": "Slet",
|
"control_bottom_app_bar_delete": "Slet",
|
||||||
"control_bottom_app_bar_delete_from_immich": "Slet fra Immich",
|
"control_bottom_app_bar_delete_from_immich": "Slet fra Immich",
|
||||||
"control_bottom_app_bar_delete_from_local": "Slet fra enhed",
|
"control_bottom_app_bar_delete_from_local": "Slet fra enhed",
|
||||||
"control_bottom_app_bar_edit": "Edit",
|
"control_bottom_app_bar_download": "Hent",
|
||||||
|
"control_bottom_app_bar_edit": "Rediger",
|
||||||
"control_bottom_app_bar_edit_location": "Rediger placering",
|
"control_bottom_app_bar_edit_location": "Rediger placering",
|
||||||
"control_bottom_app_bar_edit_time": "Rediger tid og dato",
|
"control_bottom_app_bar_edit_time": "Rediger tid og dato",
|
||||||
"control_bottom_app_bar_favorite": "Favorit",
|
"control_bottom_app_bar_favorite": "Favorit",
|
||||||
@@ -182,6 +190,7 @@
|
|||||||
"create_shared_album_page_share": "Del",
|
"create_shared_album_page_share": "Del",
|
||||||
"create_shared_album_page_share_add_assets": "TILFØJ ELEMENT",
|
"create_shared_album_page_share_add_assets": "TILFØJ ELEMENT",
|
||||||
"create_shared_album_page_share_select_photos": "Vælg billeder",
|
"create_shared_album_page_share_select_photos": "Vælg billeder",
|
||||||
|
"crop": "Beskær",
|
||||||
"curated_location_page_title": "Steder",
|
"curated_location_page_title": "Steder",
|
||||||
"curated_object_page_title": "Ting",
|
"curated_object_page_title": "Ting",
|
||||||
"daily_title_text_date": "E, dd MMM",
|
"daily_title_text_date": "E, dd MMM",
|
||||||
@@ -203,7 +212,9 @@
|
|||||||
"description_input_submit_error": "Fejl med at opdatere beskrivelsen. Tjek loggen for flere detaljer",
|
"description_input_submit_error": "Fejl med at opdatere beskrivelsen. Tjek loggen for flere detaljer",
|
||||||
"edit_date_time_dialog_date_time": "Dato og klokkeslæt",
|
"edit_date_time_dialog_date_time": "Dato og klokkeslæt",
|
||||||
"edit_date_time_dialog_timezone": "Tidszone",
|
"edit_date_time_dialog_timezone": "Tidszone",
|
||||||
|
"edit_image_title": "Rediger",
|
||||||
"edit_location_dialog_title": "Placering",
|
"edit_location_dialog_title": "Placering",
|
||||||
|
"error_saving_image": "Fejl: {}",
|
||||||
"exif_bottom_sheet_description": "Tilføj beskrivelse...",
|
"exif_bottom_sheet_description": "Tilføj beskrivelse...",
|
||||||
"exif_bottom_sheet_details": "DETALJER",
|
"exif_bottom_sheet_details": "DETALJER",
|
||||||
"exif_bottom_sheet_location": "LOKATION",
|
"exif_bottom_sheet_location": "LOKATION",
|
||||||
@@ -240,6 +251,7 @@
|
|||||||
"home_page_first_time_notice": "Hvis det er din første gang i appen, bedes du vælge en sikkerhedskopi af albummer så tidlinjen kan blive fyldt med billeder og videoer fra albummerne.",
|
"home_page_first_time_notice": "Hvis det er din første gang i appen, bedes du vælge en sikkerhedskopi af albummer så tidlinjen kan blive fyldt med billeder og videoer fra albummerne.",
|
||||||
"home_page_share_err_local": "Kan ikke dele lokale elementer via link, springer over",
|
"home_page_share_err_local": "Kan ikke dele lokale elementer via link, springer over",
|
||||||
"home_page_upload_err_limit": "Det er kun muligt at lave sikkerhedskopi af 30 elementer ad gangen. Springer over",
|
"home_page_upload_err_limit": "Det er kun muligt at lave sikkerhedskopi af 30 elementer ad gangen. Springer over",
|
||||||
|
"image_saved_successfully": "Billede gemt",
|
||||||
"image_viewer_page_state_provider_download_error": "Fejl ved download",
|
"image_viewer_page_state_provider_download_error": "Fejl ved download",
|
||||||
"image_viewer_page_state_provider_download_started": "Download startet",
|
"image_viewer_page_state_provider_download_started": "Download startet",
|
||||||
"image_viewer_page_state_provider_download_success": "Download succesfuld",
|
"image_viewer_page_state_provider_download_success": "Download succesfuld",
|
||||||
@@ -327,7 +339,7 @@
|
|||||||
"multiselect_grid_edit_date_time_err_read_only": "Kan ikke redigere datoen på kun læselige elementer. Springer over",
|
"multiselect_grid_edit_date_time_err_read_only": "Kan ikke redigere datoen på kun læselige elementer. Springer over",
|
||||||
"multiselect_grid_edit_gps_err_read_only": "Kan ikke redigere lokation af kun læselige elementer. Springer over",
|
"multiselect_grid_edit_gps_err_read_only": "Kan ikke redigere lokation af kun læselige elementer. Springer over",
|
||||||
"no_assets_to_show": "Ingen elementer at vise",
|
"no_assets_to_show": "Ingen elementer at vise",
|
||||||
"no_name": "No name",
|
"no_name": "Intet navn",
|
||||||
"notification_permission_dialog_cancel": "Annuller",
|
"notification_permission_dialog_cancel": "Annuller",
|
||||||
"notification_permission_dialog_content": "Gå til indstillinger for at slå notifikationer til.",
|
"notification_permission_dialog_content": "Gå til indstillinger for at slå notifikationer til.",
|
||||||
"notification_permission_dialog_settings": "Indstillinger",
|
"notification_permission_dialog_settings": "Indstillinger",
|
||||||
@@ -368,33 +380,34 @@
|
|||||||
"profile_drawer_sign_out": "Log ud",
|
"profile_drawer_sign_out": "Log ud",
|
||||||
"profile_drawer_trash": "Papirkurv",
|
"profile_drawer_trash": "Papirkurv",
|
||||||
"recently_added_page_title": "Nyligt tilføjet",
|
"recently_added_page_title": "Nyligt tilføjet",
|
||||||
|
"save_to_gallery": "Gem til galleri",
|
||||||
"scaffold_body_error_occurred": "Der opstod en fejl",
|
"scaffold_body_error_occurred": "Der opstod en fejl",
|
||||||
"search_bar_hint": "Søg i dine billeder",
|
"search_bar_hint": "Søg i dine billeder",
|
||||||
"search_filter_apply": "Tilføj filter",
|
"search_filter_apply": "Tilføj filter",
|
||||||
"search_filter_camera": "Camera",
|
"search_filter_camera": "Kamera",
|
||||||
"search_filter_camera_make": "Producent",
|
"search_filter_camera_make": "Producent",
|
||||||
"search_filter_camera_model": "Model",
|
"search_filter_camera_model": "Model",
|
||||||
"search_filter_camera_title": "Select camera type",
|
"search_filter_camera_title": "Vælg type af kamera",
|
||||||
"search_filter_date": "Date",
|
"search_filter_date": "Dato",
|
||||||
"search_filter_date_interval": "{start} to {end}",
|
"search_filter_date_interval": "{start} til { slut}",
|
||||||
"search_filter_date_title": "Select a date range",
|
"search_filter_date_title": "Vælg et datointerval",
|
||||||
"search_filter_display_option_archive": "Arkiv",
|
"search_filter_display_option_archive": "Arkiv",
|
||||||
"search_filter_display_option_favorite": "Favorit",
|
"search_filter_display_option_favorite": "Favorit",
|
||||||
"search_filter_display_option_not_in_album": "Ikke i album",
|
"search_filter_display_option_not_in_album": "Ikke i album",
|
||||||
"search_filter_display_options": "Display Options",
|
"search_filter_display_options": "Visningsindstillinger",
|
||||||
"search_filter_display_options_title": "Display options",
|
"search_filter_display_options_title": "Visningsindstillinger",
|
||||||
"search_filter_location": "Location",
|
"search_filter_location": "Lokation",
|
||||||
"search_filter_location_city": "By",
|
"search_filter_location_city": "By",
|
||||||
"search_filter_location_country": "Land",
|
"search_filter_location_country": "Land",
|
||||||
"search_filter_location_state": "Stat",
|
"search_filter_location_state": "Stat",
|
||||||
"search_filter_location_title": "Select location",
|
"search_filter_location_title": "Vælg lokation",
|
||||||
"search_filter_media_type": "Media Type",
|
"search_filter_media_type": "Medietype",
|
||||||
"search_filter_media_type_all": "Alle",
|
"search_filter_media_type_all": "Alle",
|
||||||
"search_filter_media_type_image": "Billede",
|
"search_filter_media_type_image": "Billede",
|
||||||
"search_filter_media_type_title": "Select media type",
|
"search_filter_media_type_title": "Vælg medietype",
|
||||||
"search_filter_media_type_video": "Video",
|
"search_filter_media_type_video": "Video",
|
||||||
"search_filter_people": "People",
|
"search_filter_people": "Personer",
|
||||||
"search_filter_people_title": "Select people",
|
"search_filter_people_title": "Vælg personer",
|
||||||
"search_page_categories": "Kategorier",
|
"search_page_categories": "Kategorier",
|
||||||
"search_page_favorites": "Favoritter",
|
"search_page_favorites": "Favoritter",
|
||||||
"search_page_motion_photos": "Bevægelsesbilleder",
|
"search_page_motion_photos": "Bevægelsesbilleder",
|
||||||
@@ -522,21 +535,31 @@
|
|||||||
"sharing_silver_appbar_create_shared_album": "Opret delt album",
|
"sharing_silver_appbar_create_shared_album": "Opret delt album",
|
||||||
"sharing_silver_appbar_shared_links": "Delte links",
|
"sharing_silver_appbar_shared_links": "Delte links",
|
||||||
"sharing_silver_appbar_share_partner": "Del med partner",
|
"sharing_silver_appbar_share_partner": "Del med partner",
|
||||||
|
"sync": "Synkroniser",
|
||||||
|
"sync_albums": "Synkroniser albummer",
|
||||||
|
"sync_albums_manual_subtitle": "Synkroniser alle uploadet billeder og videoer til de valgte backupalbummer",
|
||||||
|
"sync_upload_album_setting_subtitle": "Opret og upload dine billeder og videoer til de valgte albummer i Immich",
|
||||||
"tab_controller_nav_library": "Bibliotek",
|
"tab_controller_nav_library": "Bibliotek",
|
||||||
"tab_controller_nav_photos": "Billeder",
|
"tab_controller_nav_photos": "Billeder",
|
||||||
"tab_controller_nav_search": "Søg",
|
"tab_controller_nav_search": "Søg",
|
||||||
"tab_controller_nav_sharing": "Deling",
|
"tab_controller_nav_sharing": "Deling",
|
||||||
"theme_setting_asset_list_storage_indicator_title": "Vis opbevaringsindikator på filer",
|
"theme_setting_asset_list_storage_indicator_title": "Vis opbevaringsindikator på filer",
|
||||||
"theme_setting_asset_list_tiles_per_row_title": "Antal elementer per række ({})",
|
"theme_setting_asset_list_tiles_per_row_title": "Antal elementer per række ({})",
|
||||||
|
"theme_setting_colorful_interface_subtitle": "Tilføj primær farve til baggrundsoverflader.",
|
||||||
|
"theme_setting_colorful_interface_title": "Farverig grænseflade",
|
||||||
"theme_setting_dark_mode_switch": "Mørk tilstand",
|
"theme_setting_dark_mode_switch": "Mørk tilstand",
|
||||||
"theme_setting_image_viewer_quality_subtitle": "Juster kvaliteten i billedfremviseren",
|
"theme_setting_image_viewer_quality_subtitle": "Juster kvaliteten i billedfremviseren",
|
||||||
"theme_setting_image_viewer_quality_title": "Billedfremviserkvalitet",
|
"theme_setting_image_viewer_quality_title": "Billedfremviserkvalitet",
|
||||||
|
"theme_setting_primary_color_subtitle": "Vælg en farve til primære handlinger og accenter.",
|
||||||
|
"theme_setting_primary_color_title": "Primær farve",
|
||||||
|
"theme_setting_system_primary_color_title": "Brug systemfarver",
|
||||||
"theme_setting_system_theme_switch": "Automatisk (Følg systemindstillinger)",
|
"theme_setting_system_theme_switch": "Automatisk (Følg systemindstillinger)",
|
||||||
"theme_setting_theme_subtitle": "Vælg appens temaindstilling",
|
"theme_setting_theme_subtitle": "Vælg appens temaindstilling",
|
||||||
"theme_setting_theme_title": "Tema",
|
"theme_setting_theme_title": "Tema",
|
||||||
"theme_setting_three_stage_loading_subtitle": "Tre-trins indlæsning kan øge ydeevnen, men kan ligeledes føre til højere netværksbelastning",
|
"theme_setting_three_stage_loading_subtitle": "Tre-trins indlæsning kan øge ydeevnen, men kan ligeledes føre til højere netværksbelastning",
|
||||||
"theme_setting_three_stage_loading_title": "Slå tre-trins indlæsning til",
|
"theme_setting_three_stage_loading_title": "Slå tre-trins indlæsning til",
|
||||||
"translated_text_options": "Handlinger",
|
"translated_text_options": "Handlinger",
|
||||||
|
"trash_emptied": "Tømte papirkurven",
|
||||||
"trash_page_delete": "Slet",
|
"trash_page_delete": "Slet",
|
||||||
"trash_page_delete_all": "Slet alt",
|
"trash_page_delete_all": "Slet alt",
|
||||||
"trash_page_empty_trash_btn": "Tøm papirkurv",
|
"trash_page_empty_trash_btn": "Tøm papirkurv",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user