Compare commits
18 Commits
v1.131.2
...
tmp/demo-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f2e281b07 | ||
|
|
40cff2893c | ||
|
|
b621281351 | ||
|
|
4336afd6bf | ||
|
|
5a456ef277 | ||
|
|
5cb5fcbf62 | ||
|
|
95e3b15776 | ||
|
|
50335dc363 | ||
|
|
6e62c09d84 | ||
|
|
00d3b8d83a | ||
|
|
d911b76c08 | ||
|
|
502854cee1 | ||
|
|
59e5c82569 | ||
|
|
e4b0c00885 | ||
|
|
946507231d | ||
|
|
20ba800a50 | ||
|
|
f434e858ed | ||
|
|
3e03c47fbf |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -6,6 +6,9 @@ mobile/openapi/**/*.dart linguist-generated=true
|
||||
mobile/lib/**/*.g.dart -diff -merge
|
||||
mobile/lib/**/*.g.dart linguist-generated=true
|
||||
|
||||
mobile/lib/**/*.drift.dart -diff -merge
|
||||
mobile/lib/**/*.drift.dart linguist-generated=true
|
||||
|
||||
open-api/typescript-sdk/fetch-client.ts -diff -merge
|
||||
open-api/typescript-sdk/fetch-client.ts linguist-generated=true
|
||||
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -39,6 +39,7 @@
|
||||
],
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"*.ts": "${capture}.spec.ts,${capture}.mock.ts"
|
||||
"*.ts": "${capture}.spec.ts,${capture}.mock.ts",
|
||||
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart"
|
||||
}
|
||||
}
|
||||
6
cli/package-lock.json
generated
6
cli/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.60",
|
||||
"version": "2.2.61",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.60",
|
||||
"version": "2.2.61",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.3",
|
||||
@@ -54,7 +54,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.60",
|
||||
"version": "2.2.61",
|
||||
"description": "Command Line Interface (CLI) for Immich",
|
||||
"type": "module",
|
||||
"exports": "./dist/index.js",
|
||||
|
||||
4
docs/static/archived-versions.json
vendored
4
docs/static/archived-versions.json
vendored
@@ -1,4 +1,8 @@
|
||||
[
|
||||
{
|
||||
"label": "v1.131.3",
|
||||
"url": "https://v1.131.3.archive.immich.app"
|
||||
},
|
||||
{
|
||||
"label": "v1.131.2",
|
||||
"url": "https://v1.131.2.archive.immich.app"
|
||||
|
||||
8
e2e/package-lock.json
generated
8
e2e/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich-e2e",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
@@ -44,7 +44,7 @@
|
||||
},
|
||||
"../cli": {
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.60",
|
||||
"version": "2.2.61",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
@@ -93,7 +93,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -864,6 +864,7 @@
|
||||
"loop_videos": "Loop videos",
|
||||
"loop_videos_description": "Enable to automatically loop a video in the detail viewer.",
|
||||
"main_branch_warning": "You’re using a development version; we strongly recommend using a release version!",
|
||||
"main_menu": "Main menu",
|
||||
"make": "Make",
|
||||
"manage_shared_links": "Manage shared links",
|
||||
"manage_sharing_with_partners": "Manage sharing with partners",
|
||||
|
||||
@@ -54,7 +54,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends g++
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest@sha256:57da96c4557243fc0a732817854084e81af9393f64dc7d172f39c16465b5e2ba /uv /uvx /bin/
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest@sha256:fb91e82e8643382d5bce074ba0d167677d678faff4bd518dac670476d19b159c /uv /uvx /bin/
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||
|
||||
309
machine-learning/uv.lock
generated
309
machine-learning/uv.lock
generated
@@ -508,16 +508,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.115.11"
|
||||
version = "0.115.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/28/c5d26e5860df807241909a961a37d45e10533acef95fc368066c7dd186cd/fastapi-0.115.11.tar.gz", hash = "sha256:cc81f03f688678b92600a65a5e618b93592c65005db37157147204d8924bf94f", size = 294441 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/5d/4d8bbb94f0dbc22732350c06965e40740f4a92ca560e90bb566f4f73af41/fastapi-0.115.11-py3-none-any.whl", hash = "sha256:32e1541b7b74602e4ef4a0260ecaf3aadf9d4f19590bba3e1bf2ac4666aa2c64", size = 94926 },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1583,62 +1583,64 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "orjson"
|
||||
version = "3.10.15"
|
||||
version = "3.10.16"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/5dea21763eeff8c1590076918a446ea3d6140743e0e36f58f369928ed0f4/orjson-3.10.15.tar.gz", hash = "sha256:05ca7fe452a2e9d8d9d706a2984c95b9c2ebc5db417ce0b7a49b91d50642a23e", size = 5282482 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/c7/03913cc4332174071950acf5b0735463e3f63760c80585ef369270c2b372/orjson-3.10.16.tar.gz", hash = "sha256:d2aaa5c495e11d17b9b93205f5fa196737ee3202f000aaebf028dc9a73750f10", size = 5410415 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/52/09/e5ff18ad009e6f97eb7edc5f67ef98b3ce0c189da9c3eaca1f9587cd4c61/orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04", size = 249532 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/b8/a75883301fe332bd433d9b0ded7d2bb706ccac679602c3516984f8814fb5/orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8", size = 125229 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/4b/22f053e7a364cc9c685be203b1e40fc5f2b3f164a9b2284547504eec682e/orjson-3.10.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c2c79fa308e6edb0ffab0a31fd75a7841bf2a79a20ef08a3c6e3b26814c8ca8", size = 150148 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/64/1b54fc75ca328b57dd810541a4035fe48c12a161d466e3cf5b11a8c25649/orjson-3.10.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cb85490aa6bf98abd20607ab5c8324c0acb48d6da7863a51be48505646c814", size = 139748 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/ff/ff0c5da781807bb0a5acd789d9a7fbcb57f7b0c6e1916595da1f5ce69f3c/orjson-3.10.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763dadac05e4e9d2bc14938a45a2d0560549561287d41c465d3c58aec818b164", size = 154559 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/9a/11e2974383384ace8495810d4a2ebef5f55aacfc97b333b65e789c9d362d/orjson-3.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a330b9b4734f09a623f74a7490db713695e13b67c959713b78369f26b3dee6bf", size = 130349 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/c4/dd9583aea6aefee1b64d3aed13f51d2aadb014028bc929fe52936ec5091f/orjson-3.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a61a4622b7ff861f019974f73d8165be1bd9a0855e1cad18ee167acacabeb061", size = 138514 },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/3e/dcf1729230654f5c5594fc752de1f43dcf67e055ac0d300c8cdb1309269a/orjson-3.10.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd271247691574416b3228db667b84775c497b245fa275c6ab90dc1ffbbd2b3", size = 130940 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/2b/b9759fe704789937705c8a56a03f6c03e50dff7df87d65cba9a20fec5282/orjson-3.10.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4759b109c37f635aa5c5cc93a1b26927bfde24b254bcc0e1149a9fada253d2d", size = 414713 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/6b/b9dfdbd4b6e20a59238319eb203ae07c3f6abf07eef909169b7a37ae3bba/orjson-3.10.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e992fd5cfb8b9f00bfad2fd7a05a4299db2bbe92e6440d9dd2fab27655b3182", size = 141028 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/b5/40f5bbea619c7caf75eb4d652a9821875a8ed04acc45fe3d3ef054ca69fb/orjson-3.10.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f95fb363d79366af56c3f26b71df40b9a583b07bbaaf5b317407c4d58497852e", size = 129715 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/60/2272514061cbdf4d672edbca6e59c7e01cd1c706e881427d88f3c3e79761/orjson-3.10.15-cp310-cp310-win32.whl", hash = "sha256:f9875f5fea7492da8ec2444839dcc439b0ef298978f311103d0b7dfd775898ab", size = 142473 },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/5d/be1490ff7eafe7fef890eb4527cf5bcd8cfd6117f3efe42a3249ec847b60/orjson-3.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:17085a6aa91e1cd70ca8533989a18b5433e15d29c574582f76f821737c8d5806", size = 133564 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/a2/21b25ce4a2c71dbb90948ee81bd7a42b4fbfc63162e57faf83157d5540ae/orjson-3.10.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c4cc83960ab79a4031f3119cc4b1a1c627a3dc09df125b27c4201dff2af7eaa6", size = 249533 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/85/2076fc12d8225698a51278009726750c9c65c846eda741e77e1761cfef33/orjson-3.10.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddbeef2481d895ab8be5185f2432c334d6dec1f5d1933a9c83014d188e102cef", size = 125230 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/df/a85a7955f11274191eccf559e8481b2be74a7c6d43075d0a9506aa80284d/orjson-3.10.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e590a0477b23ecd5b0ac865b1b907b01b3c5535f5e8a8f6ab0e503efb896334", size = 150148 },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/b3/94c55625a29b8767c0eed194cb000b3787e3c23b4cdd13be17bae6ccbb4b/orjson-3.10.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6be38bd103d2fd9bdfa31c2720b23b5d47c6796bcb1d1b598e3924441b4298d", size = 139749 },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/ba/c608b1e719971e8ddac2379f290404c2e914cf8e976369bae3cad88768b1/orjson-3.10.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff4f6edb1578960ed628a3b998fa54d78d9bb3e2eb2cfc5c2a09732431c678d0", size = 154558 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/c4/c1fb835bb23ad788a39aa9ebb8821d51b1c03588d9a9e4ca7de5b354fdd5/orjson-3.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0482b21d0462eddd67e7fce10b89e0b6ac56570424662b685a0d6fccf581e13", size = 130349 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/14/bb2b48b26ab3c570b284eb2157d98c1ef331a8397f6c8bd983b270467f5c/orjson-3.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bb5cc3527036ae3d98b65e37b7986a918955f85332c1ee07f9d3f82f3a6899b5", size = 138513 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/97/d5b353a5fe532e92c46467aa37e637f81af8468aa894cd77d2ec8a12f99e/orjson-3.10.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d569c1c462912acdd119ccbf719cf7102ea2c67dd03b99edcb1a3048651ac96b", size = 130942 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/5d/a067bec55293cca48fea8b9928cfa84c623be0cce8141d47690e64a6ca12/orjson-3.10.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1e6d33efab6b71d67f22bf2962895d3dc6f82a6273a965fab762e64fa90dc399", size = 414717 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/9a/1485b8b05c6b4c4db172c438cf5db5dcfd10e72a9bc23c151a1137e763e0/orjson-3.10.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c33be3795e299f565681d69852ac8c1bc5c84863c0b0030b2b3468843be90388", size = 141033 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/d2/fc67523656e43a0c7eaeae9007c8b02e86076b15d591e9be11554d3d3138/orjson-3.10.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eea80037b9fae5339b214f59308ef0589fc06dc870578b7cce6d71eb2096764c", size = 129720 },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/42/f58c7bd4e5b54da2ce2ef0331a39ccbbaa7699b7f70206fbf06737c9ed7d/orjson-3.10.15-cp311-cp311-win32.whl", hash = "sha256:d5ac11b659fd798228a7adba3e37c010e0152b78b1982897020a8e019a94882e", size = 142473 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/f8/bb60a4644287a544ec81df1699d5b965776bc9848d9029d9f9b3402ac8bb/orjson-3.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:cf45e0214c593660339ef63e875f32ddd5aa3b4adc15e662cdb80dc49e194f8e", size = 133570 },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/85/22fe737188905a71afcc4bf7cc4c79cd7f5bbe9ed1fe0aac4ce4c33edc30/orjson-3.10.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d11c0714fc85bfcf36ada1179400862da3288fc785c30e8297844c867d7505a", size = 249504 },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/b7/2622b29f3afebe938a0a9037e184660379797d5fd5234e5998345d7a5b43/orjson-3.10.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba5a1e85d554e3897fa9fe6fbcff2ed32d55008973ec9a2b992bd9a65d2352d", size = 125080 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/8f/0b72a48f4403d0b88b2a41450c535b3e8989e8a2d7800659a967efc7c115/orjson-3.10.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7723ad949a0ea502df656948ddd8b392780a5beaa4c3b5f97e525191b102fff0", size = 150121 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/ec/acb1a20cd49edb2000be5a0404cd43e3c8aad219f376ac8c60b870518c03/orjson-3.10.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fd9bc64421e9fe9bd88039e7ce8e58d4fead67ca88e3a4014b143cec7684fd4", size = 139796 },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/e1/f7840a2ea852114b23a52a1c0b2bea0a1ea22236efbcdb876402d799c423/orjson-3.10.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dadba0e7b6594216c214ef7894c4bd5f08d7c0135f4dd0145600be4fbcc16767", size = 154636 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/da/31543337febd043b8fa80a3b67de627669b88c7b128d9ad4cc2ece005b7a/orjson-3.10.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48f59114fe318f33bbaee8ebeda696d8ccc94c9e90bc27dbe72153094e26f41", size = 130621 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/78/66115dc9afbc22496530d2139f2f4455698be444c7c2475cb48f657cefc9/orjson-3.10.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:035fb83585e0f15e076759b6fedaf0abb460d1765b6a36f48018a52858443514", size = 138516 },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/84/cd4f5fb5427ffcf823140957a47503076184cb1ce15bcc1165125c26c46c/orjson-3.10.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d13b7fe322d75bf84464b075eafd8e7dd9eae05649aa2a5354cfa32f43c59f17", size = 130762 },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/1f/67596b711ba9f56dd75d73b60089c5c92057f1130bb3a25a0f53fb9a583b/orjson-3.10.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7066b74f9f259849629e0d04db6609db4cf5b973248f455ba5d3bd58a4daaa5b", size = 414700 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/0c/6a3b3271b46443d90efb713c3e4fe83fa8cd71cda0d11a0f69a03f437c6e/orjson-3.10.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88dc3f65a026bd3175eb157fea994fca6ac7c4c8579fc5a86fc2114ad05705b7", size = 141077 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/9b/33c58e0bfc788995eccd0d525ecd6b84b40d7ed182dd0751cd4c1322ac62/orjson-3.10.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b342567e5465bd99faa559507fe45e33fc76b9fb868a63f1642c6bc0735ad02a", size = 129898 },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/c1/d577ecd2e9fa393366a1ea0a9267f6510d86e6c4bb1cdfb9877104cac44c/orjson-3.10.15-cp312-cp312-win32.whl", hash = "sha256:0a4f27ea5617828e6b58922fdbec67b0aa4bb844e2d363b9244c47fa2180e665", size = 142566 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/eb/a85317ee1732d1034b92d56f89f1de4d7bf7904f5c8fb9dcdd5b1c83917f/orjson-3.10.15-cp312-cp312-win_amd64.whl", hash = "sha256:ef5b87e7aa9545ddadd2309efe6824bd3dd64ac101c15dae0f2f597911d46eaa", size = 133732 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/10/fe7d60b8da538e8d3d3721f08c1b7bff0491e8fa4dd3bf11a17e34f4730e/orjson-3.10.15-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bae0e6ec2b7ba6895198cd981b7cca95d1487d0147c8ed751e5632ad16f031a6", size = 249399 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/83/52c356fd3a61abd829ae7e4366a6fe8e8863c825a60d7ac5156067516edf/orjson-3.10.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93ce145b2db1252dd86af37d4165b6faa83072b46e3995ecc95d4b2301b725a", size = 125044 },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/b2/d06d5901408e7ded1a74c7c20d70e3a127057a6d21355f50c90c0f337913/orjson-3.10.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c203f6f969210128af3acae0ef9ea6aab9782939f45f6fe02d05958fe761ef9", size = 150066 },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/8c/60c3106e08dc593a861755781c7c675a566445cc39558677d505878d879f/orjson-3.10.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8918719572d662e18b8af66aef699d8c21072e54b6c82a3f8f6404c1f5ccd5e0", size = 139737 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/8c/ae00d7d0ab8a4490b1efeb01ad4ab2f1982e69cc82490bf8093407718ff5/orjson-3.10.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f71eae9651465dff70aa80db92586ad5b92df46a9373ee55252109bb6b703307", size = 154804 },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/86/65dc69bd88b6dd254535310e97bc518aa50a39ef9c5a2a5d518e7a223710/orjson-3.10.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e117eb299a35f2634e25ed120c37c641398826c2f5a3d3cc39f5993b96171b9e", size = 130583 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/00/6fe01ededb05d52be42fabb13d93a36e51f1fd9be173bd95707d11a8a860/orjson-3.10.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13242f12d295e83c2955756a574ddd6741c81e5b99f2bef8ed8d53e47a01e4b7", size = 138465 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/2f/4cc151c4b471b0cdc8cb29d3eadbce5007eb0475d26fa26ed123dca93b33/orjson-3.10.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7946922ada8f3e0b7b958cc3eb22cfcf6c0df83d1fe5521b4a100103e3fa84c8", size = 130742 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/13/8a6109e4b477c518498ca37963d9c0eb1508b259725553fb53d53b20e2ea/orjson-3.10.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b7155eb1623347f0f22c38c9abdd738b287e39b9982e1da227503387b81b34ca", size = 414669 },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/7b/1d229d6d24644ed4d0a803de1b0e2df832032d5beda7346831c78191b5b2/orjson-3.10.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:208beedfa807c922da4e81061dafa9c8489c6328934ca2a562efa707e049e561", size = 141043 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/d3/6dc91156cf12ed86bed383bcb942d84d23304a1e57b7ab030bf60ea130d6/orjson-3.10.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eca81f83b1b8c07449e1d6ff7074e82e3fd6777e588f1a6632127f286a968825", size = 129826 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/c47c25b86f6996f1343be721b6ea4367bc1c8bc0fc3f6bbcd995d18cb19d/orjson-3.10.15-cp313-cp313-win32.whl", hash = "sha256:c03cd6eea1bd3b949d0d007c8d57049aa2b39bd49f58b4b2af571a5d3833d890", size = 142542 },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/f1/1d7ec15b20f8ce9300bc850de1e059132b88990e46cd0ccac29cbf11e4f9/orjson-3.10.15-cp313-cp313-win_amd64.whl", hash = "sha256:fd56a26a04f6ba5fb2045b0acc487a63162a958ed837648c5781e1fe3316cfbf", size = 133444 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/a6/22cb9b03baf167bc2d659c9e74d7580147f36e6a155e633801badfd5a74d/orjson-3.10.16-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4cb473b8e79154fa778fb56d2d73763d977be3dcc140587e07dbc545bbfc38f8", size = 249179 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/ce/3e68cc33020a6ebd8f359b8628b69d2132cd84fea68155c33057e502ee51/orjson-3.10.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:622a8e85eeec1948690409a19ca1c7d9fd8ff116f4861d261e6ae2094fe59a00", size = 138510 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/12/63bee7764ce12052f7c1a1393ce7f26dc392c93081eb8754dd3dce9b7c6b/orjson-3.10.16-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c682d852d0ce77613993dc967e90e151899fe2d8e71c20e9be164080f468e370", size = 132373 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/d5/2998c2f319adcd572f2b03ba2083e8176863d1055d8d713683ddcf927b71/orjson-3.10.16-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c520ae736acd2e32df193bcff73491e64c936f3e44a2916b548da048a48b46b", size = 136774 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/03/88c236ae307bd0604623204d4a835e15fbf9c75b8535c8f13ef45abd413f/orjson-3.10.16-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:134f87c76bfae00f2094d85cfab261b289b76d78c6da8a7a3b3c09d362fd1e06", size = 138030 },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/ba/3e256ddfeb364f98fd6ac65774844090d356158b2d1de8998db2bf984503/orjson-3.10.16-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b59afde79563e2cf37cfe62ee3b71c063fd5546c8e662d7fcfc2a3d5031a5c4c", size = 142677 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/71/73a1214bd27baa2ea5184fff4aa6193a114dfb0aa5663dad48fe63e8cd29/orjson-3.10.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:113602f8241daaff05d6fad25bd481d54c42d8d72ef4c831bb3ab682a54d9e15", size = 132798 },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/ac/0b2f41c0a1e8c095439d0fab3b33103cf41a39be8e6aa2c56298a6034259/orjson-3.10.16-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4fc0077d101f8fab4031e6554fc17b4c2ad8fdbc56ee64a727f3c95b379e31da", size = 135450 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/ca/7524c7b0bc815d426ca134dab54cad519802287b808a3846b047a5b2b7a3/orjson-3.10.16-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9c6bf6ff180cd69e93f3f50380224218cfab79953a868ea3908430bcfaf9cb5e", size = 412356 },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/1d/3ae2367c255276bf16ff7e1b210dd0af18bc8da20c4e4295755fc7de1268/orjson-3.10.16-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5673eadfa952f95a7cd76418ff189df11b0a9c34b1995dff43a6fdbce5d63bf4", size = 152769 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/2d/8eb10b6b1d30bb69c35feb15e5ba5ac82466cf743d562e3e8047540efd2f/orjson-3.10.16-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5fe638a423d852b0ae1e1a79895851696cb0d9fa0946fdbfd5da5072d9bb9551", size = 137223 },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/42/f043717930cb2de5fbebe47f308f101bed9ec2b3580b1f99c8284b2f5fe8/orjson-3.10.16-cp310-cp310-win32.whl", hash = "sha256:33af58f479b3c6435ab8f8b57999874b4b40c804c7a36b5cc6b54d8f28e1d3dd", size = 141734 },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/99/795ad7282b425b9fddcfb8a31bded5dcf84dba78ecb1e7ae716e84e794da/orjson-3.10.16-cp310-cp310-win_amd64.whl", hash = "sha256:0338356b3f56d71293c583350af26f053017071836b07e064e92819ecf1aa055", size = 133779 },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/29/43f91a5512b5d2535594438eb41c5357865fd5e64dec745d90a588820c75/orjson-3.10.16-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44fcbe1a1884f8bc9e2e863168b0f84230c3d634afe41c678637d2728ea8e739", size = 249180 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/36/2a72d55e266473c19a86d97b7363bb8bf558ab450f75205689a287d5ce61/orjson-3.10.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78177bf0a9d0192e0b34c3d78bcff7fe21d1b5d84aeb5ebdfe0dbe637b885225", size = 138510 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/ad/f86d6f55c1a68b57ff6ea7966bce5f4e5163f2e526ddb7db9fc3c2c8d1c4/orjson-3.10.16-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12824073a010a754bb27330cad21d6e9b98374f497f391b8707752b96f72e741", size = 132373 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/8b/d18f2711493a809f3082a88fda89342bc8e16767743b909cd3c34989fba3/orjson-3.10.16-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddd41007e56284e9867864aa2f29f3136bb1dd19a49ca43c0b4eda22a579cf53", size = 136773 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/dc/ce025f002f8e0749e3f057c4d773a4d4de32b7b4c1fc5a50b429e7532586/orjson-3.10.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0877c4d35de639645de83666458ca1f12560d9fa7aa9b25d8bb8f52f61627d14", size = 138029 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/1b/cf9df85852b91160029d9f26014230366a2b4deb8cc51fabe68e250a8c1a/orjson-3.10.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a09a539e9cc3beead3e7107093b4ac176d015bec64f811afb5965fce077a03c", size = 142677 },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/18/5b1e1e995bffad49dc4311a0bdfd874bc6f135fd20f0e1f671adc2c9910e/orjson-3.10.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b98bc9b40610fec971d9a4d67bb2ed02eec0a8ae35f8ccd2086320c28526ca", size = 132800 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/eb/467f25b580e942fcca1344adef40633b7f05ac44a65a63fc913f9a805d58/orjson-3.10.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0ce243f5a8739f3a18830bc62dc2e05b69a7545bafd3e3249f86668b2bcd8e50", size = 135451 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/4b/9d10888038975cb375982e9339d9495bac382d5c976c500b8d6f2c8e2e4e/orjson-3.10.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:64792c0025bae049b3074c6abe0cf06f23c8e9f5a445f4bab31dc5ca23dbf9e1", size = 412358 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/e2/cfbcfcc4fbe619e0ca9bdbbfccb2d62b540bbfe41e0ee77d44a628594f59/orjson-3.10.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea53f7e68eec718b8e17e942f7ca56c6bd43562eb19db3f22d90d75e13f0431d", size = 152772 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/d6/627a1b00569be46173007c11dde3da4618c9bfe18409325b0e3e2a82fe29/orjson-3.10.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a741ba1a9488c92227711bde8c8c2b63d7d3816883268c808fbeada00400c164", size = 137225 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/7b/a73c67b505021af845b9f05c7c848793258ea141fa2058b52dd9b067c2b4/orjson-3.10.16-cp311-cp311-win32.whl", hash = "sha256:c7ed2c61bb8226384c3fdf1fb01c51b47b03e3f4536c985078cccc2fd19f1619", size = 141733 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/22/5e8217c48d68c0adbfb181e749d6a733761074e598b083c69a1383d18147/orjson-3.10.16-cp311-cp311-win_amd64.whl", hash = "sha256:cd67d8b3e0e56222a2e7b7f7da9031e30ecd1fe251c023340b9f12caca85ab60", size = 133784 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/15/67ce9d4c959c83f112542222ea3b9209c1d424231d71d74c4890ea0acd2b/orjson-3.10.16-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6d3444abbfa71ba21bb042caa4b062535b122248259fdb9deea567969140abca", size = 249325 },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/2c/1426b06f30a1b9ada74b6f512c1ddf9d2760f53f61cdb59efeb9ad342133/orjson-3.10.16-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:30245c08d818fdcaa48b7d5b81499b8cae09acabb216fe61ca619876b128e184", size = 133621 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/88/18d26130954bc73bee3be10f95371ea1dfb8679e0e2c46b0f6d8c6289402/orjson-3.10.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0ba1d0baa71bf7579a4ccdcf503e6f3098ef9542106a0eca82395898c8a500a", size = 138270 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/f9/6d8b64fcd58fae072e80ee7981be8ba0d7c26ace954e5cd1d027fc80518f/orjson-3.10.16-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb0beefa5ef3af8845f3a69ff2a4aa62529b5acec1cfe5f8a6b4141033fd46ef", size = 132346 },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/3f/2513fd5bc786f40cd12af569c23cae6381aeddbefeed2a98f0a666eb5d0d/orjson-3.10.16-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6daa0e1c9bf2e030e93c98394de94506f2a4d12e1e9dadd7c53d5e44d0f9628e", size = 136845 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/42/b0e7b36720f5ab722b48e8ccf06514d4f769358dd73c51abd8728ef58d0b/orjson-3.10.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9da9019afb21e02410ef600e56666652b73eb3e4d213a0ec919ff391a7dd52aa", size = 138078 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/a8/d220afb8a439604be74fc755dbc740bded5ed14745ca536b304ed32eb18a/orjson-3.10.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:daeb3a1ee17b69981d3aae30c3b4e786b0f8c9e6c71f2b48f1aef934f63f38f4", size = 142712 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/88/7e41e9883c00f84f92fe357a8371edae816d9d7ef39c67b5106960c20389/orjson-3.10.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fed80eaf0e20a31942ae5d0728849862446512769692474be5e6b73123a23b", size = 133136 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/ca/61116095307ad0be828ea26093febaf59e38596d84a9c8d765c3c5e4934f/orjson-3.10.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73390ed838f03764540a7bdc4071fe0123914c2cc02fb6abf35182d5fd1b7a42", size = 135258 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/1b/09493cf7d801505f094c9295f79c98c1e0af2ac01c7ed8d25b30fcb19ada/orjson-3.10.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a22bba012a0c94ec02a7768953020ab0d3e2b884760f859176343a36c01adf87", size = 412326 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/02/125d7bbd7f7a500190ddc8ae5d2d3c39d87ed3ed28f5b37cfe76962c678d/orjson-3.10.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5385bbfdbc90ff5b2635b7e6bebf259652db00a92b5e3c45b616df75b9058e88", size = 152800 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/09/7658a9e3e793d5b3b00598023e0fb6935d0e7bbb8ff72311c5415a8ce677/orjson-3.10.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:02c6279016346e774dd92625d46c6c40db687b8a0d685aadb91e26e46cc33e1e", size = 137516 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/87/32b7a4831e909d347278101a48d4cf9f3f25901b2295e7709df1651f65a1/orjson-3.10.16-cp312-cp312-win32.whl", hash = "sha256:7ca55097a11426db80f79378e873a8c51f4dde9ffc22de44850f9696b7eb0e8c", size = 141759 },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/ce/81a27e7b439b807bd393585271364cdddf50dc281fc57c4feef7ccb186a6/orjson-3.10.16-cp312-cp312-win_amd64.whl", hash = "sha256:86d127efdd3f9bf5f04809b70faca1e6836556ea3cc46e662b44dab3fe71f3d6", size = 133944 },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/b9/ff6aa28b8c86af9526160905593a2fe8d004ac7a5e592ee0b0ff71017511/orjson-3.10.16-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:148a97f7de811ba14bc6dbc4a433e0341ffd2cc285065199fb5f6a98013744bd", size = 249289 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/81/6d92a586149b52684ab8fd70f3623c91d0e6a692f30fd8c728916ab2263c/orjson-3.10.16-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1d960c1bf0e734ea36d0adc880076de3846aaec45ffad29b78c7f1b7962516b8", size = 133640 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/88/b72443f4793d2e16039ab85d0026677932b15ab968595fb7149750d74134/orjson-3.10.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a318cd184d1269f68634464b12871386808dc8b7c27de8565234d25975a7a137", size = 138286 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/3c/72a22d4b28c076c4016d5a52bd644a8e4d849d3bb0373d9e377f9e3b2250/orjson-3.10.16-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df23f8df3ef9223d1d6748bea63fca55aae7da30a875700809c500a05975522b", size = 132307 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/a2/f1259561bdb6ad7061ff1b95dab082fe32758c4bc143ba8d3d70831f0a06/orjson-3.10.16-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b94dda8dd6d1378f1037d7f3f6b21db769ef911c4567cbaa962bb6dc5021cf90", size = 136739 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/af/c7583c4b34f33d8b8b90cfaab010ff18dd64e7074cc1e117a5f1eff20dcf/orjson-3.10.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f12970a26666a8775346003fd94347d03ccb98ab8aa063036818381acf5f523e", size = 138076 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/59/d7fc7fbdd3d4a64c2eae4fc7341a5aa39cf9549bd5e2d7f6d3c07f8b715b/orjson-3.10.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15a1431a245d856bd56e4d29ea0023eb4d2c8f71efe914beb3dee8ab3f0cd7fb", size = 142643 },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/0e/3bd8f2197d27601f16b4464ae948826da2bcf128af31230a9dbbad7ceb57/orjson-3.10.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c83655cfc247f399a222567d146524674a7b217af7ef8289c0ff53cfe8db09f0", size = 133168 },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/a8/351fd87b664b02f899f9144d2c3dc848b33ac04a5df05234cbfb9e2a7540/orjson-3.10.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fa59ae64cb6ddde8f09bdbf7baf933c4cd05734ad84dcf4e43b887eb24e37652", size = 135271 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/b0/a6d42a7d412d867c60c0337d95123517dd5a9370deea705ea1be0f89389e/orjson-3.10.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ca5426e5aacc2e9507d341bc169d8af9c3cbe88f4cd4c1cf2f87e8564730eb56", size = 412444 },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/ec/7572cd4e20863f60996f3f10bc0a6da64a6fd9c35954189a914cec0b7377/orjson-3.10.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6fd5da4edf98a400946cd3a195680de56f1e7575109b9acb9493331047157430", size = 152737 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/19/ceb9e8fed5403b2e76a8ac15f581b9d25780a3be3c9b3aa54b7777a210d5/orjson-3.10.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:980ecc7a53e567169282a5e0ff078393bac78320d44238da4e246d71a4e0e8f5", size = 137482 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/78/a78bb810f3786579dbbbd94768284cbe8f2fd65167cd7020260679665c17/orjson-3.10.16-cp313-cp313-win32.whl", hash = "sha256:28f79944dd006ac540a6465ebd5f8f45dfdf0948ff998eac7a908275b4c1add6", size = 141714 },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/9c/b66ce9245ff319df2c3278acd351a3f6145ef34b4a2d7f4b0f739368370f/orjson-3.10.16-cp313-cp313-win_amd64.whl", hash = "sha256:fe0a145e96d51971407cb8ba947e63ead2aa915db59d6631a355f5f2150b56b7", size = 133954 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1787,91 +1789,104 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.6"
|
||||
version = "2.11.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/93/a3/698b87a4d4d303d7c5f62ea5fbf7a79cab236ccfbd0a17847b7f77f8163e/pydantic-2.11.1.tar.gz", hash = "sha256:442557d2910e75c991c39f4b4ab18963d57b9b55122c8b2a9cd176d8c29ce968", size = 782817 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/12/f9221a949f2419e2e23847303c002476c26fbcfd62dc7f3d25d0bec5ca99/pydantic-2.11.1-py3-none-any.whl", hash = "sha256:5b6c415eee9f8123a14d859be0c84363fec6b1feb6b688d6435801230b56e0b8", size = 442648 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.27.2"
|
||||
version = "2.33.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/05/91ce14dfd5a3a99555fce436318cc0fd1f08c4daa32b3248ad63669ea8b4/pydantic_core-2.33.0.tar.gz", hash = "sha256:40eb8af662ba409c3cbf4a8150ad32ae73514cd7cb1f1a2113af39763dd616b3", size = 434080 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662 },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685 },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582 },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/43/0649ad07e66b36a3fb21442b425bd0348ac162c5e686b36471f363201535/pydantic_core-2.33.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71dffba8fe9ddff628c68f3abd845e91b028361d43c5f8e7b3f8b91d7d85413e", size = 2042968 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/a6/975fea4774a459e495cb4be288efd8b041ac756a0a763f0b976d0861334b/pydantic_core-2.33.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:abaeec1be6ed535a5d7ffc2e6c390083c425832b20efd621562fbb5bff6dc518", size = 1860347 },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/49/7858dadad305101a077ec4d0c606b6425a2b134ea8d858458a6d287fd871/pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759871f00e26ad3709efc773ac37b4d571de065f9dfb1778012908bcc36b3a73", size = 1910060 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/4f/6522527911d9c5fe6d76b084d8b388d5c84b09d113247b39f91937500b34/pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dcfebee69cd5e1c0b76a17e17e347c84b00acebb8dd8edb22d4a03e88e82a207", size = 1997129 },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/d0/06f396da053e3d73001ea4787e56b4d7132a87c0b5e2e15a041e808c35cd/pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b1262b912435a501fa04cd213720609e2cefa723a07c92017d18693e69bf00b", size = 2140389 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/6b/b9ff5b69cd4ef007cf665463f3be2e481dc7eb26c4a55b2f57a94308c31a/pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4726f1f3f42d6a25678c67da3f0b10f148f5655813c5aca54b0d1742ba821b8f", size = 2754237 },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/80/b4879de375cdf3718d05fcb60c9aa1f119d28e261dafa51b6a69c78f7178/pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e790954b5093dff1e3a9a2523fddc4e79722d6f07993b4cd5547825c3cbf97b5", size = 2007433 },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/24/54054713dc0af98a94eab37e0f4294dfd5cd8f70b2ca9dcdccd15709fd7e/pydantic_core-2.33.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34e7fb3abe375b5c4e64fab75733d605dda0f59827752debc99c17cb2d5f3276", size = 2123980 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/4c/257c1cb89e14cfa6e95ebcb91b308eb1dd2b348340ff76a6e6fcfa9969e1/pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ecb158fb9b9091b515213bed3061eb7deb1d3b4e02327c27a0ea714ff46b0760", size = 2087433 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/62/927df8a39ad78ef7b82c5446e01dec9bb0043e1ad71d8f426062f5f014db/pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:4d9149e7528af8bbd76cc055967e6e04617dcb2a2afdaa3dea899406c5521faa", size = 2260242 },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/f2/389414f7c77a100954e84d6f52a82bd1788ae69db72364376d8a73b38765/pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e81a295adccf73477220e15ff79235ca9dcbcee4be459eb9d4ce9a2763b8386c", size = 2258227 },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/99/94516313e15d906a1264bb40faf24a01a4af4e2ca8a7c10dd173b6513c5a/pydantic_core-2.33.0-cp310-cp310-win32.whl", hash = "sha256:f22dab23cdbce2005f26a8f0c71698457861f97fc6318c75814a50c75e87d025", size = 1925523 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/67/cc789611c6035a0b71305a1ec6ba196256ced76eba8375f316f840a70456/pydantic_core-2.33.0-cp310-cp310-win_amd64.whl", hash = "sha256:9cb2390355ba084c1ad49485d18449b4242da344dea3e0fe10babd1f0db7dcfc", size = 1951872 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/93/9e97af2619b4026596487a79133e425c7d3c374f0a7f100f3d76bcdf9c83/pydantic_core-2.33.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a608a75846804271cf9c83e40bbb4dab2ac614d33c6fd5b0c6187f53f5c593ef", size = 2042784 },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/b4/0bba8412fd242729feeb80e7152e24f0e1a1c19f4121ca3d4a307f4e6222/pydantic_core-2.33.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e1c69aa459f5609dec2fa0652d495353accf3eda5bdb18782bc5a2ae45c9273a", size = 1858179 },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/1f/c1c40305d929bd08af863df64b0a26203b70b352a1962d86f3bcd52950fe/pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9ec80eb5a5f45a2211793f1c4aeddff0c3761d1c70d684965c1807e923a588b", size = 1909396 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/99/d2e727375c329c1e652b5d450fbb9d56e8c3933a397e4bd46e67c68c2cd5/pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e925819a98318d17251776bd3d6aa9f3ff77b965762155bdad15d1a9265c4cfd", size = 1998264 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/2e/3119a33931278d96ecc2e9e1b9d50c240636cfeb0c49951746ae34e4de74/pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bf68bb859799e9cec3d9dd8323c40c00a254aabb56fe08f907e437005932f2b", size = 2140588 },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/bd/9267bd1ba55f17c80ef6cb7e07b3890b4acbe8eb6014f3102092d53d9300/pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b2ea72dea0825949a045fa4071f6d5b3d7620d2a208335207793cf29c5a182d", size = 2746296 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/ed/ef37de6478a412ee627cbebd73e7b72a680f45bfacce9ff1199de6e17e88/pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1583539533160186ac546b49f5cde9ffc928062c96920f58bd95de32ffd7bffd", size = 2005555 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/84/72c8d1439585d8ee7bc35eb8f88a04a4d302ee4018871f1f85ae1b0c6625/pydantic_core-2.33.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:23c3e77bf8a7317612e5c26a3b084c7edeb9552d645742a54a5867635b4f2453", size = 2124452 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/8f/cb13de30c6a3e303423751a529a3d1271c2effee4b98cf3e397a66ae8498/pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7a7f2a3f628d2f7ef11cb6188bcf0b9e1558151d511b974dfea10a49afe192b", size = 2087001 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/d0/e93dc8884bf288a63fedeb8040ac8f29cb71ca52e755f48e5170bb63e55b/pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:f1fb026c575e16f673c61c7b86144517705865173f3d0907040ac30c4f9f5915", size = 2261663 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/ba/4b7739c95efa0b542ee45fd872c8f6b1884ab808cf04ce7ac6621b6df76e/pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:635702b2fed997e0ac256b2cfbdb4dd0bf7c56b5d8fba8ef03489c03b3eb40e2", size = 2257786 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/98/73cbca1d2360c27752cfa2fcdcf14d96230e92d7d48ecd50499865c56bf7/pydantic_core-2.33.0-cp311-cp311-win32.whl", hash = "sha256:07b4ced28fccae3f00626eaa0c4001aa9ec140a29501770a88dbbb0966019a86", size = 1925697 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/26/d85a40edeca5d8830ffc33667d6fef329fd0f4bc0c5181b8b0e206cfe488/pydantic_core-2.33.0-cp311-cp311-win_amd64.whl", hash = "sha256:4927564be53239a87770a5f86bdc272b8d1fbb87ab7783ad70255b4ab01aa25b", size = 1949859 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/0b/5a381605f0b9870465b805f2c86c06b0a7c191668ebe4117777306c2c1e5/pydantic_core-2.33.0-cp311-cp311-win_arm64.whl", hash = "sha256:69297418ad644d521ea3e1aa2e14a2a422726167e9ad22b89e8f1130d68e1e9a", size = 1907978 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/c4/c9381323cbdc1bb26d352bc184422ce77c4bc2f2312b782761093a59fafc/pydantic_core-2.33.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6c32a40712e3662bebe524abe8abb757f2fa2000028d64cc5a1006016c06af43", size = 2025127 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/bd/af35278080716ecab8f57e84515c7dc535ed95d1c7f52c1c6f7b313a9dab/pydantic_core-2.33.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ec86b5baa36f0a0bfb37db86c7d52652f8e8aa076ab745ef7725784183c3fdd", size = 1851687 },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/e4/a01461225809c3533c23bd1916b1e8c2e21727f0fea60ab1acbffc4e2fca/pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4deac83a8cc1d09e40683be0bc6d1fa4cde8df0a9bf0cda5693f9b0569ac01b6", size = 1892232 },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/17/3d53d62a328fb0a49911c2962036b9e7a4f781b7d15e9093c26299e5f76d/pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:175ab598fb457a9aee63206a1993874badf3ed9a456e0654273e56f00747bbd6", size = 1977896 },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/98/01f9d86e02ec4a38f4b02086acf067f2c776b845d43f901bd1ee1c21bc4b/pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f36afd0d56a6c42cf4e8465b6441cf546ed69d3a4ec92724cc9c8c61bd6ecf4", size = 2127717 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/43/6f381575c61b7c58b0fd0b92134c5a1897deea4cdfc3d47567b3ff460a4e/pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a98257451164666afafc7cbf5fb00d613e33f7e7ebb322fbcd99345695a9a61", size = 2680287 },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/42/c0d10d1451d161a9a0da9bbef023b8005aa26e9993a8cc24dc9e3aa96c93/pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecc6d02d69b54a2eb83ebcc6f29df04957f734bcf309d346b4f83354d8376862", size = 2008276 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/ca/e08df9dba546905c70bae44ced9f3bea25432e34448d95618d41968f40b7/pydantic_core-2.33.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a69b7596c6603afd049ce7f3835bcf57dd3892fc7279f0ddf987bebed8caa5a", size = 2115305 },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/1f/9b01d990730a98833113581a78e595fd40ed4c20f9693f5a658fb5f91eff/pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea30239c148b6ef41364c6f51d103c2988965b643d62e10b233b5efdca8c0099", size = 2068999 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/18/fe752476a709191148e8b1e1139147841ea5d2b22adcde6ee6abb6c8e7cf/pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:abfa44cf2f7f7d7a199be6c6ec141c9024063205545aa09304349781b9a125e6", size = 2241488 },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/22/14738ad0a0bf484b928c9e52004f5e0b81dd8dabbdf23b843717b37a71d1/pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20d4275f3c4659d92048c70797e5fdc396c6e4446caf517ba5cad2db60cd39d3", size = 2248430 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/27/be7571e215ac8d321712f2433c445b03dbcd645366a18f67b334df8912bc/pydantic_core-2.33.0-cp312-cp312-win32.whl", hash = "sha256:918f2013d7eadea1d88d1a35fd4a1e16aaf90343eb446f91cb091ce7f9b431a2", size = 1908353 },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/3a/be78f28732f93128bd0e3944bdd4b3970b389a1fbd44907c97291c8dcdec/pydantic_core-2.33.0-cp312-cp312-win_amd64.whl", hash = "sha256:aec79acc183865bad120b0190afac467c20b15289050648b876b07777e67ea48", size = 1955956 },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/26/b8911ac74faa994694b76ee6a22875cc7a4abea3c381fdba4edc6c6bef84/pydantic_core-2.33.0-cp312-cp312-win_arm64.whl", hash = "sha256:5461934e895968655225dfa8b3be79e7e927e95d4bd6c2d40edd2fa7052e71b6", size = 1903259 },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/20/de2ad03ce8f5b3accf2196ea9b44f31b0cd16ac6e8cfc6b21976ed45ec35/pydantic_core-2.33.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f00e8b59e1fc8f09d05594aa7d2b726f1b277ca6155fc84c0396db1b373c4555", size = 2032214 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/af/6817dfda9aac4958d8b516cbb94af507eb171c997ea66453d4d162ae8948/pydantic_core-2.33.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a73be93ecef45786d7d95b0c5e9b294faf35629d03d5b145b09b81258c7cd6d", size = 1852338 },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/f3/49193a312d9c49314f2b953fb55740b7c530710977cabe7183b8ef111b7f/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff48a55be9da6930254565ff5238d71d5e9cd8c5487a191cb85df3bdb8c77365", size = 1896913 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/e0/c746677825b2e29a2fa02122a8991c83cdd5b4c5f638f0664d4e35edd4b2/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4ea04195638dcd8c53dadb545d70badba51735b1594810e9768c2c0b4a5da", size = 1986046 },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/ec/44914e7ff78cef16afb5e5273d480c136725acd73d894affdbe2a1bbaad5/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41d698dcbe12b60661f0632b543dbb119e6ba088103b364ff65e951610cb7ce0", size = 2128097 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/f5/c6247d424d01f605ed2e3802f338691cae17137cee6484dce9f1ac0b872b/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae62032ef513fe6281ef0009e30838a01057b832dc265da32c10469622613885", size = 2681062 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/85/114a2113b126fdd7cf9a9443b1b1fe1b572e5bd259d50ba9d5d3e1927fa9/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f225f3a3995dbbc26affc191d0443c6c4aa71b83358fd4c2b7d63e2f6f0336f9", size = 2007487 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/40/3c05ed28d225c7a9acd2b34c5c8010c279683a870219b97e9f164a5a8af0/pydantic_core-2.33.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bdd36b362f419c78d09630cbaebc64913f66f62bda6d42d5fbb08da8cc4f181", size = 2121382 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/22/e70c086f41eebd323e6baa92cc906c3f38ddce7486007eb2bdb3b11c8f64/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2a0147c0bef783fd9abc9f016d66edb6cac466dc54a17ec5f5ada08ff65caf5d", size = 2072473 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/84/d1614dedd8fe5114f6a0e348bcd1535f97d76c038d6102f271433cd1361d/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c860773a0f205926172c6644c394e02c25421dc9a456deff16f64c0e299487d3", size = 2249468 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/c0/787061eef44135e00fddb4b56b387a06c303bfd3884a6df9bea5cb730230/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:138d31e3f90087f42aa6286fb640f3c7a8eb7bdae829418265e7e7474bd2574b", size = 2254716 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/e2/27262eb04963201e89f9c280f1e10c493a7a37bc877e023f31aa72d2f911/pydantic_core-2.33.0-cp313-cp313-win32.whl", hash = "sha256:d20cbb9d3e95114325780f3cfe990f3ecae24de7a2d75f978783878cce2ad585", size = 1916450 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/8d/25ff96f1e89b19e0b70b3cd607c9ea7ca27e1dcb810a9cd4255ed6abf869/pydantic_core-2.33.0-cp313-cp313-win_amd64.whl", hash = "sha256:ca1103d70306489e3d006b0f79db8ca5dd3c977f6f13b2c59ff745249431a606", size = 1956092 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/64/66a2efeff657b04323ffcd7b898cb0354d36dae3a561049e092134a83e9c/pydantic_core-2.33.0-cp313-cp313-win_arm64.whl", hash = "sha256:6291797cad239285275558e0a27872da735b05c75d5237bbade8736f80e4c225", size = 1908367 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/54/295e38769133363d7ec4a5863a4d579f331728c71a6644ff1024ee529315/pydantic_core-2.33.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7b79af799630af263eca9ec87db519426d8c9b3be35016eddad1832bac812d87", size = 1813331 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/9c/0c8ea02db8d682aa1ef48938abae833c1d69bdfa6e5ec13b21734b01ae70/pydantic_core-2.33.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eabf946a4739b5237f4f56d77fa6668263bc466d06a8036c055587c130a46f7b", size = 1986653 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/4f/3fb47d6cbc08c7e00f92300e64ba655428c05c56b8ab6723bd290bae6458/pydantic_core-2.33.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8a1d581e8cdbb857b0e0e81df98603376c1a5c34dc5e54039dcc00f043df81e7", size = 1931234 },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/77/85e173b715e1a277ce934f28d877d82492df13e564fa68a01c96f36a47ad/pydantic_core-2.33.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e2762c568596332fdab56b07060c8ab8362c56cf2a339ee54e491cd503612c50", size = 2040129 },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/e7/33da5f8a94bbe2191cfcd15bd6d16ecd113e67da1b8c78d3cc3478112dab/pydantic_core-2.33.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bf637300ff35d4f59c006fff201c510b2b5e745b07125458a5389af3c0dff8c", size = 1872656 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/7a/9600f222bea840e5b9ba1f17c0acc79b669b24542a78c42c6a10712c0aae/pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c151ce3d59ed56ebd7ce9ce5986a409a85db697d25fc232f8e81f195aa39a1", size = 1903731 },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/d2/94c7ca4e24c5dcfb74df92e0836c189e9eb6814cf62d2f26a75ea0a906db/pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee65f0cc652261744fd07f2c6e6901c914aa6c5ff4dcfaf1136bc394d0dd26b", size = 2083966 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/74/a0259989d220e8865ed6866a6d40539e40fa8f507e587e35d2414cc081f8/pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:024d136ae44d233e6322027bbf356712b3940bee816e6c948ce4b90f18471b3d", size = 2118951 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/4c/87405ed04d6d07597920b657f082a8e8e58bf3034178bb9044b4d57a91e2/pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e37f10f6d4bc67c58fbd727108ae1d8b92b397355e68519f1e4a7babb1473442", size = 2079632 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/4c/bcb02970ef91d4cd6de7c6893101302637da456bc8b52c18ea0d047b55ce/pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:502ed542e0d958bd12e7c3e9a015bce57deaf50eaa8c2e1c439b512cb9db1e3a", size = 2250541 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/2b/dbe5450c4cd904be5da736dcc7f2357b828199e29e38de19fc81f988b288/pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:715c62af74c236bf386825c0fdfa08d092ab0f191eb5b4580d11c3189af9d330", size = 2255685 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/a6/ca1d35f695d81f639c5617fc9efb44caad21a9463383fa45364b3044175a/pydantic_core-2.33.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bccc06fa0372151f37f6b69834181aa9eb57cf8665ed36405fb45fbf6cac3bae", size = 2082395 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/b2/553e42762e7b08771fca41c0230c1ac276f9e79e78f57628e1b7d328551d/pydantic_core-2.33.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d8dc9f63a26f7259b57f46a7aab5af86b2ad6fbe48487500bb1f4b27e051e4c", size = 2041207 },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/81/a91a57bbf3efe53525ab75f65944b8950e6ef84fe3b9a26c1ec173363263/pydantic_core-2.33.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30369e54d6d0113d2aa5aee7a90d17f225c13d87902ace8fcd7bbf99b19124db", size = 1873736 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/d2/5ab52e9f551cdcbc1ee99a0b3ef595f56d031f66f88e5ca6726c49f9ce65/pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eb479354c62067afa62f53bb387827bee2f75c9c79ef25eef6ab84d4b1ae3b", size = 1903794 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/5f/a81742d3f3821b16f1265f057d6e0b68a3ab13a814fe4bffac536a1f26fd/pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0310524c833d91403c960b8a3cf9f46c282eadd6afd276c8c5edc617bd705dc9", size = 2083457 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/2f/e872005bc0fc47f9c036b67b12349a8522d32e3bda928e82d676e2a594d1/pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eddb18a00bbb855325db27b4c2a89a4ba491cd6a0bd6d852b225172a1f54b36c", size = 2119537 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/13/183f13ce647202eaf3dada9e42cdfc59cbb95faedd44d25f22b931115c7f/pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ade5dbcf8d9ef8f4b28e682d0b29f3008df9842bb5ac48ac2c17bc55771cc976", size = 2080069 },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/8b/b6be91243da44a26558d9c3a9007043b3750334136c6550551e8092d6d96/pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2c0afd34f928383e3fd25740f2050dbac9d077e7ba5adbaa2227f4d4f3c8da5c", size = 2251618 },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/c5/fbcf1977035b834f63eb542e74cd6c807177f383386175b468f0865bcac4/pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7da333f21cd9df51d5731513a6d39319892947604924ddf2e24a4612975fb936", size = 2255374 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/f8/66f328e411f1c9574b13c2c28ab01f308b53688bbbe6ca8fb981e6cabc42/pydantic_core-2.33.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b6d77c75a57f041c5ee915ff0b0bb58eabb78728b69ed967bc5b780e8f701b8", size = 2082099 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1933,14 +1948,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest-asyncio"
|
||||
version = "0.25.3"
|
||||
version = "0.26.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/a8/ecbc8ede70921dd2f544ab1cadd3ff3bf842af27f87bbdea774c7baa1d38/pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a", size = 54239 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/c4/453c52c659521066969523e87d85d54139bbd17b78f09532fb8eb8cdb58e/pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f", size = 54156 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/67/17/3493c5624e48fd97156ebaec380dcaafee9506d7e2c46218ceebbb57d7de/pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3", size = 19467 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/7f/338843f449ace853647ace35870874f69a764d251872ed1b4de9f234822c/pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0", size = 19694 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2444,23 +2459,23 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "types-pyyaml"
|
||||
version = "6.0.12.20241230"
|
||||
version = "6.0.12.20250402"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9a/f9/4d566925bcf9396136c0a2e5dc7e230ff08d86fa011a69888dd184469d80/types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c", size = 17078 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2d/68/609eed7402f87c9874af39d35942744e39646d1ea9011765ec87b01b2a3c/types_pyyaml-6.0.12.20250402.tar.gz", hash = "sha256:d7c13c3e6d335b6af4b0122a01ff1d270aba84ab96d1a1a1063ecba3e13ec075", size = 17282 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/c1/48474fbead512b70ccdb4f81ba5eb4a58f69d100ba19f17c92c0c4f50ae6/types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6", size = 20029 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/56/1fe61db05685fbb512c07ea9323f06ea727125951f1eb4dff110b3311da3/types_pyyaml-6.0.12.20250402-py3-none-any.whl", hash = "sha256:652348fa9e7a203d4b0d21066dfb00760d3cbd5a15ebb7cf8d33c88a49546681", size = 20329 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-requests"
|
||||
version = "2.32.0.20250306"
|
||||
version = "2.32.0.20250328"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/09/1a/beaeff79ef9efd186566ba5f0d95b44ae21f6d31e9413bcfbef3489b6ae3/types_requests-2.32.0.20250306.tar.gz", hash = "sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1", size = 23012 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/00/7d/eb174f74e3f5634eaacb38031bbe467dfe2e545bc255e5c90096ec46bc46/types_requests-2.32.0.20250328.tar.gz", hash = "sha256:c9e67228ea103bd811c96984fac36ed2ae8da87a36a633964a21f199d60baf32", size = 22995 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/99/26/645d89f56004aa0ba3b96fec27793e3c7e62b40982ee069e52568922b6db/types_requests-2.32.0.20250306-py3-none-any.whl", hash = "sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b", size = 20673 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/15/3700282a9d4ea3b37044264d3e4d1b1f0095a4ebf860a99914fd544e3be3/types_requests-2.32.0.20250328-py3-none-any.whl", hash = "sha256:72ff80f84b15eb3aa7a8e2625fffb6a93f2ad5a0c20215fc1dcfa61117bcb2a2", size = 20663 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2477,20 +2492,20 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "types-simplejson"
|
||||
version = "3.20.0.20250318"
|
||||
version = "3.20.0.20250326"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b7/bb/c19c0863ce03b950a2e6319e668a3e3f51c977368d6a1da529893e794162/types_simplejson-3.20.0.20250318.tar.gz", hash = "sha256:5ada2caa2f76826a90b97985f7b0caf55088a23ed4eb9c39fc1f5cb00b985e09", size = 9966 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/af/14/e26fc55e1ea56f9ea470917d3e2f8240e6d043ca914181021d04115ae0f7/types_simplejson-3.20.0.20250326.tar.gz", hash = "sha256:b2689bc91e0e672d7a5a947b4cb546b76ae7ddc2899c6678e72a10bf96cd97d2", size = 10489 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/02/5a/adb04301014b18324ec88d5fcfe0ffc262ffc68c443cfe79e32b6cc68a35/types_simplejson-3.20.0.20250318-py3-none-any.whl", hash = "sha256:e8c9cdb06b566b6ca1c7bf3d4c97fbd69a6f6a5aea760ef127f31e638a094c26", size = 10336 },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/bf/d3f3a5ba47fd18115e8446d39f025b85905d2008677c29ee4d03b4cddd57/types_simplejson-3.20.0.20250326-py3-none-any.whl", hash = "sha256:db1ddea7b8f7623b27a137578f22fc6c618db8c83ccfb1828ca0d2f0ec11efa7", size = 10462 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-ujson"
|
||||
version = "5.10.0.20240515"
|
||||
version = "5.10.0.20250326"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/12/49/abb4bcb9f2258f785edbf236b517c3e7ba8a503a8cbce6b5895930586cc0/types-ujson-5.10.0.20240515.tar.gz", hash = "sha256:ceae7127f0dafe4af5dd0ecf98ee13e9d75951ef963b5c5a9b7ea92e0d71f0d7", size = 3571 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/5c/c974451c4babdb4ae3588925487edde492d59a8403010b4642a554d09954/types_ujson-5.10.0.20250326.tar.gz", hash = "sha256:5469e05f2c31ecb3c4c0267cc8fe41bcd116826fbb4ded69801a645c687dd014", size = 8340 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/1f/9d018cee3d09ab44a5211f0b5ed9b0422ad9a8c226bf3967f5884498d8f0/types_ujson-5.10.0.20240515-py3-none-any.whl", hash = "sha256:02bafc36b3a93d2511757a64ff88bd505e0a57fba08183a9150fbcfcb2015310", size = 2757 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/c9/8a73a5f8fa6e70fc02eed506d5ac0ae9ceafbd2b8c9ad34a7de0f29900d6/types_ujson-5.10.0.20250326-py3-none-any.whl", hash = "sha256:acc0913f569def62ef6a892c8a47703f65d05669a3252391a97765cf207dca5b", size = 7644 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2502,6 +2517,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.1.0"
|
||||
|
||||
@@ -36,6 +36,8 @@ analyzer:
|
||||
exclude:
|
||||
- openapi/**
|
||||
- lib/generated_plugin_registrant.dart
|
||||
- lib/**/*.g.dart
|
||||
- lib/**/*.drift.dart
|
||||
|
||||
plugins:
|
||||
- custom_lint
|
||||
|
||||
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 192,
|
||||
"android.injected.version.name" => "1.131.2",
|
||||
"android.injected.version.code" => 193,
|
||||
"android.injected.version.name" => "1.131.3",
|
||||
}
|
||||
)
|
||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||
|
||||
24
mobile/build.yaml
Normal file
24
mobile/build.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
targets:
|
||||
$default:
|
||||
builders:
|
||||
#drift @DriftDatabase()
|
||||
drift_dev:
|
||||
# Disable default builder to use modular builder instead
|
||||
enabled: false
|
||||
drift_dev:analyzer:
|
||||
enabled: true
|
||||
options: &drift_options
|
||||
store_date_time_values_as_text: true
|
||||
named_parameters: true
|
||||
write_from_json_string_constructor: false
|
||||
data_class_to_companions: false
|
||||
# Required for make-migrations
|
||||
databases:
|
||||
main: lib/infrastructure/repositories/db.repository.dart
|
||||
generate_for: &drift_generate_for
|
||||
- lib/infrastructure/entities/*.dart
|
||||
- lib/infrastructure/repositories/db.repository.dart
|
||||
drift_dev:modular:
|
||||
enabled: true
|
||||
options: *drift_options
|
||||
generate_for: *drift_generate_for
|
||||
1
mobile/drift_schemas/main/drift_schema_v1.json
Normal file
1
mobile/drift_schemas/main/drift_schema_v1.json
Normal file
@@ -0,0 +1 @@
|
||||
{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":true},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"user_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_admin","getter_name":"isAdmin","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_admin\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_admin\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"profile_image_path","getter_name":"profileImagePath","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"quota_size_in_bytes","getter_name":"quotaSizeInBytes","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"quota_usage_in_bytes","getter_name":"quotaUsageInBytes","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":1,"references":[0],"type":"table","data":{"name":"user_metadata_entity","was_declared_in_moor":false,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"blob","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"preferences","getter_name":"preferences","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"userPreferenceConverter","dart_type_name":"UserPreferences"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["user_id"]}},{"id":2,"references":[0],"type":"table","data":{"name":"partner_entity","was_declared_in_moor":false,"columns":[{"name":"shared_by_id","getter_name":"sharedById","moor_type":"blob","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"shared_with_id","getter_name":"sharedWithId","moor_type":"blob","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"in_timeline","getter_name":"inTimeline","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"in_timeline\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"in_timeline\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["shared_by_id","shared_with_id"]}}]}
|
||||
@@ -19,7 +19,7 @@ platform :ios do
|
||||
desc "iOS Release"
|
||||
lane :release do
|
||||
increment_version_number(
|
||||
version_number: "1.131.2"
|
||||
version_number: "1.131.3"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
||||
@@ -1,32 +1,4 @@
|
||||
import 'dart:ui';
|
||||
|
||||
enum AvatarColor {
|
||||
// do not change this order or reuse indices for other purposes, adding is OK
|
||||
primary,
|
||||
pink,
|
||||
red,
|
||||
yellow,
|
||||
blue,
|
||||
green,
|
||||
purple,
|
||||
orange,
|
||||
gray,
|
||||
amber;
|
||||
|
||||
Color toColor({bool isDarkTheme = false}) => switch (this) {
|
||||
AvatarColor.primary =>
|
||||
isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF),
|
||||
AvatarColor.pink => const Color.fromARGB(255, 244, 114, 182),
|
||||
AvatarColor.red => const Color.fromARGB(255, 239, 68, 68),
|
||||
AvatarColor.yellow => const Color.fromARGB(255, 234, 179, 8),
|
||||
AvatarColor.blue => const Color.fromARGB(255, 59, 130, 246),
|
||||
AvatarColor.green => const Color.fromARGB(255, 22, 163, 74),
|
||||
AvatarColor.purple => const Color.fromARGB(255, 147, 51, 234),
|
||||
AvatarColor.orange => const Color.fromARGB(255, 234, 88, 12),
|
||||
AvatarColor.gray => const Color.fromARGB(255, 75, 85, 99),
|
||||
AvatarColor.amber => const Color.fromARGB(255, 217, 119, 6),
|
||||
};
|
||||
}
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
|
||||
// TODO: Rename to User once Isar is removed
|
||||
class UserDto {
|
||||
|
||||
105
mobile/lib/domain/models/user_metadata.model.dart
Normal file
105
mobile/lib/domain/models/user_metadata.model.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'dart:ui';
|
||||
|
||||
enum AvatarColor {
|
||||
// do not change this order or reuse indices for other purposes, adding is OK
|
||||
primary("primary"),
|
||||
pink("pink"),
|
||||
red("red"),
|
||||
yellow("yellow"),
|
||||
blue("blue"),
|
||||
green("green"),
|
||||
purple("purple"),
|
||||
orange("orange"),
|
||||
gray("gray"),
|
||||
amber("amber");
|
||||
|
||||
final String value;
|
||||
const AvatarColor(this.value);
|
||||
|
||||
Color toColor({bool isDarkTheme = false}) => switch (this) {
|
||||
AvatarColor.primary =>
|
||||
isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF),
|
||||
AvatarColor.pink => const Color.fromARGB(255, 244, 114, 182),
|
||||
AvatarColor.red => const Color.fromARGB(255, 239, 68, 68),
|
||||
AvatarColor.yellow => const Color.fromARGB(255, 234, 179, 8),
|
||||
AvatarColor.blue => const Color.fromARGB(255, 59, 130, 246),
|
||||
AvatarColor.green => const Color.fromARGB(255, 22, 163, 74),
|
||||
AvatarColor.purple => const Color.fromARGB(255, 147, 51, 234),
|
||||
AvatarColor.orange => const Color.fromARGB(255, 234, 88, 12),
|
||||
AvatarColor.gray => const Color.fromARGB(255, 75, 85, 99),
|
||||
AvatarColor.amber => const Color.fromARGB(255, 217, 119, 6),
|
||||
};
|
||||
}
|
||||
|
||||
class UserPreferences {
|
||||
final bool foldersEnabled;
|
||||
final bool memoriesEnabled;
|
||||
final bool peopleEnabled;
|
||||
final bool ratingsEnabled;
|
||||
final bool sharedLinksEnabled;
|
||||
final bool tagsEnabled;
|
||||
final AvatarColor userAvatarColor;
|
||||
final bool showSupportBadge;
|
||||
|
||||
const UserPreferences({
|
||||
this.foldersEnabled = false,
|
||||
this.memoriesEnabled = true,
|
||||
this.peopleEnabled = true,
|
||||
this.ratingsEnabled = false,
|
||||
this.sharedLinksEnabled = true,
|
||||
this.tagsEnabled = false,
|
||||
this.userAvatarColor = AvatarColor.primary,
|
||||
this.showSupportBadge = true,
|
||||
});
|
||||
|
||||
UserPreferences copyWith({
|
||||
bool? foldersEnabled,
|
||||
bool? memoriesEnabled,
|
||||
bool? peopleEnabled,
|
||||
bool? ratingsEnabled,
|
||||
bool? sharedLinksEnabled,
|
||||
bool? tagsEnabled,
|
||||
AvatarColor? userAvatarColor,
|
||||
bool? showSupportBadge,
|
||||
}) {
|
||||
return UserPreferences(
|
||||
foldersEnabled: foldersEnabled ?? this.foldersEnabled,
|
||||
memoriesEnabled: memoriesEnabled ?? this.memoriesEnabled,
|
||||
peopleEnabled: peopleEnabled ?? this.peopleEnabled,
|
||||
ratingsEnabled: ratingsEnabled ?? this.ratingsEnabled,
|
||||
sharedLinksEnabled: sharedLinksEnabled ?? this.sharedLinksEnabled,
|
||||
tagsEnabled: tagsEnabled ?? this.tagsEnabled,
|
||||
userAvatarColor: userAvatarColor ?? this.userAvatarColor,
|
||||
showSupportBadge: showSupportBadge ?? this.showSupportBadge,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, Object?> toMap() {
|
||||
final preferences = <String, Object?>{};
|
||||
preferences["folders-Enabled"] = foldersEnabled;
|
||||
preferences["memories-Enabled"] = memoriesEnabled;
|
||||
preferences["people-Enabled"] = peopleEnabled;
|
||||
preferences["ratings-Enabled"] = ratingsEnabled;
|
||||
preferences["sharedLinks-Enabled"] = sharedLinksEnabled;
|
||||
preferences["tags-Enabled"] = tagsEnabled;
|
||||
preferences["avatar-Color"] = userAvatarColor.value;
|
||||
preferences["purchase-ShowSupportBadge"] = showSupportBadge;
|
||||
return preferences;
|
||||
}
|
||||
|
||||
factory UserPreferences.fromMap(Map<String, Object?> map) {
|
||||
return UserPreferences(
|
||||
foldersEnabled: map["folders-Enabled"] as bool? ?? false,
|
||||
memoriesEnabled: map["memories-Enabled"] as bool? ?? true,
|
||||
peopleEnabled: map["people-Enabled"] as bool? ?? true,
|
||||
ratingsEnabled: map["ratings-Enabled"] as bool? ?? false,
|
||||
sharedLinksEnabled: map["sharedLinks-Enabled"] as bool? ?? true,
|
||||
tagsEnabled: map["tags-Enabled"] as bool? ?? false,
|
||||
userAvatarColor: AvatarColor.values.firstWhere(
|
||||
(e) => e.value == map["avatar-Color"] as String?,
|
||||
orElse: () => AvatarColor.primary,
|
||||
),
|
||||
showSupportBadge: map["purchase-ShowSupportBadge"] as bool? ?? true,
|
||||
);
|
||||
}
|
||||
}
|
||||
18
mobile/lib/infrastructure/entities/partner.entity.dart
Normal file
18
mobile/lib/infrastructure/entities/partner.entity.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
|
||||
class PartnerEntity extends Table with DriftDefaultsMixin {
|
||||
const PartnerEntity();
|
||||
|
||||
BlobColumn get sharedById =>
|
||||
blob().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
||||
|
||||
BlobColumn get sharedWithId =>
|
||||
blob().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
||||
|
||||
BoolColumn get inTimeline => boolean().withDefault(const Constant(false))();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {sharedById, sharedWithId};
|
||||
}
|
||||
610
mobile/lib/infrastructure/entities/partner.entity.drift.dart
generated
Normal file
610
mobile/lib/infrastructure/entities/partner.entity.drift.dart
generated
Normal file
@@ -0,0 +1,610 @@
|
||||
// dart format width=80
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
|
||||
as i1;
|
||||
import 'dart:typed_data' as i2;
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart'
|
||||
as i3;
|
||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
|
||||
as i5;
|
||||
import 'package:drift/internal/modular.dart' as i6;
|
||||
|
||||
typedef $$PartnerEntityTableCreateCompanionBuilder = i1.PartnerEntityCompanion
|
||||
Function({
|
||||
required i2.Uint8List sharedById,
|
||||
required i2.Uint8List sharedWithId,
|
||||
i0.Value<bool> inTimeline,
|
||||
});
|
||||
typedef $$PartnerEntityTableUpdateCompanionBuilder = i1.PartnerEntityCompanion
|
||||
Function({
|
||||
i0.Value<i2.Uint8List> sharedById,
|
||||
i0.Value<i2.Uint8List> sharedWithId,
|
||||
i0.Value<bool> inTimeline,
|
||||
});
|
||||
|
||||
final class $$PartnerEntityTableReferences extends i0.BaseReferences<
|
||||
i0.GeneratedDatabase, i1.$PartnerEntityTable, i1.PartnerEntityData> {
|
||||
$$PartnerEntityTableReferences(
|
||||
super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static i5.$UserEntityTable _sharedByIdTable(i0.GeneratedDatabase db) =>
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
||||
.createAlias(i0.$_aliasNameGenerator(
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i1.$PartnerEntityTable>('partner_entity')
|
||||
.sharedById,
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
||||
.id));
|
||||
|
||||
i5.$$UserEntityTableProcessedTableManager get sharedById {
|
||||
final $_column = $_itemColumn<i2.Uint8List>('shared_by_id')!;
|
||||
|
||||
final manager = i5
|
||||
.$$UserEntityTableTableManager(
|
||||
$_db,
|
||||
i6.ReadDatabaseContainer($_db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'))
|
||||
.filter((f) => f.id.sqlEquals($_column));
|
||||
final item = $_typedResult.readTableOrNull(_sharedByIdTable($_db));
|
||||
if (item == null) return manager;
|
||||
return i0.ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: [item]));
|
||||
}
|
||||
|
||||
static i5.$UserEntityTable _sharedWithIdTable(i0.GeneratedDatabase db) =>
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
||||
.createAlias(i0.$_aliasNameGenerator(
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i1.$PartnerEntityTable>('partner_entity')
|
||||
.sharedWithId,
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
||||
.id));
|
||||
|
||||
i5.$$UserEntityTableProcessedTableManager get sharedWithId {
|
||||
final $_column = $_itemColumn<i2.Uint8List>('shared_with_id')!;
|
||||
|
||||
final manager = i5
|
||||
.$$UserEntityTableTableManager(
|
||||
$_db,
|
||||
i6.ReadDatabaseContainer($_db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'))
|
||||
.filter((f) => f.id.sqlEquals($_column));
|
||||
final item = $_typedResult.readTableOrNull(_sharedWithIdTable($_db));
|
||||
if (item == null) return manager;
|
||||
return i0.ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: [item]));
|
||||
}
|
||||
}
|
||||
|
||||
class $$PartnerEntityTableFilterComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$PartnerEntityTable> {
|
||||
$$PartnerEntityTableFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnFilters<bool> get inTimeline => $composableBuilder(
|
||||
column: $table.inTimeline, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i5.$$UserEntityTableFilterComposer get sharedById {
|
||||
final i5.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.sharedById,
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i5.$$UserEntityTableFilterComposer(
|
||||
$db: $db,
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
|
||||
i5.$$UserEntityTableFilterComposer get sharedWithId {
|
||||
final i5.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.sharedWithId,
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i5.$$UserEntityTableFilterComposer(
|
||||
$db: $db,
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $$PartnerEntityTableOrderingComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$PartnerEntityTable> {
|
||||
$$PartnerEntityTableOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnOrderings<bool> get inTimeline => $composableBuilder(
|
||||
column: $table.inTimeline,
|
||||
builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i5.$$UserEntityTableOrderingComposer get sharedById {
|
||||
final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.sharedById,
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i5.$$UserEntityTableOrderingComposer(
|
||||
$db: $db,
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
|
||||
i5.$$UserEntityTableOrderingComposer get sharedWithId {
|
||||
final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.sharedWithId,
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i5.$$UserEntityTableOrderingComposer(
|
||||
$db: $db,
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $$PartnerEntityTableAnnotationComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$PartnerEntityTable> {
|
||||
$$PartnerEntityTableAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.GeneratedColumn<bool> get inTimeline => $composableBuilder(
|
||||
column: $table.inTimeline, builder: (column) => column);
|
||||
|
||||
i5.$$UserEntityTableAnnotationComposer get sharedById {
|
||||
final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.sharedById,
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i5.$$UserEntityTableAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
|
||||
i5.$$UserEntityTableAnnotationComposer get sharedWithId {
|
||||
final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.sharedWithId,
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i5.$$UserEntityTableAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $$PartnerEntityTableTableManager extends i0.RootTableManager<
|
||||
i0.GeneratedDatabase,
|
||||
i1.$PartnerEntityTable,
|
||||
i1.PartnerEntityData,
|
||||
i1.$$PartnerEntityTableFilterComposer,
|
||||
i1.$$PartnerEntityTableOrderingComposer,
|
||||
i1.$$PartnerEntityTableAnnotationComposer,
|
||||
$$PartnerEntityTableCreateCompanionBuilder,
|
||||
$$PartnerEntityTableUpdateCompanionBuilder,
|
||||
(i1.PartnerEntityData, i1.$$PartnerEntityTableReferences),
|
||||
i1.PartnerEntityData,
|
||||
i0.PrefetchHooks Function({bool sharedById, bool sharedWithId})> {
|
||||
$$PartnerEntityTableTableManager(
|
||||
i0.GeneratedDatabase db, i1.$PartnerEntityTable table)
|
||||
: super(i0.TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
i1.$$PartnerEntityTableFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
i1.$$PartnerEntityTableOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
i1.$$PartnerEntityTableAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
i0.Value<i2.Uint8List> sharedById = const i0.Value.absent(),
|
||||
i0.Value<i2.Uint8List> sharedWithId = const i0.Value.absent(),
|
||||
i0.Value<bool> inTimeline = const i0.Value.absent(),
|
||||
}) =>
|
||||
i1.PartnerEntityCompanion(
|
||||
sharedById: sharedById,
|
||||
sharedWithId: sharedWithId,
|
||||
inTimeline: inTimeline,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required i2.Uint8List sharedById,
|
||||
required i2.Uint8List sharedWithId,
|
||||
i0.Value<bool> inTimeline = const i0.Value.absent(),
|
||||
}) =>
|
||||
i1.PartnerEntityCompanion.insert(
|
||||
sharedById: sharedById,
|
||||
sharedWithId: sharedWithId,
|
||||
inTimeline: inTimeline,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (
|
||||
e.readTable(table),
|
||||
i1.$$PartnerEntityTableReferences(db, table, e)
|
||||
))
|
||||
.toList(),
|
||||
prefetchHooksCallback: ({sharedById = false, sharedWithId = false}) {
|
||||
return i0.PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [],
|
||||
addJoins: <
|
||||
T extends i0.TableManagerState<
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic>>(state) {
|
||||
if (sharedById) {
|
||||
state = state.withJoin(
|
||||
currentTable: table,
|
||||
currentColumn: table.sharedById,
|
||||
referencedTable:
|
||||
i1.$$PartnerEntityTableReferences._sharedByIdTable(db),
|
||||
referencedColumn: i1.$$PartnerEntityTableReferences
|
||||
._sharedByIdTable(db)
|
||||
.id,
|
||||
) as T;
|
||||
}
|
||||
if (sharedWithId) {
|
||||
state = state.withJoin(
|
||||
currentTable: table,
|
||||
currentColumn: table.sharedWithId,
|
||||
referencedTable: i1.$$PartnerEntityTableReferences
|
||||
._sharedWithIdTable(db),
|
||||
referencedColumn: i1.$$PartnerEntityTableReferences
|
||||
._sharedWithIdTable(db)
|
||||
.id,
|
||||
) as T;
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
return [];
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
typedef $$PartnerEntityTableProcessedTableManager = i0.ProcessedTableManager<
|
||||
i0.GeneratedDatabase,
|
||||
i1.$PartnerEntityTable,
|
||||
i1.PartnerEntityData,
|
||||
i1.$$PartnerEntityTableFilterComposer,
|
||||
i1.$$PartnerEntityTableOrderingComposer,
|
||||
i1.$$PartnerEntityTableAnnotationComposer,
|
||||
$$PartnerEntityTableCreateCompanionBuilder,
|
||||
$$PartnerEntityTableUpdateCompanionBuilder,
|
||||
(i1.PartnerEntityData, i1.$$PartnerEntityTableReferences),
|
||||
i1.PartnerEntityData,
|
||||
i0.PrefetchHooks Function({bool sharedById, bool sharedWithId})>;
|
||||
|
||||
class $PartnerEntityTable extends i3.PartnerEntity
|
||||
with i0.TableInfo<$PartnerEntityTable, i1.PartnerEntityData> {
|
||||
@override
|
||||
final i0.GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$PartnerEntityTable(this.attachedDatabase, [this._alias]);
|
||||
static const i0.VerificationMeta _sharedByIdMeta =
|
||||
const i0.VerificationMeta('sharedById');
|
||||
@override
|
||||
late final i0.GeneratedColumn<i2.Uint8List> sharedById =
|
||||
i0.GeneratedColumn<i2.Uint8List>('shared_by_id', aliasedName, false,
|
||||
type: i0.DriftSqlType.blob,
|
||||
requiredDuringInsert: true,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES user_entity (id) ON DELETE CASCADE'));
|
||||
static const i0.VerificationMeta _sharedWithIdMeta =
|
||||
const i0.VerificationMeta('sharedWithId');
|
||||
@override
|
||||
late final i0.GeneratedColumn<i2.Uint8List> sharedWithId =
|
||||
i0.GeneratedColumn<i2.Uint8List>('shared_with_id', aliasedName, false,
|
||||
type: i0.DriftSqlType.blob,
|
||||
requiredDuringInsert: true,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES user_entity (id) ON DELETE CASCADE'));
|
||||
static const i0.VerificationMeta _inTimelineMeta =
|
||||
const i0.VerificationMeta('inTimeline');
|
||||
@override
|
||||
late final i0.GeneratedColumn<bool> inTimeline = i0.GeneratedColumn<bool>(
|
||||
'in_timeline', aliasedName, false,
|
||||
type: i0.DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("in_timeline" IN (0, 1))'),
|
||||
defaultValue: const i4.Constant(false));
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns =>
|
||||
[sharedById, sharedWithId, inTimeline];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'partner_entity';
|
||||
@override
|
||||
i0.VerificationContext validateIntegrity(
|
||||
i0.Insertable<i1.PartnerEntityData> instance,
|
||||
{bool isInserting = false}) {
|
||||
final context = i0.VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('shared_by_id')) {
|
||||
context.handle(
|
||||
_sharedByIdMeta,
|
||||
sharedById.isAcceptableOrUnknown(
|
||||
data['shared_by_id']!, _sharedByIdMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_sharedByIdMeta);
|
||||
}
|
||||
if (data.containsKey('shared_with_id')) {
|
||||
context.handle(
|
||||
_sharedWithIdMeta,
|
||||
sharedWithId.isAcceptableOrUnknown(
|
||||
data['shared_with_id']!, _sharedWithIdMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_sharedWithIdMeta);
|
||||
}
|
||||
if (data.containsKey('in_timeline')) {
|
||||
context.handle(
|
||||
_inTimelineMeta,
|
||||
inTimeline.isAcceptableOrUnknown(
|
||||
data['in_timeline']!, _inTimelineMeta));
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<i0.GeneratedColumn> get $primaryKey => {sharedById, sharedWithId};
|
||||
@override
|
||||
i1.PartnerEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return i1.PartnerEntityData(
|
||||
sharedById: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.blob, data['${effectivePrefix}shared_by_id'])!,
|
||||
sharedWithId: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.blob, data['${effectivePrefix}shared_with_id'])!,
|
||||
inTimeline: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.bool, data['${effectivePrefix}in_timeline'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
$PartnerEntityTable createAlias(String alias) {
|
||||
return $PartnerEntityTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get withoutRowId => true;
|
||||
@override
|
||||
bool get isStrict => true;
|
||||
}
|
||||
|
||||
class PartnerEntityData extends i0.DataClass
|
||||
implements i0.Insertable<i1.PartnerEntityData> {
|
||||
final i2.Uint8List sharedById;
|
||||
final i2.Uint8List sharedWithId;
|
||||
final bool inTimeline;
|
||||
const PartnerEntityData(
|
||||
{required this.sharedById,
|
||||
required this.sharedWithId,
|
||||
required this.inTimeline});
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
map['shared_by_id'] = i0.Variable<i2.Uint8List>(sharedById);
|
||||
map['shared_with_id'] = i0.Variable<i2.Uint8List>(sharedWithId);
|
||||
map['in_timeline'] = i0.Variable<bool>(inTimeline);
|
||||
return map;
|
||||
}
|
||||
|
||||
factory PartnerEntityData.fromJson(Map<String, dynamic> json,
|
||||
{i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return PartnerEntityData(
|
||||
sharedById: serializer.fromJson<i2.Uint8List>(json['sharedById']),
|
||||
sharedWithId: serializer.fromJson<i2.Uint8List>(json['sharedWithId']),
|
||||
inTimeline: serializer.fromJson<bool>(json['inTimeline']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'sharedById': serializer.toJson<i2.Uint8List>(sharedById),
|
||||
'sharedWithId': serializer.toJson<i2.Uint8List>(sharedWithId),
|
||||
'inTimeline': serializer.toJson<bool>(inTimeline),
|
||||
};
|
||||
}
|
||||
|
||||
i1.PartnerEntityData copyWith(
|
||||
{i2.Uint8List? sharedById,
|
||||
i2.Uint8List? sharedWithId,
|
||||
bool? inTimeline}) =>
|
||||
i1.PartnerEntityData(
|
||||
sharedById: sharedById ?? this.sharedById,
|
||||
sharedWithId: sharedWithId ?? this.sharedWithId,
|
||||
inTimeline: inTimeline ?? this.inTimeline,
|
||||
);
|
||||
PartnerEntityData copyWithCompanion(i1.PartnerEntityCompanion data) {
|
||||
return PartnerEntityData(
|
||||
sharedById:
|
||||
data.sharedById.present ? data.sharedById.value : this.sharedById,
|
||||
sharedWithId: data.sharedWithId.present
|
||||
? data.sharedWithId.value
|
||||
: this.sharedWithId,
|
||||
inTimeline:
|
||||
data.inTimeline.present ? data.inTimeline.value : this.inTimeline,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('PartnerEntityData(')
|
||||
..write('sharedById: $sharedById, ')
|
||||
..write('sharedWithId: $sharedWithId, ')
|
||||
..write('inTimeline: $inTimeline')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(i0.$driftBlobEquality.hash(sharedById),
|
||||
i0.$driftBlobEquality.hash(sharedWithId), inTimeline);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is i1.PartnerEntityData &&
|
||||
i0.$driftBlobEquality.equals(other.sharedById, this.sharedById) &&
|
||||
i0.$driftBlobEquality.equals(other.sharedWithId, this.sharedWithId) &&
|
||||
other.inTimeline == this.inTimeline);
|
||||
}
|
||||
|
||||
class PartnerEntityCompanion extends i0.UpdateCompanion<i1.PartnerEntityData> {
|
||||
final i0.Value<i2.Uint8List> sharedById;
|
||||
final i0.Value<i2.Uint8List> sharedWithId;
|
||||
final i0.Value<bool> inTimeline;
|
||||
const PartnerEntityCompanion({
|
||||
this.sharedById = const i0.Value.absent(),
|
||||
this.sharedWithId = const i0.Value.absent(),
|
||||
this.inTimeline = const i0.Value.absent(),
|
||||
});
|
||||
PartnerEntityCompanion.insert({
|
||||
required i2.Uint8List sharedById,
|
||||
required i2.Uint8List sharedWithId,
|
||||
this.inTimeline = const i0.Value.absent(),
|
||||
}) : sharedById = i0.Value(sharedById),
|
||||
sharedWithId = i0.Value(sharedWithId);
|
||||
static i0.Insertable<i1.PartnerEntityData> custom({
|
||||
i0.Expression<i2.Uint8List>? sharedById,
|
||||
i0.Expression<i2.Uint8List>? sharedWithId,
|
||||
i0.Expression<bool>? inTimeline,
|
||||
}) {
|
||||
return i0.RawValuesInsertable({
|
||||
if (sharedById != null) 'shared_by_id': sharedById,
|
||||
if (sharedWithId != null) 'shared_with_id': sharedWithId,
|
||||
if (inTimeline != null) 'in_timeline': inTimeline,
|
||||
});
|
||||
}
|
||||
|
||||
i1.PartnerEntityCompanion copyWith(
|
||||
{i0.Value<i2.Uint8List>? sharedById,
|
||||
i0.Value<i2.Uint8List>? sharedWithId,
|
||||
i0.Value<bool>? inTimeline}) {
|
||||
return i1.PartnerEntityCompanion(
|
||||
sharedById: sharedById ?? this.sharedById,
|
||||
sharedWithId: sharedWithId ?? this.sharedWithId,
|
||||
inTimeline: inTimeline ?? this.inTimeline,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
if (sharedById.present) {
|
||||
map['shared_by_id'] = i0.Variable<i2.Uint8List>(sharedById.value);
|
||||
}
|
||||
if (sharedWithId.present) {
|
||||
map['shared_with_id'] = i0.Variable<i2.Uint8List>(sharedWithId.value);
|
||||
}
|
||||
if (inTimeline.present) {
|
||||
map['in_timeline'] = i0.Variable<bool>(inTimeline.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('PartnerEntityCompanion(')
|
||||
..write('sharedById: $sharedById, ')
|
||||
..write('sharedWithId: $sharedWithId, ')
|
||||
..write('inTimeline: $inTimeline')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import 'package:drift/drift.dart' hide Index;
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
import 'package:immich_mobile/utils/hash.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
@@ -71,3 +74,20 @@ class User {
|
||||
quotaSizeInBytes: quotaSizeInBytes,
|
||||
);
|
||||
}
|
||||
|
||||
class UserEntity extends Table with DriftDefaultsMixin {
|
||||
const UserEntity();
|
||||
|
||||
BlobColumn get id => blob()();
|
||||
TextColumn get name => text()();
|
||||
BoolColumn get isAdmin => boolean().withDefault(const Constant(false))();
|
||||
TextColumn get email => text()();
|
||||
TextColumn get profileImagePath => text().nullable()();
|
||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
// Quota
|
||||
IntColumn get quotaSizeInBytes => integer().nullable()();
|
||||
IntColumn get quotaUsageInBytes => integer().withDefault(const Constant(0))();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
656
mobile/lib/infrastructure/entities/user.entity.drift.dart
generated
Normal file
656
mobile/lib/infrastructure/entities/user.entity.drift.dart
generated
Normal file
@@ -0,0 +1,656 @@
|
||||
// dart format width=80
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
|
||||
as i1;
|
||||
import 'dart:typed_data' as i2;
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as i3;
|
||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
||||
|
||||
typedef $$UserEntityTableCreateCompanionBuilder = i1.UserEntityCompanion
|
||||
Function({
|
||||
required i2.Uint8List id,
|
||||
required String name,
|
||||
i0.Value<bool> isAdmin,
|
||||
required String email,
|
||||
i0.Value<String?> profileImagePath,
|
||||
i0.Value<DateTime> updatedAt,
|
||||
i0.Value<int?> quotaSizeInBytes,
|
||||
i0.Value<int> quotaUsageInBytes,
|
||||
});
|
||||
typedef $$UserEntityTableUpdateCompanionBuilder = i1.UserEntityCompanion
|
||||
Function({
|
||||
i0.Value<i2.Uint8List> id,
|
||||
i0.Value<String> name,
|
||||
i0.Value<bool> isAdmin,
|
||||
i0.Value<String> email,
|
||||
i0.Value<String?> profileImagePath,
|
||||
i0.Value<DateTime> updatedAt,
|
||||
i0.Value<int?> quotaSizeInBytes,
|
||||
i0.Value<int> quotaUsageInBytes,
|
||||
});
|
||||
|
||||
class $$UserEntityTableFilterComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$UserEntityTable> {
|
||||
$$UserEntityTableFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnFilters<i2.Uint8List> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i0.ColumnFilters<String> get name => $composableBuilder(
|
||||
column: $table.name, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i0.ColumnFilters<bool> get isAdmin => $composableBuilder(
|
||||
column: $table.isAdmin, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i0.ColumnFilters<String> get email => $composableBuilder(
|
||||
column: $table.email, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i0.ColumnFilters<String> get profileImagePath => $composableBuilder(
|
||||
column: $table.profileImagePath,
|
||||
builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i0.ColumnFilters<DateTime> get updatedAt => $composableBuilder(
|
||||
column: $table.updatedAt, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i0.ColumnFilters<int> get quotaSizeInBytes => $composableBuilder(
|
||||
column: $table.quotaSizeInBytes,
|
||||
builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i0.ColumnFilters<int> get quotaUsageInBytes => $composableBuilder(
|
||||
column: $table.quotaUsageInBytes,
|
||||
builder: (column) => i0.ColumnFilters(column));
|
||||
}
|
||||
|
||||
class $$UserEntityTableOrderingComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$UserEntityTable> {
|
||||
$$UserEntityTableOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnOrderings<i2.Uint8List> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<String> get name => $composableBuilder(
|
||||
column: $table.name, builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<bool> get isAdmin => $composableBuilder(
|
||||
column: $table.isAdmin, builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<String> get email => $composableBuilder(
|
||||
column: $table.email, builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<String> get profileImagePath => $composableBuilder(
|
||||
column: $table.profileImagePath,
|
||||
builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<DateTime> get updatedAt => $composableBuilder(
|
||||
column: $table.updatedAt,
|
||||
builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<int> get quotaSizeInBytes => $composableBuilder(
|
||||
column: $table.quotaSizeInBytes,
|
||||
builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<int> get quotaUsageInBytes => $composableBuilder(
|
||||
column: $table.quotaUsageInBytes,
|
||||
builder: (column) => i0.ColumnOrderings(column));
|
||||
}
|
||||
|
||||
class $$UserEntityTableAnnotationComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$UserEntityTable> {
|
||||
$$UserEntityTableAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.GeneratedColumn<i2.Uint8List> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<String> get name =>
|
||||
$composableBuilder(column: $table.name, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<bool> get isAdmin =>
|
||||
$composableBuilder(column: $table.isAdmin, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<String> get email =>
|
||||
$composableBuilder(column: $table.email, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<String> get profileImagePath => $composableBuilder(
|
||||
column: $table.profileImagePath, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<DateTime> get updatedAt =>
|
||||
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<int> get quotaSizeInBytes => $composableBuilder(
|
||||
column: $table.quotaSizeInBytes, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<int> get quotaUsageInBytes => $composableBuilder(
|
||||
column: $table.quotaUsageInBytes, builder: (column) => column);
|
||||
}
|
||||
|
||||
class $$UserEntityTableTableManager extends i0.RootTableManager<
|
||||
i0.GeneratedDatabase,
|
||||
i1.$UserEntityTable,
|
||||
i1.UserEntityData,
|
||||
i1.$$UserEntityTableFilterComposer,
|
||||
i1.$$UserEntityTableOrderingComposer,
|
||||
i1.$$UserEntityTableAnnotationComposer,
|
||||
$$UserEntityTableCreateCompanionBuilder,
|
||||
$$UserEntityTableUpdateCompanionBuilder,
|
||||
(
|
||||
i1.UserEntityData,
|
||||
i0.BaseReferences<i0.GeneratedDatabase, i1.$UserEntityTable,
|
||||
i1.UserEntityData>
|
||||
),
|
||||
i1.UserEntityData,
|
||||
i0.PrefetchHooks Function()> {
|
||||
$$UserEntityTableTableManager(
|
||||
i0.GeneratedDatabase db, i1.$UserEntityTable table)
|
||||
: super(i0.TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
i1.$$UserEntityTableFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
i1.$$UserEntityTableOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
i1.$$UserEntityTableAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
i0.Value<i2.Uint8List> id = const i0.Value.absent(),
|
||||
i0.Value<String> name = const i0.Value.absent(),
|
||||
i0.Value<bool> isAdmin = const i0.Value.absent(),
|
||||
i0.Value<String> email = const i0.Value.absent(),
|
||||
i0.Value<String?> profileImagePath = const i0.Value.absent(),
|
||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||
i0.Value<int?> quotaSizeInBytes = const i0.Value.absent(),
|
||||
i0.Value<int> quotaUsageInBytes = const i0.Value.absent(),
|
||||
}) =>
|
||||
i1.UserEntityCompanion(
|
||||
id: id,
|
||||
name: name,
|
||||
isAdmin: isAdmin,
|
||||
email: email,
|
||||
profileImagePath: profileImagePath,
|
||||
updatedAt: updatedAt,
|
||||
quotaSizeInBytes: quotaSizeInBytes,
|
||||
quotaUsageInBytes: quotaUsageInBytes,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required i2.Uint8List id,
|
||||
required String name,
|
||||
i0.Value<bool> isAdmin = const i0.Value.absent(),
|
||||
required String email,
|
||||
i0.Value<String?> profileImagePath = const i0.Value.absent(),
|
||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||
i0.Value<int?> quotaSizeInBytes = const i0.Value.absent(),
|
||||
i0.Value<int> quotaUsageInBytes = const i0.Value.absent(),
|
||||
}) =>
|
||||
i1.UserEntityCompanion.insert(
|
||||
id: id,
|
||||
name: name,
|
||||
isAdmin: isAdmin,
|
||||
email: email,
|
||||
profileImagePath: profileImagePath,
|
||||
updatedAt: updatedAt,
|
||||
quotaSizeInBytes: quotaSizeInBytes,
|
||||
quotaUsageInBytes: quotaUsageInBytes,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
));
|
||||
}
|
||||
|
||||
typedef $$UserEntityTableProcessedTableManager = i0.ProcessedTableManager<
|
||||
i0.GeneratedDatabase,
|
||||
i1.$UserEntityTable,
|
||||
i1.UserEntityData,
|
||||
i1.$$UserEntityTableFilterComposer,
|
||||
i1.$$UserEntityTableOrderingComposer,
|
||||
i1.$$UserEntityTableAnnotationComposer,
|
||||
$$UserEntityTableCreateCompanionBuilder,
|
||||
$$UserEntityTableUpdateCompanionBuilder,
|
||||
(
|
||||
i1.UserEntityData,
|
||||
i0.BaseReferences<i0.GeneratedDatabase, i1.$UserEntityTable,
|
||||
i1.UserEntityData>
|
||||
),
|
||||
i1.UserEntityData,
|
||||
i0.PrefetchHooks Function()>;
|
||||
|
||||
class $UserEntityTable extends i3.UserEntity
|
||||
with i0.TableInfo<$UserEntityTable, i1.UserEntityData> {
|
||||
@override
|
||||
final i0.GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$UserEntityTable(this.attachedDatabase, [this._alias]);
|
||||
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
|
||||
@override
|
||||
late final i0.GeneratedColumn<i2.Uint8List> id =
|
||||
i0.GeneratedColumn<i2.Uint8List>('id', aliasedName, false,
|
||||
type: i0.DriftSqlType.blob, requiredDuringInsert: true);
|
||||
static const i0.VerificationMeta _nameMeta =
|
||||
const i0.VerificationMeta('name');
|
||||
@override
|
||||
late final i0.GeneratedColumn<String> name = i0.GeneratedColumn<String>(
|
||||
'name', aliasedName, false,
|
||||
type: i0.DriftSqlType.string, requiredDuringInsert: true);
|
||||
static const i0.VerificationMeta _isAdminMeta =
|
||||
const i0.VerificationMeta('isAdmin');
|
||||
@override
|
||||
late final i0.GeneratedColumn<bool> isAdmin = i0.GeneratedColumn<bool>(
|
||||
'is_admin', aliasedName, false,
|
||||
type: i0.DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints:
|
||||
i0.GeneratedColumn.constraintIsAlways('CHECK ("is_admin" IN (0, 1))'),
|
||||
defaultValue: const i4.Constant(false));
|
||||
static const i0.VerificationMeta _emailMeta =
|
||||
const i0.VerificationMeta('email');
|
||||
@override
|
||||
late final i0.GeneratedColumn<String> email = i0.GeneratedColumn<String>(
|
||||
'email', aliasedName, false,
|
||||
type: i0.DriftSqlType.string, requiredDuringInsert: true);
|
||||
static const i0.VerificationMeta _profileImagePathMeta =
|
||||
const i0.VerificationMeta('profileImagePath');
|
||||
@override
|
||||
late final i0.GeneratedColumn<String> profileImagePath =
|
||||
i0.GeneratedColumn<String>('profile_image_path', aliasedName, true,
|
||||
type: i0.DriftSqlType.string, requiredDuringInsert: false);
|
||||
static const i0.VerificationMeta _updatedAtMeta =
|
||||
const i0.VerificationMeta('updatedAt');
|
||||
@override
|
||||
late final i0.GeneratedColumn<DateTime> updatedAt =
|
||||
i0.GeneratedColumn<DateTime>('updated_at', aliasedName, false,
|
||||
type: i0.DriftSqlType.dateTime,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: i4.currentDateAndTime);
|
||||
static const i0.VerificationMeta _quotaSizeInBytesMeta =
|
||||
const i0.VerificationMeta('quotaSizeInBytes');
|
||||
@override
|
||||
late final i0.GeneratedColumn<int> quotaSizeInBytes = i0.GeneratedColumn<int>(
|
||||
'quota_size_in_bytes', aliasedName, true,
|
||||
type: i0.DriftSqlType.int, requiredDuringInsert: false);
|
||||
static const i0.VerificationMeta _quotaUsageInBytesMeta =
|
||||
const i0.VerificationMeta('quotaUsageInBytes');
|
||||
@override
|
||||
late final i0.GeneratedColumn<int> quotaUsageInBytes =
|
||||
i0.GeneratedColumn<int>('quota_usage_in_bytes', aliasedName, false,
|
||||
type: i0.DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: const i4.Constant(0));
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns => [
|
||||
id,
|
||||
name,
|
||||
isAdmin,
|
||||
email,
|
||||
profileImagePath,
|
||||
updatedAt,
|
||||
quotaSizeInBytes,
|
||||
quotaUsageInBytes
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'user_entity';
|
||||
@override
|
||||
i0.VerificationContext validateIntegrity(
|
||||
i0.Insertable<i1.UserEntityData> instance,
|
||||
{bool isInserting = false}) {
|
||||
final context = i0.VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('id')) {
|
||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_idMeta);
|
||||
}
|
||||
if (data.containsKey('name')) {
|
||||
context.handle(
|
||||
_nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_nameMeta);
|
||||
}
|
||||
if (data.containsKey('is_admin')) {
|
||||
context.handle(_isAdminMeta,
|
||||
isAdmin.isAcceptableOrUnknown(data['is_admin']!, _isAdminMeta));
|
||||
}
|
||||
if (data.containsKey('email')) {
|
||||
context.handle(
|
||||
_emailMeta, email.isAcceptableOrUnknown(data['email']!, _emailMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_emailMeta);
|
||||
}
|
||||
if (data.containsKey('profile_image_path')) {
|
||||
context.handle(
|
||||
_profileImagePathMeta,
|
||||
profileImagePath.isAcceptableOrUnknown(
|
||||
data['profile_image_path']!, _profileImagePathMeta));
|
||||
}
|
||||
if (data.containsKey('updated_at')) {
|
||||
context.handle(_updatedAtMeta,
|
||||
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta));
|
||||
}
|
||||
if (data.containsKey('quota_size_in_bytes')) {
|
||||
context.handle(
|
||||
_quotaSizeInBytesMeta,
|
||||
quotaSizeInBytes.isAcceptableOrUnknown(
|
||||
data['quota_size_in_bytes']!, _quotaSizeInBytesMeta));
|
||||
}
|
||||
if (data.containsKey('quota_usage_in_bytes')) {
|
||||
context.handle(
|
||||
_quotaUsageInBytesMeta,
|
||||
quotaUsageInBytes.isAcceptableOrUnknown(
|
||||
data['quota_usage_in_bytes']!, _quotaUsageInBytesMeta));
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<i0.GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
i1.UserEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return i1.UserEntityData(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.blob, data['${effectivePrefix}id'])!,
|
||||
name: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}name'])!,
|
||||
isAdmin: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.bool, data['${effectivePrefix}is_admin'])!,
|
||||
email: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}email'])!,
|
||||
profileImagePath: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string, data['${effectivePrefix}profile_image_path']),
|
||||
updatedAt: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!,
|
||||
quotaSizeInBytes: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.int, data['${effectivePrefix}quota_size_in_bytes']),
|
||||
quotaUsageInBytes: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.int, data['${effectivePrefix}quota_usage_in_bytes'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
$UserEntityTable createAlias(String alias) {
|
||||
return $UserEntityTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get withoutRowId => true;
|
||||
@override
|
||||
bool get isStrict => true;
|
||||
}
|
||||
|
||||
class UserEntityData extends i0.DataClass
|
||||
implements i0.Insertable<i1.UserEntityData> {
|
||||
final i2.Uint8List id;
|
||||
final String name;
|
||||
final bool isAdmin;
|
||||
final String email;
|
||||
final String? profileImagePath;
|
||||
final DateTime updatedAt;
|
||||
final int? quotaSizeInBytes;
|
||||
final int quotaUsageInBytes;
|
||||
const UserEntityData(
|
||||
{required this.id,
|
||||
required this.name,
|
||||
required this.isAdmin,
|
||||
required this.email,
|
||||
this.profileImagePath,
|
||||
required this.updatedAt,
|
||||
this.quotaSizeInBytes,
|
||||
required this.quotaUsageInBytes});
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
map['id'] = i0.Variable<i2.Uint8List>(id);
|
||||
map['name'] = i0.Variable<String>(name);
|
||||
map['is_admin'] = i0.Variable<bool>(isAdmin);
|
||||
map['email'] = i0.Variable<String>(email);
|
||||
if (!nullToAbsent || profileImagePath != null) {
|
||||
map['profile_image_path'] = i0.Variable<String>(profileImagePath);
|
||||
}
|
||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
||||
if (!nullToAbsent || quotaSizeInBytes != null) {
|
||||
map['quota_size_in_bytes'] = i0.Variable<int>(quotaSizeInBytes);
|
||||
}
|
||||
map['quota_usage_in_bytes'] = i0.Variable<int>(quotaUsageInBytes);
|
||||
return map;
|
||||
}
|
||||
|
||||
factory UserEntityData.fromJson(Map<String, dynamic> json,
|
||||
{i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return UserEntityData(
|
||||
id: serializer.fromJson<i2.Uint8List>(json['id']),
|
||||
name: serializer.fromJson<String>(json['name']),
|
||||
isAdmin: serializer.fromJson<bool>(json['isAdmin']),
|
||||
email: serializer.fromJson<String>(json['email']),
|
||||
profileImagePath: serializer.fromJson<String?>(json['profileImagePath']),
|
||||
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||
quotaSizeInBytes: serializer.fromJson<int?>(json['quotaSizeInBytes']),
|
||||
quotaUsageInBytes: serializer.fromJson<int>(json['quotaUsageInBytes']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<i2.Uint8List>(id),
|
||||
'name': serializer.toJson<String>(name),
|
||||
'isAdmin': serializer.toJson<bool>(isAdmin),
|
||||
'email': serializer.toJson<String>(email),
|
||||
'profileImagePath': serializer.toJson<String?>(profileImagePath),
|
||||
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
||||
'quotaSizeInBytes': serializer.toJson<int?>(quotaSizeInBytes),
|
||||
'quotaUsageInBytes': serializer.toJson<int>(quotaUsageInBytes),
|
||||
};
|
||||
}
|
||||
|
||||
i1.UserEntityData copyWith(
|
||||
{i2.Uint8List? id,
|
||||
String? name,
|
||||
bool? isAdmin,
|
||||
String? email,
|
||||
i0.Value<String?> profileImagePath = const i0.Value.absent(),
|
||||
DateTime? updatedAt,
|
||||
i0.Value<int?> quotaSizeInBytes = const i0.Value.absent(),
|
||||
int? quotaUsageInBytes}) =>
|
||||
i1.UserEntityData(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
isAdmin: isAdmin ?? this.isAdmin,
|
||||
email: email ?? this.email,
|
||||
profileImagePath: profileImagePath.present
|
||||
? profileImagePath.value
|
||||
: this.profileImagePath,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
quotaSizeInBytes: quotaSizeInBytes.present
|
||||
? quotaSizeInBytes.value
|
||||
: this.quotaSizeInBytes,
|
||||
quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes,
|
||||
);
|
||||
UserEntityData copyWithCompanion(i1.UserEntityCompanion data) {
|
||||
return UserEntityData(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
name: data.name.present ? data.name.value : this.name,
|
||||
isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin,
|
||||
email: data.email.present ? data.email.value : this.email,
|
||||
profileImagePath: data.profileImagePath.present
|
||||
? data.profileImagePath.value
|
||||
: this.profileImagePath,
|
||||
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
||||
quotaSizeInBytes: data.quotaSizeInBytes.present
|
||||
? data.quotaSizeInBytes.value
|
||||
: this.quotaSizeInBytes,
|
||||
quotaUsageInBytes: data.quotaUsageInBytes.present
|
||||
? data.quotaUsageInBytes.value
|
||||
: this.quotaUsageInBytes,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('UserEntityData(')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('isAdmin: $isAdmin, ')
|
||||
..write('email: $email, ')
|
||||
..write('profileImagePath: $profileImagePath, ')
|
||||
..write('updatedAt: $updatedAt, ')
|
||||
..write('quotaSizeInBytes: $quotaSizeInBytes, ')
|
||||
..write('quotaUsageInBytes: $quotaUsageInBytes')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(i0.$driftBlobEquality.hash(id), name, isAdmin,
|
||||
email, profileImagePath, updatedAt, quotaSizeInBytes, quotaUsageInBytes);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is i1.UserEntityData &&
|
||||
i0.$driftBlobEquality.equals(other.id, this.id) &&
|
||||
other.name == this.name &&
|
||||
other.isAdmin == this.isAdmin &&
|
||||
other.email == this.email &&
|
||||
other.profileImagePath == this.profileImagePath &&
|
||||
other.updatedAt == this.updatedAt &&
|
||||
other.quotaSizeInBytes == this.quotaSizeInBytes &&
|
||||
other.quotaUsageInBytes == this.quotaUsageInBytes);
|
||||
}
|
||||
|
||||
class UserEntityCompanion extends i0.UpdateCompanion<i1.UserEntityData> {
|
||||
final i0.Value<i2.Uint8List> id;
|
||||
final i0.Value<String> name;
|
||||
final i0.Value<bool> isAdmin;
|
||||
final i0.Value<String> email;
|
||||
final i0.Value<String?> profileImagePath;
|
||||
final i0.Value<DateTime> updatedAt;
|
||||
final i0.Value<int?> quotaSizeInBytes;
|
||||
final i0.Value<int> quotaUsageInBytes;
|
||||
const UserEntityCompanion({
|
||||
this.id = const i0.Value.absent(),
|
||||
this.name = const i0.Value.absent(),
|
||||
this.isAdmin = const i0.Value.absent(),
|
||||
this.email = const i0.Value.absent(),
|
||||
this.profileImagePath = const i0.Value.absent(),
|
||||
this.updatedAt = const i0.Value.absent(),
|
||||
this.quotaSizeInBytes = const i0.Value.absent(),
|
||||
this.quotaUsageInBytes = const i0.Value.absent(),
|
||||
});
|
||||
UserEntityCompanion.insert({
|
||||
required i2.Uint8List id,
|
||||
required String name,
|
||||
this.isAdmin = const i0.Value.absent(),
|
||||
required String email,
|
||||
this.profileImagePath = const i0.Value.absent(),
|
||||
this.updatedAt = const i0.Value.absent(),
|
||||
this.quotaSizeInBytes = const i0.Value.absent(),
|
||||
this.quotaUsageInBytes = const i0.Value.absent(),
|
||||
}) : id = i0.Value(id),
|
||||
name = i0.Value(name),
|
||||
email = i0.Value(email);
|
||||
static i0.Insertable<i1.UserEntityData> custom({
|
||||
i0.Expression<i2.Uint8List>? id,
|
||||
i0.Expression<String>? name,
|
||||
i0.Expression<bool>? isAdmin,
|
||||
i0.Expression<String>? email,
|
||||
i0.Expression<String>? profileImagePath,
|
||||
i0.Expression<DateTime>? updatedAt,
|
||||
i0.Expression<int>? quotaSizeInBytes,
|
||||
i0.Expression<int>? quotaUsageInBytes,
|
||||
}) {
|
||||
return i0.RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (name != null) 'name': name,
|
||||
if (isAdmin != null) 'is_admin': isAdmin,
|
||||
if (email != null) 'email': email,
|
||||
if (profileImagePath != null) 'profile_image_path': profileImagePath,
|
||||
if (updatedAt != null) 'updated_at': updatedAt,
|
||||
if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes,
|
||||
if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes,
|
||||
});
|
||||
}
|
||||
|
||||
i1.UserEntityCompanion copyWith(
|
||||
{i0.Value<i2.Uint8List>? id,
|
||||
i0.Value<String>? name,
|
||||
i0.Value<bool>? isAdmin,
|
||||
i0.Value<String>? email,
|
||||
i0.Value<String?>? profileImagePath,
|
||||
i0.Value<DateTime>? updatedAt,
|
||||
i0.Value<int?>? quotaSizeInBytes,
|
||||
i0.Value<int>? quotaUsageInBytes}) {
|
||||
return i1.UserEntityCompanion(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
isAdmin: isAdmin ?? this.isAdmin,
|
||||
email: email ?? this.email,
|
||||
profileImagePath: profileImagePath ?? this.profileImagePath,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes,
|
||||
quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = i0.Variable<i2.Uint8List>(id.value);
|
||||
}
|
||||
if (name.present) {
|
||||
map['name'] = i0.Variable<String>(name.value);
|
||||
}
|
||||
if (isAdmin.present) {
|
||||
map['is_admin'] = i0.Variable<bool>(isAdmin.value);
|
||||
}
|
||||
if (email.present) {
|
||||
map['email'] = i0.Variable<String>(email.value);
|
||||
}
|
||||
if (profileImagePath.present) {
|
||||
map['profile_image_path'] = i0.Variable<String>(profileImagePath.value);
|
||||
}
|
||||
if (updatedAt.present) {
|
||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
||||
}
|
||||
if (quotaSizeInBytes.present) {
|
||||
map['quota_size_in_bytes'] = i0.Variable<int>(quotaSizeInBytes.value);
|
||||
}
|
||||
if (quotaUsageInBytes.present) {
|
||||
map['quota_usage_in_bytes'] = i0.Variable<int>(quotaUsageInBytes.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('UserEntityCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('isAdmin: $isAdmin, ')
|
||||
..write('email: $email, ')
|
||||
..write('profileImagePath: $profileImagePath, ')
|
||||
..write('updatedAt: $updatedAt, ')
|
||||
..write('quotaSizeInBytes: $quotaSizeInBytes, ')
|
||||
..write('quotaUsageInBytes: $quotaUsageInBytes')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
21
mobile/lib/infrastructure/entities/user_metadata.entity.dart
Normal file
21
mobile/lib/infrastructure/entities/user_metadata.entity.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
|
||||
class UserMetadataEntity extends Table with DriftDefaultsMixin {
|
||||
const UserMetadataEntity();
|
||||
|
||||
BlobColumn get userId =>
|
||||
blob().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
||||
TextColumn get preferences => text().map(userPreferenceConverter)();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {userId};
|
||||
}
|
||||
|
||||
final JsonTypeConverter2<UserPreferences, String, Object?>
|
||||
userPreferenceConverter = TypeConverter.json2(
|
||||
fromJson: (json) => UserPreferences.fromMap(json as Map<String, Object?>),
|
||||
toJson: (pref) => pref.toMap(),
|
||||
);
|
||||
468
mobile/lib/infrastructure/entities/user_metadata.entity.drift.dart
generated
Normal file
468
mobile/lib/infrastructure/entities/user_metadata.entity.drift.dart
generated
Normal file
@@ -0,0 +1,468 @@
|
||||
// dart format width=80
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart'
|
||||
as i1;
|
||||
import 'dart:typed_data' as i2;
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart' as i3;
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart'
|
||||
as i4;
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
|
||||
as i5;
|
||||
import 'package:drift/internal/modular.dart' as i6;
|
||||
|
||||
typedef $$UserMetadataEntityTableCreateCompanionBuilder
|
||||
= i1.UserMetadataEntityCompanion Function({
|
||||
required i2.Uint8List userId,
|
||||
required i3.UserPreferences preferences,
|
||||
});
|
||||
typedef $$UserMetadataEntityTableUpdateCompanionBuilder
|
||||
= i1.UserMetadataEntityCompanion Function({
|
||||
i0.Value<i2.Uint8List> userId,
|
||||
i0.Value<i3.UserPreferences> preferences,
|
||||
});
|
||||
|
||||
final class $$UserMetadataEntityTableReferences extends i0.BaseReferences<
|
||||
i0.GeneratedDatabase,
|
||||
i1.$UserMetadataEntityTable,
|
||||
i1.UserMetadataEntityData> {
|
||||
$$UserMetadataEntityTableReferences(
|
||||
super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static i5.$UserEntityTable _userIdTable(i0.GeneratedDatabase db) =>
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
||||
.createAlias(i0.$_aliasNameGenerator(
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i1.$UserMetadataEntityTable>(
|
||||
'user_metadata_entity')
|
||||
.userId,
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
||||
.id));
|
||||
|
||||
i5.$$UserEntityTableProcessedTableManager get userId {
|
||||
final $_column = $_itemColumn<i2.Uint8List>('user_id')!;
|
||||
|
||||
final manager = i5
|
||||
.$$UserEntityTableTableManager(
|
||||
$_db,
|
||||
i6.ReadDatabaseContainer($_db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'))
|
||||
.filter((f) => f.id.sqlEquals($_column));
|
||||
final item = $_typedResult.readTableOrNull(_userIdTable($_db));
|
||||
if (item == null) return manager;
|
||||
return i0.ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: [item]));
|
||||
}
|
||||
}
|
||||
|
||||
class $$UserMetadataEntityTableFilterComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$UserMetadataEntityTable> {
|
||||
$$UserMetadataEntityTableFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnWithTypeConverterFilters<i3.UserPreferences, i3.UserPreferences,
|
||||
String>
|
||||
get preferences => $composableBuilder(
|
||||
column: $table.preferences,
|
||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column));
|
||||
|
||||
i5.$$UserEntityTableFilterComposer get userId {
|
||||
final i5.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.userId,
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i5.$$UserEntityTableFilterComposer(
|
||||
$db: $db,
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $$UserMetadataEntityTableOrderingComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$UserMetadataEntityTable> {
|
||||
$$UserMetadataEntityTableOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnOrderings<String> get preferences => $composableBuilder(
|
||||
column: $table.preferences,
|
||||
builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i5.$$UserEntityTableOrderingComposer get userId {
|
||||
final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.userId,
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i5.$$UserEntityTableOrderingComposer(
|
||||
$db: $db,
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $$UserMetadataEntityTableAnnotationComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$UserMetadataEntityTable> {
|
||||
$$UserMetadataEntityTableAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.GeneratedColumnWithTypeConverter<i3.UserPreferences, String>
|
||||
get preferences => $composableBuilder(
|
||||
column: $table.preferences, builder: (column) => column);
|
||||
|
||||
i5.$$UserEntityTableAnnotationComposer get userId {
|
||||
final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.userId,
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i5.$$UserEntityTableAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $$UserMetadataEntityTableTableManager extends i0.RootTableManager<
|
||||
i0.GeneratedDatabase,
|
||||
i1.$UserMetadataEntityTable,
|
||||
i1.UserMetadataEntityData,
|
||||
i1.$$UserMetadataEntityTableFilterComposer,
|
||||
i1.$$UserMetadataEntityTableOrderingComposer,
|
||||
i1.$$UserMetadataEntityTableAnnotationComposer,
|
||||
$$UserMetadataEntityTableCreateCompanionBuilder,
|
||||
$$UserMetadataEntityTableUpdateCompanionBuilder,
|
||||
(i1.UserMetadataEntityData, i1.$$UserMetadataEntityTableReferences),
|
||||
i1.UserMetadataEntityData,
|
||||
i0.PrefetchHooks Function({bool userId})> {
|
||||
$$UserMetadataEntityTableTableManager(
|
||||
i0.GeneratedDatabase db, i1.$UserMetadataEntityTable table)
|
||||
: super(i0.TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () => i1
|
||||
.$$UserMetadataEntityTableFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
i1.$$UserMetadataEntityTableOrderingComposer(
|
||||
$db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
i1.$$UserMetadataEntityTableAnnotationComposer(
|
||||
$db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
i0.Value<i2.Uint8List> userId = const i0.Value.absent(),
|
||||
i0.Value<i3.UserPreferences> preferences = const i0.Value.absent(),
|
||||
}) =>
|
||||
i1.UserMetadataEntityCompanion(
|
||||
userId: userId,
|
||||
preferences: preferences,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required i2.Uint8List userId,
|
||||
required i3.UserPreferences preferences,
|
||||
}) =>
|
||||
i1.UserMetadataEntityCompanion.insert(
|
||||
userId: userId,
|
||||
preferences: preferences,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (
|
||||
e.readTable(table),
|
||||
i1.$$UserMetadataEntityTableReferences(db, table, e)
|
||||
))
|
||||
.toList(),
|
||||
prefetchHooksCallback: ({userId = false}) {
|
||||
return i0.PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [],
|
||||
addJoins: <
|
||||
T extends i0.TableManagerState<
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic>>(state) {
|
||||
if (userId) {
|
||||
state = state.withJoin(
|
||||
currentTable: table,
|
||||
currentColumn: table.userId,
|
||||
referencedTable:
|
||||
i1.$$UserMetadataEntityTableReferences._userIdTable(db),
|
||||
referencedColumn: i1.$$UserMetadataEntityTableReferences
|
||||
._userIdTable(db)
|
||||
.id,
|
||||
) as T;
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
return [];
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
typedef $$UserMetadataEntityTableProcessedTableManager
|
||||
= i0.ProcessedTableManager<
|
||||
i0.GeneratedDatabase,
|
||||
i1.$UserMetadataEntityTable,
|
||||
i1.UserMetadataEntityData,
|
||||
i1.$$UserMetadataEntityTableFilterComposer,
|
||||
i1.$$UserMetadataEntityTableOrderingComposer,
|
||||
i1.$$UserMetadataEntityTableAnnotationComposer,
|
||||
$$UserMetadataEntityTableCreateCompanionBuilder,
|
||||
$$UserMetadataEntityTableUpdateCompanionBuilder,
|
||||
(i1.UserMetadataEntityData, i1.$$UserMetadataEntityTableReferences),
|
||||
i1.UserMetadataEntityData,
|
||||
i0.PrefetchHooks Function({bool userId})>;
|
||||
|
||||
class $UserMetadataEntityTable extends i4.UserMetadataEntity
|
||||
with i0.TableInfo<$UserMetadataEntityTable, i1.UserMetadataEntityData> {
|
||||
@override
|
||||
final i0.GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$UserMetadataEntityTable(this.attachedDatabase, [this._alias]);
|
||||
static const i0.VerificationMeta _userIdMeta =
|
||||
const i0.VerificationMeta('userId');
|
||||
@override
|
||||
late final i0.GeneratedColumn<i2.Uint8List> userId =
|
||||
i0.GeneratedColumn<i2.Uint8List>('user_id', aliasedName, false,
|
||||
type: i0.DriftSqlType.blob,
|
||||
requiredDuringInsert: true,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES user_entity (id) ON DELETE CASCADE'));
|
||||
@override
|
||||
late final i0.GeneratedColumnWithTypeConverter<i3.UserPreferences, String>
|
||||
preferences = i0.GeneratedColumn<String>(
|
||||
'preferences', aliasedName, false,
|
||||
type: i0.DriftSqlType.string, requiredDuringInsert: true)
|
||||
.withConverter<i3.UserPreferences>(
|
||||
i1.$UserMetadataEntityTable.$converterpreferences);
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns => [userId, preferences];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'user_metadata_entity';
|
||||
@override
|
||||
i0.VerificationContext validateIntegrity(
|
||||
i0.Insertable<i1.UserMetadataEntityData> instance,
|
||||
{bool isInserting = false}) {
|
||||
final context = i0.VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('user_id')) {
|
||||
context.handle(_userIdMeta,
|
||||
userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_userIdMeta);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<i0.GeneratedColumn> get $primaryKey => {userId};
|
||||
@override
|
||||
i1.UserMetadataEntityData map(Map<String, dynamic> data,
|
||||
{String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return i1.UserMetadataEntityData(
|
||||
userId: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.blob, data['${effectivePrefix}user_id'])!,
|
||||
preferences: i1.$UserMetadataEntityTable.$converterpreferences.fromSql(
|
||||
attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string, data['${effectivePrefix}preferences'])!),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
$UserMetadataEntityTable createAlias(String alias) {
|
||||
return $UserMetadataEntityTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static i0.JsonTypeConverter2<i3.UserPreferences, String, Object?>
|
||||
$converterpreferences = i4.userPreferenceConverter;
|
||||
@override
|
||||
bool get withoutRowId => true;
|
||||
@override
|
||||
bool get isStrict => true;
|
||||
}
|
||||
|
||||
class UserMetadataEntityData extends i0.DataClass
|
||||
implements i0.Insertable<i1.UserMetadataEntityData> {
|
||||
final i2.Uint8List userId;
|
||||
final i3.UserPreferences preferences;
|
||||
const UserMetadataEntityData(
|
||||
{required this.userId, required this.preferences});
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
map['user_id'] = i0.Variable<i2.Uint8List>(userId);
|
||||
{
|
||||
map['preferences'] = i0.Variable<String>(
|
||||
i1.$UserMetadataEntityTable.$converterpreferences.toSql(preferences));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
factory UserMetadataEntityData.fromJson(Map<String, dynamic> json,
|
||||
{i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return UserMetadataEntityData(
|
||||
userId: serializer.fromJson<i2.Uint8List>(json['userId']),
|
||||
preferences: i1.$UserMetadataEntityTable.$converterpreferences
|
||||
.fromJson(serializer.fromJson<Object?>(json['preferences'])),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'userId': serializer.toJson<i2.Uint8List>(userId),
|
||||
'preferences': serializer.toJson<Object?>(i1
|
||||
.$UserMetadataEntityTable.$converterpreferences
|
||||
.toJson(preferences)),
|
||||
};
|
||||
}
|
||||
|
||||
i1.UserMetadataEntityData copyWith(
|
||||
{i2.Uint8List? userId, i3.UserPreferences? preferences}) =>
|
||||
i1.UserMetadataEntityData(
|
||||
userId: userId ?? this.userId,
|
||||
preferences: preferences ?? this.preferences,
|
||||
);
|
||||
UserMetadataEntityData copyWithCompanion(
|
||||
i1.UserMetadataEntityCompanion data) {
|
||||
return UserMetadataEntityData(
|
||||
userId: data.userId.present ? data.userId.value : this.userId,
|
||||
preferences:
|
||||
data.preferences.present ? data.preferences.value : this.preferences,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('UserMetadataEntityData(')
|
||||
..write('userId: $userId, ')
|
||||
..write('preferences: $preferences')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(i0.$driftBlobEquality.hash(userId), preferences);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is i1.UserMetadataEntityData &&
|
||||
i0.$driftBlobEquality.equals(other.userId, this.userId) &&
|
||||
other.preferences == this.preferences);
|
||||
}
|
||||
|
||||
class UserMetadataEntityCompanion
|
||||
extends i0.UpdateCompanion<i1.UserMetadataEntityData> {
|
||||
final i0.Value<i2.Uint8List> userId;
|
||||
final i0.Value<i3.UserPreferences> preferences;
|
||||
const UserMetadataEntityCompanion({
|
||||
this.userId = const i0.Value.absent(),
|
||||
this.preferences = const i0.Value.absent(),
|
||||
});
|
||||
UserMetadataEntityCompanion.insert({
|
||||
required i2.Uint8List userId,
|
||||
required i3.UserPreferences preferences,
|
||||
}) : userId = i0.Value(userId),
|
||||
preferences = i0.Value(preferences);
|
||||
static i0.Insertable<i1.UserMetadataEntityData> custom({
|
||||
i0.Expression<i2.Uint8List>? userId,
|
||||
i0.Expression<String>? preferences,
|
||||
}) {
|
||||
return i0.RawValuesInsertable({
|
||||
if (userId != null) 'user_id': userId,
|
||||
if (preferences != null) 'preferences': preferences,
|
||||
});
|
||||
}
|
||||
|
||||
i1.UserMetadataEntityCompanion copyWith(
|
||||
{i0.Value<i2.Uint8List>? userId,
|
||||
i0.Value<i3.UserPreferences>? preferences}) {
|
||||
return i1.UserMetadataEntityCompanion(
|
||||
userId: userId ?? this.userId,
|
||||
preferences: preferences ?? this.preferences,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
if (userId.present) {
|
||||
map['user_id'] = i0.Variable<i2.Uint8List>(userId.value);
|
||||
}
|
||||
if (preferences.present) {
|
||||
map['preferences'] = i0.Variable<String>(i1
|
||||
.$UserMetadataEntityTable.$converterpreferences
|
||||
.toSql(preferences.value));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('UserMetadataEntityCompanion(')
|
||||
..write('userId: $userId, ')
|
||||
..write('preferences: $preferences')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,15 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_flutter/drift_flutter.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
import 'db.repository.drift.dart';
|
||||
|
||||
// #zoneTxn is the symbol used by Isar to mark a transaction within the current zone
|
||||
// ref: isar/isar_common.dart
|
||||
const Symbol _kzoneTxn = #zoneTxn;
|
||||
@@ -17,3 +24,35 @@ class IsarDatabaseRepository implements IDatabaseRepository {
|
||||
Future<T> transaction<T>(Future<T> Function() callback) =>
|
||||
Zone.current[_kzoneTxn] == null ? _db.writeTxn(callback) : callback();
|
||||
}
|
||||
|
||||
@DriftDatabase(tables: [UserEntity, UserMetadataEntity, PartnerEntity])
|
||||
class Drift extends $Drift implements IDatabaseRepository {
|
||||
Drift([QueryExecutor? executor])
|
||||
: super(
|
||||
executor ??
|
||||
driftDatabase(
|
||||
name: 'immich',
|
||||
native: const DriftNativeOptions(shareAcrossIsolates: true),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
beforeOpen: (details) async {
|
||||
await customStatement('PRAGMA journal_mode = WAL');
|
||||
await customStatement('PRAGMA foreign_keys = ON');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class DriftDatabaseRepository implements IDatabaseRepository {
|
||||
final Drift _db;
|
||||
const DriftDatabaseRepository(this._db);
|
||||
|
||||
@override
|
||||
Future<T> transaction<T>(Future<T> Function() callback) =>
|
||||
_db.transaction(callback);
|
||||
}
|
||||
|
||||
67
mobile/lib/infrastructure/repositories/db.repository.drift.dart
generated
Normal file
67
mobile/lib/infrastructure/repositories/db.repository.drift.dart
generated
Normal file
@@ -0,0 +1,67 @@
|
||||
// dart format width=80
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
|
||||
as i1;
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart'
|
||||
as i2;
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
|
||||
as i3;
|
||||
|
||||
abstract class $Drift extends i0.GeneratedDatabase {
|
||||
$Drift(i0.QueryExecutor e) : super(e);
|
||||
$DriftManager get managers => $DriftManager(this);
|
||||
late final i1.$UserEntityTable userEntity = i1.$UserEntityTable(this);
|
||||
late final i2.$UserMetadataEntityTable userMetadataEntity =
|
||||
i2.$UserMetadataEntityTable(this);
|
||||
late final i3.$PartnerEntityTable partnerEntity =
|
||||
i3.$PartnerEntityTable(this);
|
||||
@override
|
||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||
@override
|
||||
List<i0.DatabaseSchemaEntity> get allSchemaEntities =>
|
||||
[userEntity, userMetadataEntity, partnerEntity];
|
||||
@override
|
||||
i0.StreamQueryUpdateRules get streamUpdateRules =>
|
||||
const i0.StreamQueryUpdateRules(
|
||||
[
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName('user_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete),
|
||||
result: [
|
||||
i0.TableUpdate('user_metadata_entity',
|
||||
kind: i0.UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName('user_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete),
|
||||
result: [
|
||||
i0.TableUpdate('partner_entity', kind: i0.UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName('user_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete),
|
||||
result: [
|
||||
i0.TableUpdate('partner_entity', kind: i0.UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
@override
|
||||
i0.DriftDatabaseOptions get options =>
|
||||
const i0.DriftDatabaseOptions(storeDateTimeAsText: true);
|
||||
}
|
||||
|
||||
class $DriftManager {
|
||||
final $Drift _db;
|
||||
$DriftManager(this._db);
|
||||
i1.$$UserEntityTableTableManager get userEntity =>
|
||||
i1.$$UserEntityTableTableManager(_db, _db.userEntity);
|
||||
i2.$$UserMetadataEntityTableTableManager get userMetadataEntity =>
|
||||
i2.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity);
|
||||
i3.$$PartnerEntityTableTableManager get partnerEntity =>
|
||||
i3.$$PartnerEntityTableTableManager(_db, _db.partnerEntity);
|
||||
}
|
||||
9
mobile/lib/infrastructure/utils/drift_default.mixin.dart
Normal file
9
mobile/lib/infrastructure/utils/drift_default.mixin.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
mixin DriftDefaultsMixin on Table {
|
||||
@override
|
||||
bool get isStrict => true;
|
||||
|
||||
@override
|
||||
bool get withoutRowId => true;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
// TODO: Move to repository once all classes are refactored
|
||||
abstract final class ExifDtoConverter {
|
||||
static ExifInfo fromDto(ExifResponseDto dto) {
|
||||
return ExifInfo(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
// TODO: Move to repository once all classes are refactored
|
||||
abstract final class UserConverter {
|
||||
/// Base user dto used where the complete user object is not required
|
||||
static UserDto fromSimpleUserDto(UserResponseDto dto) => UserDto(
|
||||
|
||||
@@ -71,8 +71,13 @@ class AssetRepository extends DatabaseRepository implements IAssetRepository {
|
||||
Future<List<Asset>> getAllByRemoteId(
|
||||
Iterable<String> ids, {
|
||||
AssetState? state,
|
||||
}) =>
|
||||
_getAllByRemoteIdImpl(ids, state).findAll();
|
||||
}) async {
|
||||
if (ids.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return _getAllByRemoteIdImpl(ids, state).findAll();
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> _getAllByRemoteIdImpl(
|
||||
Iterable<String> ids,
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
|
||||
@@ -14,3 +14,6 @@ create_splash:
|
||||
|
||||
build_release_android:
|
||||
flutter build appbundle
|
||||
|
||||
migrations:
|
||||
dart run drift_dev make-migrations
|
||||
|
||||
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@@ -3,7 +3,7 @@ Immich API
|
||||
|
||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 1.131.2
|
||||
- API version: 1.131.3
|
||||
- Generator version: 7.8.0
|
||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||
|
||||
|
||||
@@ -206,6 +206,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -382,6 +390,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.2"
|
||||
drift:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: drift
|
||||
sha256: "14a61af39d4584faf1d73b5b35e4b758a43008cf4c0fdb0576ec8e7032c0d9a5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.26.0"
|
||||
drift_dev:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: drift_dev
|
||||
sha256: "0d3f8b33b76cf1c6a82ee34d9511c40957549c4674b8f1688609e6d6c7306588"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.26.0"
|
||||
drift_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: drift_flutter
|
||||
sha256: "0cadbf3b8733409a6cf61d18ba2e94e149df81df7de26f48ae0695b48fd71922"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.4"
|
||||
dynamic_color:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1288,6 +1320,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
recase:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: recase
|
||||
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
riverpod:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1549,6 +1589,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
sqlite3:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3
|
||||
sha256: "310af39c40dd0bb2058538333c9d9840a2725ae0b9f77e4fd09ad6696aa8f66e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.5"
|
||||
sqlite3_flutter_libs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3_flutter_libs
|
||||
sha256: "7adb4cc96dc08648a5eb1d80a7619070796ca6db03901ff2b6dcb15ee30468f3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.31"
|
||||
sqlparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlparser
|
||||
sha256: "27dd0a9f0c02e22ac0eb42a23df9ea079ce69b52bb4a3b478d64e0ef34a263ee"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.41.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -2,7 +2,7 @@ name: immich_mobile
|
||||
description: Immich - selfhosted backup media file on mobile phone
|
||||
|
||||
publish_to: 'none'
|
||||
version: 1.131.2+192
|
||||
version: 1.131.3+193
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.0 <4.0.0'
|
||||
@@ -73,6 +73,9 @@ dependencies:
|
||||
isar_flutter_libs: # contains Isar Core
|
||||
version: *isar_version
|
||||
hosted: https://pub.isar-community.dev/
|
||||
# DB
|
||||
drift: ^2.23.1
|
||||
drift_flutter: ^0.2.4
|
||||
|
||||
dependency_overrides:
|
||||
analyzer: ^6.0.0
|
||||
@@ -99,6 +102,8 @@ dev_dependencies:
|
||||
immich_mobile_immich_lint:
|
||||
path: './immich_lint'
|
||||
fake_async: ^1.3.1
|
||||
# Drift generator
|
||||
drift_dev: ^2.23.1
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||
1
mobile/test/fixtures/user.stub.dart
vendored
1
mobile/test/fixtures/user.stub.dart
vendored
@@ -1,4 +1,5 @@
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
|
||||
abstract final class UserStub {
|
||||
const UserStub._();
|
||||
|
||||
@@ -7656,7 +7656,7 @@
|
||||
"info": {
|
||||
"title": "Immich",
|
||||
"description": "Immich API",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"contact": {}
|
||||
},
|
||||
"tags": [],
|
||||
|
||||
4
open-api/typescript-sdk/package-lock.json
generated
4
open-api/typescript-sdk/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||
"type": "module",
|
||||
"main": "./build/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Immich
|
||||
* 1.131.2
|
||||
* 1.131.3
|
||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||
* See https://www.npmjs.com/package/oazapfts
|
||||
*/
|
||||
|
||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { MoveRepository } from 'src/repositories/move.repository';
|
||||
import { PersonRepository } from 'src/repositories/person.repository';
|
||||
import { StorageRepository } from 'src/repositories/storage.repository';
|
||||
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { getAssetFile } from 'src/utils/asset.util';
|
||||
import { getConfig } from 'src/utils/config';
|
||||
|
||||
export interface MoveRequest {
|
||||
@@ -117,8 +117,7 @@ export class StorageCore {
|
||||
|
||||
async moveAssetImage(asset: AssetEntity, pathType: GeneratedImageType, format: ImageFormat) {
|
||||
const { id: entityId, files } = asset;
|
||||
const { thumbnailFile, previewFile } = getAssetFiles(files);
|
||||
const oldFile = pathType === AssetPathType.PREVIEW ? previewFile : thumbnailFile;
|
||||
const oldFile = getAssetFile(files, pathType);
|
||||
return this.moveFile({
|
||||
entityId,
|
||||
pathType,
|
||||
|
||||
@@ -136,6 +136,7 @@ with
|
||||
"asset_faces"
|
||||
inner join "assets" on "assets"."id" = "asset_faces"."assetId"
|
||||
inner join "face_search" on "face_search"."faceId" = "asset_faces"."id"
|
||||
left join "person" on "person"."id" = "asset_faces"."personId"
|
||||
where
|
||||
"assets"."ownerId" = any ($2::uuid[])
|
||||
and "assets"."deletedAt" is null
|
||||
|
||||
@@ -32,47 +32,7 @@ where
|
||||
"asset_stack"."ownerId" = $1
|
||||
|
||||
-- StackRepository.delete
|
||||
select
|
||||
*,
|
||||
(
|
||||
select
|
||||
coalesce(json_agg(agg), '[]')
|
||||
from
|
||||
(
|
||||
select
|
||||
"assets".*,
|
||||
(
|
||||
select
|
||||
coalesce(json_agg(agg), '[]')
|
||||
from
|
||||
(
|
||||
select
|
||||
"tags".*
|
||||
from
|
||||
"tags"
|
||||
inner join "tag_asset" on "tags"."id" = "tag_asset"."tagsId"
|
||||
where
|
||||
"tag_asset"."assetsId" = "assets"."id"
|
||||
) as agg
|
||||
) as "tags",
|
||||
to_json("exifInfo") as "exifInfo"
|
||||
from
|
||||
"assets"
|
||||
inner join lateral (
|
||||
select
|
||||
"exif".*
|
||||
from
|
||||
"exif"
|
||||
where
|
||||
"exif"."assetId" = "assets"."id"
|
||||
) as "exifInfo" on true
|
||||
where
|
||||
"assets"."deletedAt" is null
|
||||
and "assets"."stackId" = "asset_stack"."id"
|
||||
) as agg
|
||||
) as "assets"
|
||||
from
|
||||
"asset_stack"
|
||||
delete from "asset_stack"
|
||||
where
|
||||
"id" = $1::uuid
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@ export interface FaceEmbeddingSearch extends SearchEmbeddingOptions {
|
||||
hasPerson?: boolean;
|
||||
numResults: number;
|
||||
maxDistance: number;
|
||||
minBirthDate?: Date;
|
||||
}
|
||||
|
||||
export interface AssetDuplicateSearch {
|
||||
@@ -338,7 +339,7 @@ export class SearchRepository {
|
||||
},
|
||||
],
|
||||
})
|
||||
searchFaces({ userIds, embedding, numResults, maxDistance, hasPerson }: FaceEmbeddingSearch) {
|
||||
searchFaces({ userIds, embedding, numResults, maxDistance, hasPerson, minBirthDate }: FaceEmbeddingSearch) {
|
||||
if (!isValidInteger(numResults, { min: 1, max: 1000 })) {
|
||||
throw new Error(`Invalid value for 'numResults': ${numResults}`);
|
||||
}
|
||||
@@ -354,9 +355,13 @@ export class SearchRepository {
|
||||
])
|
||||
.innerJoin('assets', 'assets.id', 'asset_faces.assetId')
|
||||
.innerJoin('face_search', 'face_search.faceId', 'asset_faces.id')
|
||||
.leftJoin('person', 'person.id', 'asset_faces.personId')
|
||||
.where('assets.ownerId', '=', anyUuid(userIds))
|
||||
.where('assets.deletedAt', 'is', null)
|
||||
.$if(!!hasPerson, (qb) => qb.where('asset_faces.personId', 'is not', null))
|
||||
.$if(!!minBirthDate, (qb) =>
|
||||
qb.where((eb) => eb.or([eb('person.birthDate', 'is', null), eb('person.birthDate', '<=', minBirthDate!)])),
|
||||
)
|
||||
.orderBy(sql`face_search.embedding <=> ${embedding}`)
|
||||
.limit(numResults),
|
||||
)
|
||||
|
||||
@@ -122,38 +122,11 @@ export class StackRepository {
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async delete(id: string): Promise<void> {
|
||||
const stack = await this.getById(id);
|
||||
if (!stack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const assetIds = stack.assets.map(({ id }) => id);
|
||||
|
||||
await this.db.deleteFrom('asset_stack').where('id', '=', asUuid(id)).execute();
|
||||
await this.db
|
||||
.updateTable('assets')
|
||||
.set({ stackId: null, updatedAt: new Date() })
|
||||
.where('id', 'in', assetIds)
|
||||
.execute();
|
||||
}
|
||||
|
||||
async deleteAll(ids: string[]): Promise<void> {
|
||||
const assetIds = [];
|
||||
for (const id of ids) {
|
||||
const stack = await this.getById(id);
|
||||
if (!stack) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assetIds.push(...stack.assets.map(({ id }) => id));
|
||||
}
|
||||
|
||||
await this.db
|
||||
.updateTable('assets')
|
||||
.set({ updatedAt: new Date(), stackId: null })
|
||||
.where('id', 'in', assetIds)
|
||||
.where('stackId', 'in', ids)
|
||||
.execute();
|
||||
await this.db.deleteFrom('asset_stack').where('id', 'in', ids).execute();
|
||||
}
|
||||
|
||||
update(id: string, entity: Updateable<StackEntity>): Promise<StackEntity> {
|
||||
|
||||
@@ -584,7 +584,7 @@ describe(AssetMediaService.name, () => {
|
||||
sut.viewThumbnail(authStub.admin, assetStub.image.id, { size: AssetMediaSize.PREVIEW }),
|
||||
).resolves.toEqual(
|
||||
new ImmichFileResponse({
|
||||
path: assetStub.image.files[0].path,
|
||||
path: '/uploads/user-id/thumbs/path.jpg',
|
||||
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
|
||||
contentType: 'image/jpeg',
|
||||
fileName: 'asset-id_preview.jpg',
|
||||
@@ -599,7 +599,7 @@ describe(AssetMediaService.name, () => {
|
||||
sut.viewThumbnail(authStub.admin, assetStub.image.id, { size: AssetMediaSize.THUMBNAIL }),
|
||||
).resolves.toEqual(
|
||||
new ImmichFileResponse({
|
||||
path: assetStub.image.files[1].path,
|
||||
path: '/uploads/user-id/webp/path.ext',
|
||||
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
|
||||
contentType: 'application/octet-stream',
|
||||
fileName: 'asset-id_thumbnail.ext',
|
||||
|
||||
@@ -578,6 +578,7 @@ describe(AssetService.name, () => {
|
||||
files: [
|
||||
'/uploads/user-id/webp/path.ext',
|
||||
'/uploads/user-id/thumbs/path.jpg',
|
||||
'/uploads/user-id/fullsize/path.webp',
|
||||
assetWithFace.encodedVideoPath,
|
||||
assetWithFace.sidecarPath,
|
||||
assetWithFace.originalPath,
|
||||
@@ -637,7 +638,14 @@ describe(AssetService.name, () => {
|
||||
{
|
||||
name: JobName.DELETE_FILES,
|
||||
data: {
|
||||
files: [undefined, undefined, undefined, undefined, 'fake_path/asset_1.jpeg'],
|
||||
files: [
|
||||
'/uploads/user-id/webp/path.ext',
|
||||
'/uploads/user-id/thumbs/path.jpg',
|
||||
'/uploads/user-id/fullsize/path.webp',
|
||||
undefined,
|
||||
undefined,
|
||||
'fake_path/asset_1.jpeg',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -658,7 +666,14 @@ describe(AssetService.name, () => {
|
||||
{
|
||||
name: JobName.DELETE_FILES,
|
||||
data: {
|
||||
files: [undefined, undefined, undefined, undefined, 'fake_path/asset_1.jpeg'],
|
||||
files: [
|
||||
'/uploads/user-id/webp/path.ext',
|
||||
'/uploads/user-id/thumbs/path.jpg',
|
||||
'/uploads/user-id/fullsize/path.webp',
|
||||
undefined,
|
||||
undefined,
|
||||
'fake_path/asset_1.jpeg',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -233,8 +233,8 @@ export class AssetService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
const { thumbnailFile, previewFile } = getAssetFiles(asset.files);
|
||||
const files = [thumbnailFile?.path, previewFile?.path, asset.encodedVideoPath];
|
||||
const { fullsizeFile, previewFile, thumbnailFile } = getAssetFiles(asset.files);
|
||||
const files = [thumbnailFile?.path, previewFile?.path, fullsizeFile?.path, asset.encodedVideoPath];
|
||||
|
||||
if (deleteOnDisk) {
|
||||
files.push(asset.sidecarPath, asset.originalPath);
|
||||
|
||||
@@ -136,8 +136,14 @@ export class AuditService extends BaseService {
|
||||
for await (const assets of pagination) {
|
||||
assetCount += assets.length;
|
||||
for (const { id, files, originalPath, encodedVideoPath, isExternal, checksum } of assets) {
|
||||
const { previewFile, thumbnailFile } = getAssetFiles(files);
|
||||
for (const file of [originalPath, previewFile?.path, encodedVideoPath, thumbnailFile?.path]) {
|
||||
const { fullsizeFile, previewFile, thumbnailFile } = getAssetFiles(files);
|
||||
for (const file of [
|
||||
originalPath,
|
||||
fullsizeFile?.path,
|
||||
previewFile?.path,
|
||||
encodedVideoPath,
|
||||
thumbnailFile?.path,
|
||||
]) {
|
||||
track(file);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@ import { mapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { DuplicateResponseDto } from 'src/dtos/duplicate.dto';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { JobName, JobStatus, QueueName } from 'src/enum';
|
||||
import { AssetFileType, JobName, JobStatus, QueueName } from 'src/enum';
|
||||
import { WithoutProperty } from 'src/repositories/asset.repository';
|
||||
import { AssetDuplicateResult } from 'src/repositories/search.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { JobOf } from 'src/types';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { getAssetFile } from 'src/utils/asset.util';
|
||||
import { isDuplicateDetectionEnabled } from 'src/utils/misc';
|
||||
import { usePagination } from 'src/utils/pagination';
|
||||
|
||||
@@ -69,7 +69,7 @@ export class DuplicateService extends BaseService {
|
||||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
const { previewFile } = getAssetFiles(asset.files);
|
||||
const previewFile = getAssetFile(asset.files, AssetFileType.PREVIEW);
|
||||
if (!previewFile) {
|
||||
this.logger.warn(`Asset ${id} is missing preview image`);
|
||||
return JobStatus.FAILED;
|
||||
|
||||
@@ -234,6 +234,24 @@ describe(MediaService.name, () => {
|
||||
});
|
||||
|
||||
await expect(sut.handleAssetMigration({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS);
|
||||
expect(mocks.move.create).toHaveBeenCalledWith({
|
||||
entityId: assetStub.image.id,
|
||||
pathType: AssetPathType.FULLSIZE,
|
||||
oldPath: '/uploads/user-id/fullsize/path.webp',
|
||||
newPath: 'upload/thumbs/user-id/as/se/asset-id-fullsize.jpeg',
|
||||
});
|
||||
expect(mocks.move.create).toHaveBeenCalledWith({
|
||||
entityId: assetStub.image.id,
|
||||
pathType: AssetPathType.PREVIEW,
|
||||
oldPath: '/uploads/user-id/thumbs/path.jpg',
|
||||
newPath: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
|
||||
});
|
||||
expect(mocks.move.create).toHaveBeenCalledWith({
|
||||
entityId: assetStub.image.id,
|
||||
pathType: AssetPathType.THUMBNAIL,
|
||||
oldPath: '/uploads/user-id/webp/path.ext',
|
||||
newPath: 'upload/thumbs/user-id/as/se/asset-id-thumbnail.webp',
|
||||
});
|
||||
expect(mocks.move.create).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -140,7 +140,7 @@ export class MediaService extends BaseService {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
await this.storageCore.moveAssetImage(asset, AssetPathType.FULLSIZE, ImageFormat.JPEG);
|
||||
await this.storageCore.moveAssetImage(asset, AssetPathType.FULLSIZE, image.fullsize.format);
|
||||
await this.storageCore.moveAssetImage(asset, AssetPathType.PREVIEW, image.preview.format);
|
||||
await this.storageCore.moveAssetImage(asset, AssetPathType.THUMBNAIL, image.thumbnail.format);
|
||||
await this.storageCore.moveAssetVideo(asset);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BinaryField, ExifDateTime } from 'exiftool-vendored';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { Stats } from 'node:fs';
|
||||
import { constants } from 'node:fs/promises';
|
||||
import { defaults } from 'src/config';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
@@ -21,14 +22,8 @@ describe(MetadataService.name, () => {
|
||||
let mocks: ServiceMocks;
|
||||
|
||||
const mockReadTags = (exifData?: Partial<ImmichTags>, sidecarData?: Partial<ImmichTags>) => {
|
||||
exifData = {
|
||||
FileSize: '123456',
|
||||
FileCreateDate: '2024-01-01T00:00:00.000Z',
|
||||
FileModifyDate: '2024-01-01T00:00:00.000Z',
|
||||
...exifData,
|
||||
};
|
||||
mocks.metadata.readTags.mockReset();
|
||||
mocks.metadata.readTags.mockResolvedValueOnce(exifData);
|
||||
mocks.metadata.readTags.mockResolvedValueOnce(exifData ?? {});
|
||||
mocks.metadata.readTags.mockResolvedValueOnce(sidecarData ?? {});
|
||||
};
|
||||
|
||||
@@ -114,6 +109,17 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
describe('handleMetadataExtraction', () => {
|
||||
beforeEach(() => {
|
||||
const time = new Date('2022-01-01T00:00:00.000Z');
|
||||
const timeMs = time.valueOf();
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: time,
|
||||
mtimeMs: timeMs,
|
||||
birthtimeMs: timeMs,
|
||||
} as Stats);
|
||||
});
|
||||
|
||||
it('should handle an asset that could not be found', async () => {
|
||||
await expect(sut.handleMetadataExtraction({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED);
|
||||
|
||||
@@ -145,10 +151,13 @@ describe(MetadataService.name, () => {
|
||||
const fileCreatedAt = new Date('2022-01-01T00:00:00.000Z');
|
||||
const fileModifiedAt = new Date('2021-01-01T00:00:00.000Z');
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({
|
||||
FileCreateDate: fileCreatedAt.toISOString(),
|
||||
FileModifyDate: fileModifiedAt.toISOString(),
|
||||
});
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: fileModifiedAt,
|
||||
mtimeMs: fileModifiedAt.valueOf(),
|
||||
birthtimeMs: fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mockReadTags();
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
@@ -168,10 +177,13 @@ describe(MetadataService.name, () => {
|
||||
const fileCreatedAt = new Date('2021-01-01T00:00:00.000Z');
|
||||
const fileModifiedAt = new Date('2022-01-01T00:00:00.000Z');
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({
|
||||
FileCreateDate: fileCreatedAt.toISOString(),
|
||||
FileModifyDate: fileModifiedAt.toISOString(),
|
||||
});
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: fileModifiedAt,
|
||||
mtimeMs: fileModifiedAt.valueOf(),
|
||||
birthtimeMs: fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mockReadTags();
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
@@ -206,10 +218,14 @@ describe(MetadataService.name, () => {
|
||||
|
||||
it('should handle lists of numbers', async () => {
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: assetStub.image.fileModifiedAt,
|
||||
mtimeMs: assetStub.image.fileModifiedAt.valueOf(),
|
||||
birthtimeMs: assetStub.image.fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mockReadTags({
|
||||
ISO: [160],
|
||||
FileCreateDate: assetStub.image.fileCreatedAt.toISOString(),
|
||||
FileModifyDate: assetStub.image.fileModifiedAt.toISOString(),
|
||||
});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -228,11 +244,15 @@ describe(MetadataService.name, () => {
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.withLocation]);
|
||||
mocks.systemMetadata.get.mockResolvedValue({ reverseGeocoding: { enabled: true } });
|
||||
mocks.map.reverseGeocode.mockResolvedValue({ city: 'City', state: 'State', country: 'Country' });
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: assetStub.withLocation.fileModifiedAt,
|
||||
mtimeMs: assetStub.withLocation.fileModifiedAt.valueOf(),
|
||||
birthtimeMs: assetStub.withLocation.fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mockReadTags({
|
||||
GPSLatitude: assetStub.withLocation.exifInfo!.latitude!,
|
||||
GPSLongitude: assetStub.withLocation.exifInfo!.longitude!,
|
||||
FileCreateDate: assetStub.withLocation.fileCreatedAt.toISOString(),
|
||||
FileModifyDate: assetStub.withLocation.fileModifiedAt.toISOString(),
|
||||
});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -475,6 +495,12 @@ describe(MetadataService.name, () => {
|
||||
|
||||
it('should extract the MotionPhotoVideo tag from Samsung HEIC motion photos', async () => {
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null }]);
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: assetStub.livePhotoWithOriginalFileName.fileModifiedAt,
|
||||
mtimeMs: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.valueOf(),
|
||||
birthtimeMs: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mockReadTags({
|
||||
Directory: 'foo/bar/',
|
||||
MotionPhoto: 1,
|
||||
@@ -483,8 +509,6 @@ describe(MetadataService.name, () => {
|
||||
// instead of the EmbeddedVideoFile, since HEIC MotionPhotos include both
|
||||
EmbeddedVideoFile: new BinaryField(0, ''),
|
||||
EmbeddedVideoType: 'MotionPhoto_Data',
|
||||
FileCreateDate: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.toISOString(),
|
||||
FileModifyDate: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.toISOString(),
|
||||
});
|
||||
mocks.crypto.hashSha1.mockReturnValue(randomBytes(512));
|
||||
mocks.asset.create.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||
@@ -525,14 +549,18 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract the EmbeddedVideo tag from Samsung JPEG motion photos', async () => {
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: assetStub.livePhotoWithOriginalFileName.fileModifiedAt,
|
||||
mtimeMs: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.valueOf(),
|
||||
birthtimeMs: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null }]);
|
||||
mockReadTags({
|
||||
Directory: 'foo/bar/',
|
||||
EmbeddedVideoFile: new BinaryField(0, ''),
|
||||
EmbeddedVideoType: 'MotionPhoto_Data',
|
||||
MotionPhoto: 1,
|
||||
FileCreateDate: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.toISOString(),
|
||||
FileModifyDate: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.toISOString(),
|
||||
});
|
||||
mocks.crypto.hashSha1.mockReturnValue(randomBytes(512));
|
||||
mocks.asset.create.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||
@@ -574,13 +602,17 @@ describe(MetadataService.name, () => {
|
||||
|
||||
it('should extract the motion photo video from the XMP directory entry ', async () => {
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null }]);
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: assetStub.livePhotoWithOriginalFileName.fileModifiedAt,
|
||||
mtimeMs: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.valueOf(),
|
||||
birthtimeMs: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mockReadTags({
|
||||
Directory: 'foo/bar/',
|
||||
MotionPhoto: 1,
|
||||
MicroVideo: 1,
|
||||
MicroVideoOffset: 1,
|
||||
FileCreateDate: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.toISOString(),
|
||||
FileModifyDate: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.toISOString(),
|
||||
});
|
||||
mocks.crypto.hashSha1.mockReturnValue(randomBytes(512));
|
||||
mocks.asset.create.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ContainerDirectoryItem, ExifDateTime, Maybe, Tags } from 'exiftool-vendored';
|
||||
import { ContainerDirectoryItem, Maybe, Tags } from 'exiftool-vendored';
|
||||
import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime';
|
||||
import { Insertable } from 'kysely';
|
||||
import _ from 'lodash';
|
||||
import { Duration } from 'luxon';
|
||||
import { Stats } from 'node:fs';
|
||||
import { constants } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants';
|
||||
@@ -77,6 +78,11 @@ const validateRange = (value: number | undefined, min: number, max: number): Non
|
||||
|
||||
type ImmichTagsWithFaces = ImmichTags & { RegionInfo: NonNullable<ImmichTags['RegionInfo']> };
|
||||
|
||||
type Dates = {
|
||||
dateTimeOriginal: Date;
|
||||
localDateTime: Date;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class MetadataService extends BaseService {
|
||||
@OnEvent({ name: 'app.bootstrap', workers: [ImmichWorker.MICROSERVICES] })
|
||||
@@ -111,6 +117,7 @@ export class MetadataService extends BaseService {
|
||||
this.logger.log(`Initialized local reverse geocoder`);
|
||||
} catch (error: Error | any) {
|
||||
this.logger.error(`Unable to initialize reverse geocoding: ${error}`, error?.stack);
|
||||
throw new Error(`Metadata service init failed`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,18 +178,13 @@ export class MetadataService extends BaseService {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
const exifTags = await this.getExifTags(asset);
|
||||
if (!exifTags.FileCreateDate || !exifTags.FileModifyDate || exifTags.FileSize === undefined) {
|
||||
this.logger.warn(`Missing file creation or modification date for asset ${asset.id}: ${asset.originalPath}`);
|
||||
const stat = await this.storageRepository.stat(asset.originalPath);
|
||||
exifTags.FileCreateDate = stat.ctime.toISOString();
|
||||
exifTags.FileModifyDate = stat.mtime.toISOString();
|
||||
exifTags.FileSize = stat.size.toString();
|
||||
}
|
||||
|
||||
const [exifTags, stats] = await Promise.all([
|
||||
this.getExifTags(asset),
|
||||
this.storageRepository.stat(asset.originalPath),
|
||||
]);
|
||||
this.logger.verbose('Exif Tags', exifTags);
|
||||
|
||||
const { dateTimeOriginal, localDateTime, timeZone, modifyDate } = this.getDates(asset, exifTags);
|
||||
const dates = this.getDates(asset, exifTags, stats);
|
||||
|
||||
const { width, height } = this.getImageDimensions(exifTags);
|
||||
let geo: ReverseGeocodeResult, latitude: number | null, longitude: number | null;
|
||||
@@ -200,9 +202,9 @@ export class MetadataService extends BaseService {
|
||||
assetId: asset.id,
|
||||
|
||||
// dates
|
||||
dateTimeOriginal,
|
||||
modifyDate,
|
||||
timeZone,
|
||||
dateTimeOriginal: dates.dateTimeOriginal,
|
||||
modifyDate: stats.mtime,
|
||||
timeZone: dates.timeZone,
|
||||
|
||||
// gps
|
||||
latitude,
|
||||
@@ -212,7 +214,7 @@ export class MetadataService extends BaseService {
|
||||
city: geo.city,
|
||||
|
||||
// image/file
|
||||
fileSizeInByte: Number.parseInt(exifTags.FileSize!),
|
||||
fileSizeInByte: stats.size,
|
||||
exifImageHeight: validate(height),
|
||||
exifImageWidth: validate(width),
|
||||
orientation: validate(exifTags.Orientation)?.toString() ?? null,
|
||||
@@ -245,15 +247,15 @@ export class MetadataService extends BaseService {
|
||||
this.assetRepository.update({
|
||||
id: asset.id,
|
||||
duration: exifTags.Duration?.toString() ?? null,
|
||||
localDateTime,
|
||||
fileCreatedAt: exifData.dateTimeOriginal ?? undefined,
|
||||
fileModifiedAt: exifData.modifyDate ?? undefined,
|
||||
localDateTime: dates.localDateTime,
|
||||
fileCreatedAt: dates.dateTimeOriginal ?? undefined,
|
||||
fileModifiedAt: stats.mtime,
|
||||
}),
|
||||
this.applyTagList(asset, exifTags),
|
||||
];
|
||||
|
||||
if (this.isMotionPhoto(asset, exifTags)) {
|
||||
promises.push(this.applyMotionPhotos(asset, exifTags, exifData.fileSizeInByte!));
|
||||
promises.push(this.applyMotionPhotos(asset, exifTags, dates, stats));
|
||||
}
|
||||
|
||||
if (isFaceImportEnabled(metadata) && this.hasTaggedFaces(exifTags)) {
|
||||
@@ -432,7 +434,7 @@ export class MetadataService extends BaseService {
|
||||
return asset.type === AssetType.IMAGE && !!(tags.MotionPhoto || tags.MicroVideo);
|
||||
}
|
||||
|
||||
private async applyMotionPhotos(asset: AssetEntity, tags: ImmichTags, fileSize: number) {
|
||||
private async applyMotionPhotos(asset: AssetEntity, tags: ImmichTags, dates: Dates, stats: Stats) {
|
||||
const isMotionPhoto = tags.MotionPhoto;
|
||||
const isMicroVideo = tags.MicroVideo;
|
||||
const videoOffset = tags.MicroVideoOffset;
|
||||
@@ -466,7 +468,7 @@ export class MetadataService extends BaseService {
|
||||
this.logger.debug(`Starting motion photo video extraction for asset ${asset.id}: ${asset.originalPath}`);
|
||||
|
||||
try {
|
||||
const position = fileSize - length - padding;
|
||||
const position = stats.size - length - padding;
|
||||
let video: Buffer;
|
||||
// Samsung MotionPhoto video extraction
|
||||
// HEIC-encoded
|
||||
@@ -505,13 +507,12 @@ export class MetadataService extends BaseService {
|
||||
}
|
||||
} else {
|
||||
const motionAssetId = this.cryptoRepository.randomUUID();
|
||||
const dates = this.getDates(asset, tags);
|
||||
motionAsset = await this.assetRepository.create({
|
||||
id: motionAssetId,
|
||||
libraryId: asset.libraryId,
|
||||
type: AssetType.VIDEO,
|
||||
fileCreatedAt: dates.dateTimeOriginal,
|
||||
fileModifiedAt: dates.modifyDate,
|
||||
fileModifiedAt: stats.mtime,
|
||||
localDateTime: dates.localDateTime,
|
||||
checksum,
|
||||
ownerId: asset.ownerId,
|
||||
@@ -634,7 +635,7 @@ export class MetadataService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
private getDates(asset: AssetEntity, exifTags: ImmichTags) {
|
||||
private getDates(asset: AssetEntity, exifTags: ImmichTags, stats: Stats) {
|
||||
const dateTime = firstDateTime(exifTags as Maybe<Tags>, EXIF_DATE_TAGS);
|
||||
this.logger.verbose(`Date and time is ${dateTime} for asset ${asset.id}: ${asset.originalPath}`);
|
||||
|
||||
@@ -654,17 +655,16 @@ export class MetadataService extends BaseService {
|
||||
this.logger.debug(`No timezone information found for asset ${asset.id}: ${asset.originalPath}`);
|
||||
}
|
||||
|
||||
const modifyDate = this.toDate(exifTags.FileModifyDate!);
|
||||
let dateTimeOriginal = dateTime?.toDate();
|
||||
let localDateTime = dateTime?.toDateTime().setZone('UTC', { keepLocalTime: true }).toJSDate();
|
||||
if (!localDateTime || !dateTimeOriginal) {
|
||||
const fileCreatedAt = this.toDate(exifTags.FileCreateDate!);
|
||||
const earliestDate = this.earliestDate(fileCreatedAt, modifyDate);
|
||||
// FileCreateDate is not available on linux, likely because exiftool hasn't integrated the statx syscall yet
|
||||
// birthtime is not available in Docker on macOS, so it appears as 0
|
||||
const earliestDate = stats.birthtimeMs ? new Date(Math.min(stats.mtimeMs, stats.birthtimeMs)) : stats.mtime;
|
||||
this.logger.debug(
|
||||
`No exif date time found, falling back on ${earliestDate.toISOString()}, earliest of file creation and modification for assset ${asset.id}: ${asset.originalPath}`,
|
||||
`No exif date time found, falling back on ${earliestDate.toISOString()}, earliest of file creation and modification for asset ${asset.id}: ${asset.originalPath}`,
|
||||
);
|
||||
dateTimeOriginal = earliestDate;
|
||||
localDateTime = earliestDate;
|
||||
dateTimeOriginal = localDateTime = earliestDate;
|
||||
}
|
||||
|
||||
this.logger.verbose(
|
||||
@@ -675,18 +675,9 @@ export class MetadataService extends BaseService {
|
||||
dateTimeOriginal,
|
||||
timeZone,
|
||||
localDateTime,
|
||||
modifyDate,
|
||||
};
|
||||
}
|
||||
|
||||
private toDate(date: string | ExifDateTime): Date {
|
||||
return typeof date === 'string' ? new Date(date) : date.toDate();
|
||||
}
|
||||
|
||||
private earliestDate(a: Date, b: Date) {
|
||||
return new Date(Math.min(a.valueOf(), b.valueOf()));
|
||||
}
|
||||
|
||||
private hasGeo(tags: ImmichTags): tags is ImmichTags & { GPSLatitude: number; GPSLongitude: number } {
|
||||
return (
|
||||
tags.GPSLatitude !== undefined &&
|
||||
|
||||
@@ -2,12 +2,12 @@ import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { OnEvent, OnJob } from 'src/decorators';
|
||||
import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto';
|
||||
import { AlbumEntity } from 'src/entities/album.entity';
|
||||
import { JobName, JobStatus, QueueName } from 'src/enum';
|
||||
import { AssetFileType, JobName, JobStatus, QueueName } from 'src/enum';
|
||||
import { ArgOf } from 'src/repositories/event.repository';
|
||||
import { EmailTemplate } from 'src/repositories/notification.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { EmailImageAttachment, IEntityJob, INotifyAlbumUpdateJob, JobItem, JobOf } from 'src/types';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { getAssetFile } from 'src/utils/asset.util';
|
||||
import { getFilenameExtension } from 'src/utils/file';
|
||||
import { getExternalDomain } from 'src/utils/misc';
|
||||
import { isEqualObject } from 'src/utils/object';
|
||||
@@ -398,7 +398,11 @@ export class NotificationService extends BaseService {
|
||||
}
|
||||
|
||||
const albumThumbnail = await this.assetRepository.getById(album.albumThumbnailAssetId, { files: true });
|
||||
const { thumbnailFile } = getAssetFiles(albumThumbnail?.files);
|
||||
if (!albumThumbnail) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thumbnailFile = getAssetFile(albumThumbnail.files, AssetFileType.THUMBNAIL);
|
||||
if (!thumbnailFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -896,6 +896,66 @@ describe(PersonService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should match existing person if their birth date is unknown', async () => {
|
||||
if (!faceStub.primaryFace1.person) {
|
||||
throw new Error('faceStub.primaryFace1.person is null');
|
||||
}
|
||||
|
||||
const faces = [
|
||||
{ ...faceStub.noPerson1, distance: 0 },
|
||||
{ ...faceStub.primaryFace1, distance: 0.2 },
|
||||
{ ...faceStub.withBirthDate, distance: 0.3 },
|
||||
] as FaceSearchResult[];
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue({ machineLearning: { facialRecognition: { minFaces: 1 } } });
|
||||
mocks.search.searchFaces.mockResolvedValue(faces);
|
||||
mocks.person.getFaceByIdWithAssets.mockResolvedValue(faceStub.noPerson1);
|
||||
mocks.person.create.mockResolvedValue(faceStub.primaryFace1.person);
|
||||
|
||||
await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id });
|
||||
|
||||
expect(mocks.person.create).not.toHaveBeenCalled();
|
||||
expect(mocks.person.reassignFaces).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.person.reassignFaces).toHaveBeenCalledWith({
|
||||
faceIds: expect.arrayContaining([faceStub.noPerson1.id]),
|
||||
newPersonId: faceStub.primaryFace1.person.id,
|
||||
});
|
||||
expect(mocks.person.reassignFaces).toHaveBeenCalledWith({
|
||||
faceIds: expect.not.arrayContaining([faceStub.face1.id]),
|
||||
newPersonId: faceStub.primaryFace1.person.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should match existing person if their birth date is before file creation', async () => {
|
||||
if (!faceStub.primaryFace1.person) {
|
||||
throw new Error('faceStub.primaryFace1.person is null');
|
||||
}
|
||||
|
||||
const faces = [
|
||||
{ ...faceStub.noPerson1, distance: 0 },
|
||||
{ ...faceStub.withBirthDate, distance: 0.2 },
|
||||
{ ...faceStub.primaryFace1, distance: 0.3 },
|
||||
] as FaceSearchResult[];
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue({ machineLearning: { facialRecognition: { minFaces: 1 } } });
|
||||
mocks.search.searchFaces.mockResolvedValue(faces);
|
||||
mocks.person.getFaceByIdWithAssets.mockResolvedValue(faceStub.noPerson1);
|
||||
mocks.person.create.mockResolvedValue(faceStub.primaryFace1.person);
|
||||
|
||||
await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id });
|
||||
|
||||
expect(mocks.person.create).not.toHaveBeenCalled();
|
||||
expect(mocks.person.reassignFaces).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.person.reassignFaces).toHaveBeenCalledWith({
|
||||
faceIds: expect.arrayContaining([faceStub.noPerson1.id]),
|
||||
newPersonId: faceStub.withBirthDate.person?.id,
|
||||
});
|
||||
expect(mocks.person.reassignFaces).toHaveBeenCalledWith({
|
||||
faceIds: expect.not.arrayContaining([faceStub.face1.id]),
|
||||
newPersonId: faceStub.withBirthDate.person?.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new person if the face is a core point with no person', async () => {
|
||||
const faces = [
|
||||
{ ...faceStub.noPerson1, distance: 0 },
|
||||
|
||||
@@ -26,6 +26,7 @@ import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { FaceSearchEntity } from 'src/entities/face-search.entity';
|
||||
import { PersonEntity } from 'src/entities/person.entity';
|
||||
import {
|
||||
AssetFileType,
|
||||
AssetType,
|
||||
CacheControl,
|
||||
ImageFormat,
|
||||
@@ -42,7 +43,7 @@ import { BoundingBox } from 'src/repositories/machine-learning.repository';
|
||||
import { UpdateFacesData } from 'src/repositories/person.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { CropOptions, ImageDimensions, InputDimensions, JobItem, JobOf } from 'src/types';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { getAssetFile } from 'src/utils/asset.util';
|
||||
import { ImmichFileResponse } from 'src/utils/file';
|
||||
import { mimeTypes } from 'src/utils/mime-types';
|
||||
import { isFaceImportEnabled, isFacialRecognitionEnabled } from 'src/utils/misc';
|
||||
@@ -300,7 +301,7 @@ export class PersonService extends BaseService {
|
||||
|
||||
const relations = { exifInfo: true, faces: { person: false, withDeleted: true }, files: true };
|
||||
const [asset] = await this.assetRepository.getByIds([id], relations);
|
||||
const { previewFile } = getAssetFiles(asset.files);
|
||||
const previewFile = getAssetFile(asset.files, AssetFileType.PREVIEW);
|
||||
if (!asset || !previewFile) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
@@ -482,6 +483,7 @@ export class PersonService extends BaseService {
|
||||
embedding: face.faceSearch.embedding,
|
||||
maxDistance: machineLearning.facialRecognition.maxDistance,
|
||||
numResults: machineLearning.facialRecognition.minFaces,
|
||||
minBirthDate: face.asset.fileCreatedAt,
|
||||
});
|
||||
|
||||
// `matches` also includes the face itself
|
||||
@@ -507,6 +509,7 @@ export class PersonService extends BaseService {
|
||||
maxDistance: machineLearning.facialRecognition.maxDistance,
|
||||
numResults: 1,
|
||||
hasPerson: true,
|
||||
minBirthDate: face.asset.fileCreatedAt,
|
||||
});
|
||||
|
||||
if (matchWithPerson.length > 0) {
|
||||
@@ -674,7 +677,7 @@ export class PersonService extends BaseService {
|
||||
throw new Error(`Asset ${asset.id} dimensions are unknown`);
|
||||
}
|
||||
|
||||
const { previewFile } = getAssetFiles(asset.files);
|
||||
const previewFile = getAssetFile(asset.files, AssetFileType.PREVIEW);
|
||||
if (!previewFile) {
|
||||
throw new Error(`Asset ${asset.id} has no preview path`);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ import { Injectable } from '@nestjs/common';
|
||||
import { SystemConfig } from 'src/config';
|
||||
import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants';
|
||||
import { OnEvent, OnJob } from 'src/decorators';
|
||||
import { DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName } from 'src/enum';
|
||||
import { AssetFileType, DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName } from 'src/enum';
|
||||
import { WithoutProperty } from 'src/repositories/asset.repository';
|
||||
import { ArgOf } from 'src/repositories/event.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { JobOf } from 'src/types';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { getAssetFile } from 'src/utils/asset.util';
|
||||
import { getCLIPModelInfo, isSmartSearchEnabled } from 'src/utils/misc';
|
||||
import { usePagination } from 'src/utils/pagination';
|
||||
|
||||
@@ -116,7 +116,7 @@ export class SmartInfoService extends BaseService {
|
||||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
const { previewFile } = getAssetFiles(asset.files);
|
||||
const previewFile = getAssetFile(asset.files, AssetFileType.PREVIEW);
|
||||
if (!previewFile) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { StorageCore } from 'src/cores/storage.core';
|
||||
import { GeneratedImageType, StorageCore } from 'src/cores/storage.core';
|
||||
import { BulkIdErrorReason, BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto';
|
||||
import { UploadFieldName } from 'src/dtos/asset-media.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
@@ -13,14 +13,14 @@ import { PartnerRepository } from 'src/repositories/partner.repository';
|
||||
import { IBulkAsset, ImmichFile, UploadFile } from 'src/types';
|
||||
import { checkAccess } from 'src/utils/access';
|
||||
|
||||
const getFileByType = (files: AssetFileEntity[] | undefined, type: AssetFileType) => {
|
||||
export const getAssetFile = (files: AssetFileEntity[], type: AssetFileType | GeneratedImageType) => {
|
||||
return (files || []).find((file) => file.type === type);
|
||||
};
|
||||
|
||||
export const getAssetFiles = (files?: AssetFileEntity[]) => ({
|
||||
fullsizeFile: getFileByType(files, AssetFileType.FULLSIZE),
|
||||
previewFile: getFileByType(files, AssetFileType.PREVIEW),
|
||||
thumbnailFile: getFileByType(files, AssetFileType.THUMBNAIL),
|
||||
export const getAssetFiles = (files: AssetFileEntity[]) => ({
|
||||
fullsizeFile: getAssetFile(files, AssetFileType.FULLSIZE),
|
||||
previewFile: getAssetFile(files, AssetFileType.PREVIEW),
|
||||
thumbnailFile: getAssetFile(files, AssetFileType.THUMBNAIL),
|
||||
});
|
||||
|
||||
export const addAssets = async (
|
||||
|
||||
@@ -128,6 +128,8 @@ export class BaseConfig implements VideoCodecSWConfig {
|
||||
'-fps_mode passthrough',
|
||||
// explicitly selects the video stream instead of leaving it up to FFmpeg
|
||||
`-map 0:${videoStream.index}`,
|
||||
// Strip metadata like capture date, camera, and GPS
|
||||
'-map_metadata -1',
|
||||
];
|
||||
|
||||
if (audioStream) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Insertable, Kysely } from 'kysely';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { Writable } from 'node:stream';
|
||||
import { Assets, DB, Partners, Sessions } from 'src/db';
|
||||
import { AssetFaces, Assets, DB, Person as DbPerson, FaceSearch, Partners, Sessions } from 'src/db';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { AssetType } from 'src/enum';
|
||||
import { AssetType, SourceType } from 'src/enum';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
import { AlbumRepository } from 'src/repositories/album.repository';
|
||||
@@ -37,7 +37,7 @@ import { VersionHistoryRepository } from 'src/repositories/version-history.repos
|
||||
import { ViewRepository } from 'src/repositories/view-repository';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock';
|
||||
import { newUuid } from 'test/small.factory';
|
||||
import { newDate, newEmbedding, newUuid } from 'test/small.factory';
|
||||
import { automock } from 'test/utils';
|
||||
|
||||
class CustomWritable extends Writable {
|
||||
@@ -61,12 +61,18 @@ type Asset = Partial<Insertable<Assets>>;
|
||||
type User = Partial<Insertable<UserTable>>;
|
||||
type Session = Omit<Insertable<Sessions>, 'token'> & { token?: string };
|
||||
type Partner = Insertable<Partners>;
|
||||
type AssetFace = Partial<Insertable<AssetFaces>>;
|
||||
type Person = Partial<Insertable<DbPerson>>;
|
||||
type Face = Partial<Insertable<FaceSearch>>;
|
||||
|
||||
export class TestFactory {
|
||||
private assets: Asset[] = [];
|
||||
private sessions: Session[] = [];
|
||||
private users: User[] = [];
|
||||
private partners: Partner[] = [];
|
||||
private assetFaces: AssetFace[] = [];
|
||||
private persons: Person[] = [];
|
||||
private faces: Face[] = [];
|
||||
|
||||
private constructor(private context: TestContext) {}
|
||||
|
||||
@@ -141,6 +147,53 @@ export class TestFactory {
|
||||
};
|
||||
}
|
||||
|
||||
static assetFace(assetFace: AssetFace) {
|
||||
const defaults = {
|
||||
assetId: assetFace.assetId || newUuid(),
|
||||
boundingBoxX1: assetFace.boundingBoxX1 || 0,
|
||||
boundingBoxX2: assetFace.boundingBoxX2 || 1,
|
||||
boundingBoxY1: assetFace.boundingBoxY1 || 0,
|
||||
boundingBoxY2: assetFace.boundingBoxY2 || 1,
|
||||
deletedAt: assetFace.deletedAt || null,
|
||||
id: assetFace.id || newUuid(),
|
||||
imageHeight: assetFace.imageHeight || 10,
|
||||
imageWidth: assetFace.imageWidth || 10,
|
||||
personId: assetFace.personId || null,
|
||||
sourceType: assetFace.sourceType || SourceType.MACHINE_LEARNING,
|
||||
};
|
||||
|
||||
return { ...defaults, ...assetFace };
|
||||
}
|
||||
|
||||
static person(person: Person) {
|
||||
const defaults = {
|
||||
birthDate: person.birthDate || null,
|
||||
color: person.color || null,
|
||||
createdAt: person.createdAt || newDate(),
|
||||
faceAssetId: person.faceAssetId || null,
|
||||
id: person.id || newUuid(),
|
||||
isFavorite: person.isFavorite || false,
|
||||
isHidden: person.isHidden || false,
|
||||
name: person.name || 'Test Name',
|
||||
ownerId: person.ownerId || newUuid(),
|
||||
thumbnailPath: person.thumbnailPath || '/path/to/thumbnail.jpg',
|
||||
updatedAt: person.updatedAt || newDate(),
|
||||
updateId: person.updateId || newUuid(),
|
||||
};
|
||||
return { ...defaults, ...person };
|
||||
}
|
||||
|
||||
static face(face: Face) {
|
||||
const defaults = {
|
||||
faceId: face.faceId || newUuid(),
|
||||
embedding: face.embedding || newEmbedding(),
|
||||
};
|
||||
return {
|
||||
...defaults,
|
||||
...face,
|
||||
};
|
||||
}
|
||||
|
||||
withAsset(asset: Asset) {
|
||||
this.assets.push(asset);
|
||||
return this;
|
||||
@@ -161,6 +214,21 @@ export class TestFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
withAssetFace(assetFace: AssetFace) {
|
||||
this.assetFaces.push(assetFace);
|
||||
return this;
|
||||
}
|
||||
|
||||
withPerson(person: Person) {
|
||||
this.persons.push(person);
|
||||
return this;
|
||||
}
|
||||
|
||||
withFaces(face: Face) {
|
||||
this.faces.push(face);
|
||||
return this;
|
||||
}
|
||||
|
||||
async create() {
|
||||
for (const user of this.users) {
|
||||
await this.context.createUser(user);
|
||||
@@ -178,6 +246,16 @@ export class TestFactory {
|
||||
await this.context.createAsset(asset);
|
||||
}
|
||||
|
||||
for (const person of this.persons) {
|
||||
await this.context.createPerson(person);
|
||||
}
|
||||
|
||||
await this.context.refreshFaces(
|
||||
this.assetFaces,
|
||||
[],
|
||||
this.faces.map((f) => TestFactory.face(f)),
|
||||
);
|
||||
|
||||
return this.context;
|
||||
}
|
||||
}
|
||||
@@ -276,4 +354,16 @@ export class TestContext {
|
||||
createSession(session: Session) {
|
||||
return this.session.create(TestFactory.session(session));
|
||||
}
|
||||
|
||||
createPerson(person: Person) {
|
||||
return this.person.create(TestFactory.person(person));
|
||||
}
|
||||
|
||||
refreshFaces(facesToAdd: AssetFace[], faceIdsToRemove: string[], embeddingsToAdd?: Insertable<FaceSearch>[]) {
|
||||
return this.person.refreshFaces(
|
||||
facesToAdd.map((f) => TestFactory.assetFace(f)),
|
||||
faceIdsToRemove,
|
||||
embeddingsToAdd,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
12
server/test/fixtures/asset.stub.ts
vendored
12
server/test/fixtures/asset.stub.ts
vendored
@@ -26,7 +26,16 @@ const thumbnailFile: AssetFileEntity = {
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
};
|
||||
|
||||
const files: AssetFileEntity[] = [previewFile, thumbnailFile];
|
||||
const fullsizeFile: AssetFileEntity = {
|
||||
id: 'file-3',
|
||||
assetId: 'asset-id',
|
||||
type: AssetFileType.FULLSIZE,
|
||||
path: '/uploads/user-id/fullsize/path.webp',
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
};
|
||||
|
||||
const files: AssetFileEntity[] = [fullsizeFile, previewFile, thumbnailFile];
|
||||
|
||||
export const stackStub = (stackId: string, assets: AssetEntity[]): StackEntity => {
|
||||
return {
|
||||
@@ -553,6 +562,7 @@ export const assetStub = {
|
||||
fileSizeInByte: 25_000,
|
||||
timeZone: `America/New_York`,
|
||||
},
|
||||
files,
|
||||
} as AssetEntity),
|
||||
|
||||
livePhotoWithOriginalFileName: Object.freeze({
|
||||
|
||||
15
server/test/fixtures/face.stub.ts
vendored
15
server/test/fixtures/face.stub.ts
vendored
@@ -164,4 +164,19 @@ export const faceStub = {
|
||||
sourceType: SourceType.EXIF,
|
||||
deletedAt: null,
|
||||
}),
|
||||
withBirthDate: Object.freeze<AssetFaceEntity>({
|
||||
id: 'assetFaceId10',
|
||||
assetId: assetStub.image.id,
|
||||
asset: assetStub.image,
|
||||
personId: personStub.withBirthDate.id,
|
||||
person: personStub.withBirthDate,
|
||||
boundingBoxX1: 0,
|
||||
boundingBoxY1: 0,
|
||||
boundingBoxX2: 1,
|
||||
boundingBoxY2: 1,
|
||||
imageHeight: 1024,
|
||||
imageWidth: 1024,
|
||||
sourceType: SourceType.MACHINE_LEARNING,
|
||||
deletedAt: null,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -39,7 +39,12 @@ describe(MetadataService.name, () => {
|
||||
beforeEach(() => {
|
||||
({ sut, mocks } = newTestService(MetadataService, { metadata: metadataRepository }));
|
||||
|
||||
mocks.storage.stat.mockResolvedValue({ size: 123_456, ctime: new Date(), mtime: new Date() } as Stats);
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: new Date(654_321),
|
||||
mtimeMs: 654_321,
|
||||
birthtimeMs: 654_322,
|
||||
} as Stats);
|
||||
|
||||
delete process.env.TZ;
|
||||
});
|
||||
@@ -54,8 +59,6 @@ describe(MetadataService.name, () => {
|
||||
description: 'should handle no time zone information',
|
||||
exifData: {
|
||||
DateTimeOriginal: '2022:01:01 00:00:00',
|
||||
FileCreateDate: '2022:01:01 00:00:00',
|
||||
FileModifyDate: '2022:01:01 00:00:00',
|
||||
},
|
||||
expected: {
|
||||
localDateTime: '2022-01-01T00:00:00.000Z',
|
||||
@@ -68,8 +71,6 @@ describe(MetadataService.name, () => {
|
||||
serverTimeZone: 'America/Los_Angeles',
|
||||
exifData: {
|
||||
DateTimeOriginal: '2022:01:01 00:00:00',
|
||||
FileCreateDate: '2022:01:01 00:00:00',
|
||||
FileModifyDate: '2022:01:01 00:00:00',
|
||||
},
|
||||
expected: {
|
||||
localDateTime: '2022-01-01T00:00:00.000Z',
|
||||
@@ -82,8 +83,6 @@ describe(MetadataService.name, () => {
|
||||
serverTimeZone: 'Europe/Brussels',
|
||||
exifData: {
|
||||
DateTimeOriginal: '2022:01:01 00:00:00',
|
||||
FileCreateDate: '2022:01:01 00:00:00',
|
||||
FileModifyDate: '2022:01:01 00:00:00',
|
||||
},
|
||||
expected: {
|
||||
localDateTime: '2022-01-01T00:00:00.000Z',
|
||||
@@ -96,8 +95,6 @@ describe(MetadataService.name, () => {
|
||||
serverTimeZone: 'Europe/Brussels',
|
||||
exifData: {
|
||||
DateTimeOriginal: '2022:06:01 00:00:00',
|
||||
FileCreateDate: '2022:06:01 00:00:00',
|
||||
FileModifyDate: '2022:06:01 00:00:00',
|
||||
},
|
||||
expected: {
|
||||
localDateTime: '2022-06-01T00:00:00.000Z',
|
||||
@@ -109,8 +106,6 @@ describe(MetadataService.name, () => {
|
||||
description: 'should handle a +13:00 time zone',
|
||||
exifData: {
|
||||
DateTimeOriginal: '2022:01:01 00:00:00+13:00',
|
||||
FileCreateDate: '2022:01:01 00:00:00+13:00',
|
||||
FileModifyDate: '2022:01:01 00:00:00+13:00',
|
||||
},
|
||||
expected: {
|
||||
localDateTime: '2022-01-01T00:00:00.000Z',
|
||||
|
||||
201
server/test/medium/specs/person.service.spec.ts
Normal file
201
server/test/medium/specs/person.service.spec.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import { Kysely } from 'kysely';
|
||||
import { JobStatus, SourceType } from 'src/enum';
|
||||
import { PersonService } from 'src/services/person.service';
|
||||
import { TestContext, TestFactory } from 'test/factory';
|
||||
import { newEmbedding } from 'test/small.factory';
|
||||
import { getKyselyDB, newTestService } from 'test/utils';
|
||||
|
||||
const setup = async (db: Kysely<any>) => {
|
||||
const context = await TestContext.from(db).create();
|
||||
const { sut, mocks } = newTestService(PersonService, context);
|
||||
|
||||
return { sut, mocks, context };
|
||||
};
|
||||
|
||||
describe.concurrent(PersonService.name, () => {
|
||||
let sut: PersonService;
|
||||
let context: TestContext;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ sut, context } = await setup(await getKyselyDB()));
|
||||
});
|
||||
|
||||
describe('handleRecognizeFaces', () => {
|
||||
it('should skip if face source type is not MACHINE_LEARNING', async () => {
|
||||
const user = TestFactory.user();
|
||||
const asset = TestFactory.asset({ ownerId: user.id });
|
||||
const assetFace = TestFactory.assetFace({ assetId: asset.id, sourceType: SourceType.MANUAL });
|
||||
const face = TestFactory.face({ faceId: assetFace.id });
|
||||
await context.getFactory().withUser(user).withAsset(asset).withAssetFace(assetFace).withFaces(face).create();
|
||||
|
||||
const result = await sut.handleRecognizeFaces({ id: assetFace.id, deferred: false });
|
||||
|
||||
expect(result).toBe(JobStatus.SKIPPED);
|
||||
const newPersonId = await context.db
|
||||
.selectFrom('asset_faces')
|
||||
.select('asset_faces.personId')
|
||||
.where('asset_faces.id', '=', assetFace.id)
|
||||
.executeTakeFirst();
|
||||
expect(newPersonId?.personId).toBeNull();
|
||||
});
|
||||
|
||||
it('should fail if face does not have an embedding', async () => {
|
||||
const user = TestFactory.user();
|
||||
const asset = TestFactory.asset({ ownerId: user.id });
|
||||
const assetFace = TestFactory.assetFace({ assetId: asset.id, sourceType: SourceType.MACHINE_LEARNING });
|
||||
await context.getFactory().withUser(user).withAsset(asset).withAssetFace(assetFace).create();
|
||||
|
||||
const result = await sut.handleRecognizeFaces({ id: assetFace.id, deferred: false });
|
||||
|
||||
expect(result).toBe(JobStatus.FAILED);
|
||||
const newPersonId = await context.db
|
||||
.selectFrom('asset_faces')
|
||||
.select('asset_faces.personId')
|
||||
.where('asset_faces.id', '=', assetFace.id)
|
||||
.executeTakeFirst();
|
||||
expect(newPersonId?.personId).toBeNull();
|
||||
});
|
||||
|
||||
it('should skip if face already has a person assigned', async () => {
|
||||
const user = TestFactory.user();
|
||||
const asset = TestFactory.asset({ ownerId: user.id });
|
||||
const person = TestFactory.person({ ownerId: user.id });
|
||||
const assetFace = TestFactory.assetFace({
|
||||
assetId: asset.id,
|
||||
sourceType: SourceType.MACHINE_LEARNING,
|
||||
personId: person.id,
|
||||
});
|
||||
const face = TestFactory.face({ faceId: assetFace.id });
|
||||
await context
|
||||
.getFactory()
|
||||
.withUser(user)
|
||||
.withAsset(asset)
|
||||
.withPerson(person)
|
||||
.withAssetFace(assetFace)
|
||||
.withFaces(face)
|
||||
.create();
|
||||
|
||||
const result = await sut.handleRecognizeFaces({ id: assetFace.id, deferred: false });
|
||||
|
||||
expect(result).toBe(JobStatus.SKIPPED);
|
||||
const newPersonId = await context.db
|
||||
.selectFrom('asset_faces')
|
||||
.select('asset_faces.personId')
|
||||
.where('asset_faces.id', '=', assetFace.id)
|
||||
.executeTakeFirst();
|
||||
expect(newPersonId?.personId).toEqual(person.id);
|
||||
});
|
||||
|
||||
it('should create a new person if no matches are found', async () => {
|
||||
const user = TestFactory.user();
|
||||
const embedding = newEmbedding();
|
||||
|
||||
let factory = context.getFactory().withUser(user);
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const existingAsset = TestFactory.asset({ ownerId: user.id });
|
||||
const existingAssetFace = TestFactory.assetFace({
|
||||
assetId: existingAsset.id,
|
||||
sourceType: SourceType.MACHINE_LEARNING,
|
||||
});
|
||||
const existingFace = TestFactory.face({ faceId: existingAssetFace.id, embedding });
|
||||
factory = factory.withAsset(existingAsset).withAssetFace(existingAssetFace).withFaces(existingFace);
|
||||
}
|
||||
|
||||
const newAsset = TestFactory.asset({ ownerId: user.id });
|
||||
const newAssetFace = TestFactory.assetFace({ assetId: newAsset.id, sourceType: SourceType.MACHINE_LEARNING });
|
||||
const newFace = TestFactory.face({ faceId: newAssetFace.id, embedding });
|
||||
|
||||
await factory.withAsset(newAsset).withAssetFace(newAssetFace).withFaces(newFace).create();
|
||||
|
||||
const result = await sut.handleRecognizeFaces({ id: newAssetFace.id, deferred: false });
|
||||
|
||||
expect(result).toBe(JobStatus.SUCCESS);
|
||||
|
||||
const newPersonId = await context.db
|
||||
.selectFrom('asset_faces')
|
||||
.select('asset_faces.personId')
|
||||
.where('asset_faces.id', '=', newAssetFace.id)
|
||||
.executeTakeFirstOrThrow();
|
||||
expect(newPersonId.personId).toBeDefined();
|
||||
});
|
||||
|
||||
it('should assign face to an existing person if matches are found', async () => {
|
||||
const user = TestFactory.user();
|
||||
const existingPerson = TestFactory.person({ ownerId: user.id });
|
||||
const embedding = newEmbedding();
|
||||
|
||||
let factory = context.getFactory().withUser(user).withPerson(existingPerson);
|
||||
|
||||
const assetFaces: string[] = [];
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const existingAsset = TestFactory.asset({ ownerId: user.id });
|
||||
const existingAssetFace = TestFactory.assetFace({
|
||||
assetId: existingAsset.id,
|
||||
sourceType: SourceType.MACHINE_LEARNING,
|
||||
});
|
||||
assetFaces.push(existingAssetFace.id);
|
||||
const existingFace = TestFactory.face({ faceId: existingAssetFace.id, embedding });
|
||||
factory = factory.withAsset(existingAsset).withAssetFace(existingAssetFace).withFaces(existingFace);
|
||||
}
|
||||
|
||||
const newAsset = TestFactory.asset({ ownerId: user.id });
|
||||
const newAssetFace = TestFactory.assetFace({ assetId: newAsset.id, sourceType: SourceType.MACHINE_LEARNING });
|
||||
const newFace = TestFactory.face({ faceId: newAssetFace.id, embedding });
|
||||
await factory.withAsset(newAsset).withAssetFace(newAssetFace).withFaces(newFace).create();
|
||||
await context.person.reassignFaces({ newPersonId: existingPerson.id, faceIds: assetFaces });
|
||||
|
||||
const result = await sut.handleRecognizeFaces({ id: newAssetFace.id, deferred: false });
|
||||
|
||||
expect(result).toBe(JobStatus.SUCCESS);
|
||||
|
||||
const after = await context.db
|
||||
.selectFrom('asset_faces')
|
||||
.select('asset_faces.personId')
|
||||
.where('asset_faces.id', '=', newAssetFace.id)
|
||||
.executeTakeFirstOrThrow();
|
||||
expect(after.personId).toEqual(existingPerson.id);
|
||||
});
|
||||
|
||||
it('should not assign face to an existing person if asset is older than person', async () => {
|
||||
const user = TestFactory.user();
|
||||
const assetCreatedAt = new Date('2020-02-23T05:06:29.716Z');
|
||||
const birthDate = new Date(assetCreatedAt.getTime() + 3600 * 1000 * 365);
|
||||
const existingPerson = TestFactory.person({ ownerId: user.id, birthDate });
|
||||
const embedding = newEmbedding();
|
||||
|
||||
let factory = context.getFactory().withUser(user).withPerson(existingPerson);
|
||||
|
||||
const assetFaces: string[] = [];
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const existingAsset = TestFactory.asset({ ownerId: user.id });
|
||||
const existingAssetFace = TestFactory.assetFace({
|
||||
assetId: existingAsset.id,
|
||||
sourceType: SourceType.MACHINE_LEARNING,
|
||||
});
|
||||
assetFaces.push(existingAssetFace.id);
|
||||
const existingFace = TestFactory.face({ faceId: existingAssetFace.id, embedding });
|
||||
factory = factory.withAsset(existingAsset).withAssetFace(existingAssetFace).withFaces(existingFace);
|
||||
}
|
||||
|
||||
const newAsset = TestFactory.asset({ ownerId: user.id, fileCreatedAt: assetCreatedAt });
|
||||
const newAssetFace = TestFactory.assetFace({ assetId: newAsset.id, sourceType: SourceType.MACHINE_LEARNING });
|
||||
const newFace = TestFactory.face({ faceId: newAssetFace.id, embedding });
|
||||
await factory.withAsset(newAsset).withAssetFace(newAssetFace).withFaces(newFace).create();
|
||||
await context.person.reassignFaces({ newPersonId: existingPerson.id, faceIds: assetFaces });
|
||||
|
||||
const result = await sut.handleRecognizeFaces({ id: newAssetFace.id, deferred: false });
|
||||
|
||||
expect(result).toBe(JobStatus.SKIPPED);
|
||||
|
||||
const after = await context.db
|
||||
.selectFrom('asset_faces')
|
||||
.select('asset_faces.personId')
|
||||
.where('asset_faces.id', '=', newAssetFace.id)
|
||||
.executeTakeFirstOrThrow();
|
||||
expect(after.personId).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
36
server/test/repositories/person.repository.mock.ts
Normal file
36
server/test/repositories/person.repository.mock.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { PersonRepository } from 'src/repositories/person.repository';
|
||||
import { RepositoryInterface } from 'src/types';
|
||||
import { Mocked, vitest } from 'vitest';
|
||||
|
||||
export const newPersonRepositoryMock = (): Mocked<RepositoryInterface<PersonRepository>> => {
|
||||
return {
|
||||
reassignFaces: vitest.fn(),
|
||||
unassignFaces: vitest.fn(),
|
||||
delete: vitest.fn(),
|
||||
deleteFaces: vitest.fn(),
|
||||
getAllFaces: vitest.fn(),
|
||||
getAll: vitest.fn(),
|
||||
getAllForUser: vitest.fn(),
|
||||
getAllWithoutFaces: vitest.fn(),
|
||||
getFaces: vitest.fn(),
|
||||
getFaceById: vitest.fn(),
|
||||
getFaceByIdWithAssets: vitest.fn(),
|
||||
reassignFace: vitest.fn(),
|
||||
getById: vitest.fn(),
|
||||
getByName: vitest.fn(),
|
||||
getDistinctNames: vitest.fn(),
|
||||
getStatistics: vitest.fn(),
|
||||
getNumberOfPeople: vitest.fn(),
|
||||
create: vitest.fn(),
|
||||
createAll: vitest.fn(),
|
||||
refreshFaces: vitest.fn(),
|
||||
update: vitest.fn(),
|
||||
updateAll: vitest.fn(),
|
||||
getFacesByIds: vitest.fn(),
|
||||
getRandomFace: vitest.fn(),
|
||||
getLatestFaceDate: vitest.fn(),
|
||||
createAssetFace: vitest.fn(),
|
||||
deleteAssetFace: vitest.fn(),
|
||||
softDeleteAssetFaces: vitest.fn(),
|
||||
};
|
||||
};
|
||||
@@ -12,6 +12,12 @@ export const newUuids = () =>
|
||||
export const newDate = () => new Date();
|
||||
export const newUpdateId = () => 'uuid-v7';
|
||||
export const newSha1 = () => Buffer.from('this is a fake hash');
|
||||
export const newEmbedding = () => {
|
||||
const embedding = Array.from({ length: 512 })
|
||||
.fill(0)
|
||||
.map(() => Math.random());
|
||||
return '[' + embedding + ']';
|
||||
};
|
||||
|
||||
const authFactory = ({ apiKey, ...user }: Partial<AuthUser> & { apiKey?: Partial<AuthApiKey> } = {}) => {
|
||||
const auth: AuthDto = {
|
||||
|
||||
@@ -58,6 +58,7 @@ import { newDatabaseRepositoryMock } from 'test/repositories/database.repository
|
||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
|
||||
import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock';
|
||||
import { newMetadataRepositoryMock } from 'test/repositories/metadata.repository.mock';
|
||||
import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
|
||||
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
|
||||
import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock';
|
||||
import { ITelemetryRepositoryMock, newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock';
|
||||
@@ -197,7 +198,7 @@ export const newTestService = <T extends BaseService>(
|
||||
notification: automock(NotificationRepository, { args: [loggerMock] }),
|
||||
oauth: automock(OAuthRepository, { args: [loggerMock] }),
|
||||
partner: automock(PartnerRepository, { strict: false }),
|
||||
person: automock(PersonRepository, { strict: false }),
|
||||
person: newPersonRepositoryMock(),
|
||||
process: automock(ProcessRepository, { args: [loggerMock] }),
|
||||
search: automock(SearchRepository, { args: [loggerMock], strict: false }),
|
||||
// eslint-disable-next-line no-sparse-arrays
|
||||
|
||||
6
web/package-lock.json
generated
6
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich-web",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich-web",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||
@@ -81,7 +81,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-web",
|
||||
"version": "1.131.2",
|
||||
"version": "1.131.3",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
|
||||
interface Props {
|
||||
show: boolean;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
let { show = $bindable() }: Props = $props();
|
||||
let { show = $bindable(), active = $bindable() }: Props = $props();
|
||||
</script>
|
||||
|
||||
<button type="button" onclick={() => (show = true)}>Open</button>
|
||||
|
||||
{#if show}
|
||||
<div use:focusTrap>
|
||||
<div use:focusTrap={{ active }}>
|
||||
<div>
|
||||
<span>text</span>
|
||||
<button data-testid="one" type="button" onclick={() => (show = false)}>Close</button>
|
||||
|
||||
@@ -12,6 +12,12 @@ describe('focusTrap action', () => {
|
||||
expect(document.activeElement).toEqual(screen.getByTestId('one'));
|
||||
});
|
||||
|
||||
it('should not set focus if inactive', async () => {
|
||||
render(FocusTrapTest, { show: true, active: false });
|
||||
await tick();
|
||||
expect(document.activeElement).toBe(document.body);
|
||||
});
|
||||
|
||||
it('supports backward focus wrapping', async () => {
|
||||
render(FocusTrapTest, { show: true });
|
||||
await tick();
|
||||
|
||||
@@ -35,12 +35,12 @@ export function clickOutside(node: HTMLElement, options: Options = {}): ActionRe
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClick, true);
|
||||
document.addEventListener('mousedown', handleClick, false);
|
||||
node.addEventListener('keydown', handleKey, false);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('mousedown', handleClick, true);
|
||||
document.removeEventListener('mousedown', handleClick, false);
|
||||
node.removeEventListener('keydown', handleKey, false);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,16 +1,34 @@
|
||||
import { shortcuts } from '$lib/actions/shortcut';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
const selectors =
|
||||
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
||||
interface Options {
|
||||
/**
|
||||
* Set whether the trap is active or not.
|
||||
*/
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
export function focusTrap(container: HTMLElement) {
|
||||
const selectors =
|
||||
'button:not([disabled], .hidden), [href]:not(.hidden), input:not([disabled], .hidden), select:not([disabled], .hidden), textarea:not([disabled], .hidden), [tabindex]:not([tabindex="-1"], .hidden)';
|
||||
|
||||
export function focusTrap(container: HTMLElement, options?: Options) {
|
||||
const triggerElement = document.activeElement;
|
||||
|
||||
const focusableElement = container.querySelector<HTMLElement>(selectors);
|
||||
const withDefaults = (options?: Options) => {
|
||||
return {
|
||||
active: options?.active ?? true,
|
||||
};
|
||||
};
|
||||
|
||||
// Use tick() to ensure focus trap works correctly inside <Portal />
|
||||
void tick().then(() => focusableElement?.focus());
|
||||
const setInitialFocus = () => {
|
||||
const focusableElement = container.querySelector<HTMLElement>(selectors);
|
||||
// Use tick() to ensure focus trap works correctly inside <Portal />
|
||||
void tick().then(() => focusableElement?.focus());
|
||||
};
|
||||
|
||||
if (withDefaults(options).active) {
|
||||
setInitialFocus();
|
||||
}
|
||||
|
||||
const getFocusableElements = (): [HTMLElement | null, HTMLElement | null] => {
|
||||
const focusableElements = container.querySelectorAll<HTMLElement>(selectors);
|
||||
@@ -27,7 +45,7 @@ export function focusTrap(container: HTMLElement) {
|
||||
shortcut: { key: 'Tab' },
|
||||
onShortcut: (event) => {
|
||||
const [firstElement, lastElement] = getFocusableElements();
|
||||
if (document.activeElement === lastElement) {
|
||||
if (document.activeElement === lastElement && withDefaults(options).active) {
|
||||
event.preventDefault();
|
||||
firstElement?.focus();
|
||||
}
|
||||
@@ -39,7 +57,7 @@ export function focusTrap(container: HTMLElement) {
|
||||
shortcut: { key: 'Tab', shift: true },
|
||||
onShortcut: (event) => {
|
||||
const [firstElement, lastElement] = getFocusableElements();
|
||||
if (document.activeElement === firstElement) {
|
||||
if (document.activeElement === firstElement && withDefaults(options).active) {
|
||||
event.preventDefault();
|
||||
lastElement?.focus();
|
||||
}
|
||||
@@ -48,6 +66,12 @@ export function focusTrap(container: HTMLElement) {
|
||||
]);
|
||||
|
||||
return {
|
||||
update(newOptions?: Options) {
|
||||
options = newOptions;
|
||||
if (withDefaults(options).active) {
|
||||
setInitialFocus();
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
destroyShortcuts?.();
|
||||
if (triggerElement instanceof HTMLElement) {
|
||||
|
||||
@@ -592,29 +592,27 @@
|
||||
id="stack-slideshow"
|
||||
class="z-[1002] flex place-item-center place-content-center absolute bottom-0 w-full col-span-4 col-start-1 overflow-x-auto horizontal-scrollbar"
|
||||
>
|
||||
<div class="relative w-full whitespace-nowrap transition-all">
|
||||
<div class="relative w-full whitespace-nowrap">
|
||||
{#each stackedAssets as stackedAsset (stackedAsset.id)}
|
||||
<div
|
||||
class="{stackedAsset.id == asset.id
|
||||
? '-translate-y-[1px]'
|
||||
: '-translate-y-0'} inline-block px-1 transition-transform"
|
||||
class={['inline-block px-1 relative transition-all pb-2']}
|
||||
style:bottom={stackedAsset.id === asset.id ? '0' : '-10px'}
|
||||
>
|
||||
<Thumbnail
|
||||
class="{stackedAsset.id == asset.id
|
||||
? 'bg-transparent border-2 border-white'
|
||||
: 'bg-gray-700/40'} inline-block hover:bg-transparent"
|
||||
imageClass={{ 'border-2 border-white': stackedAsset.id === asset.id }}
|
||||
dimmed={stackedAsset.id !== asset.id}
|
||||
asset={stackedAsset}
|
||||
onClick={(stackedAsset) => {
|
||||
asset = stackedAsset;
|
||||
}}
|
||||
onMouseEvent={({ isMouseOver }) => handleStackedAssetMouseEvent(isMouseOver, stackedAsset)}
|
||||
disableMouseOver
|
||||
readonly
|
||||
thumbnailSize={stackedAsset.id == asset.id ? 65 : 60}
|
||||
thumbnailSize={stackedAsset.id === asset.id ? 65 : 60}
|
||||
showStackedIcon={false}
|
||||
disableLinkMouseOver
|
||||
/>
|
||||
|
||||
{#if stackedAsset.id == asset.id}
|
||||
{#if stackedAsset.id === asset.id}
|
||||
<div class="w-full flex place-items-center place-content-center">
|
||||
<div class="w-2 h-2 bg-white rounded-full flex mt-[2px]"></div>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,9 @@ vi.hoisted(() => {
|
||||
describe('Thumbnail component', () => {
|
||||
beforeAll(() => {
|
||||
vi.stubGlobal('IntersectionObserver', getIntersectionObserverMock());
|
||||
vi.mock('$lib/utils/navigation', () => ({
|
||||
currentUrlReplaceAssetId: vi.fn(),
|
||||
}));
|
||||
});
|
||||
|
||||
it('should only contain a single tabbable element (the container)', () => {
|
||||
@@ -30,7 +33,6 @@ describe('Thumbnail component', () => {
|
||||
render(Thumbnail, {
|
||||
asset,
|
||||
focussed: false,
|
||||
overrideDisplayForTest: true,
|
||||
selected: true,
|
||||
});
|
||||
|
||||
@@ -55,7 +57,6 @@ describe('Thumbnail component', () => {
|
||||
const handleFocusSpy = vi.fn();
|
||||
render(Thumbnail, {
|
||||
asset,
|
||||
overrideDisplayForTest: true,
|
||||
handleFocus: handleFocusSpy,
|
||||
});
|
||||
|
||||
@@ -70,7 +71,6 @@ describe('Thumbnail component', () => {
|
||||
const handleFocusSpy = vi.fn();
|
||||
render(Thumbnail, {
|
||||
asset,
|
||||
overrideDisplayForTest: true,
|
||||
focussed: true,
|
||||
handleFocus: handleFocusSpy,
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { TUNABLES } from '$lib/utils/tunables';
|
||||
import { mdiEyeOffOutline } from '@mdi/js';
|
||||
import type { ClassValue } from 'svelte/elements';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
interface Props {
|
||||
@@ -19,6 +20,7 @@
|
||||
hidden?: boolean;
|
||||
border?: boolean;
|
||||
hiddenIconClass?: string;
|
||||
class?: ClassValue;
|
||||
onComplete?: (() => void) | undefined;
|
||||
}
|
||||
|
||||
@@ -36,6 +38,7 @@
|
||||
border = false,
|
||||
hiddenIconClass = 'text-white',
|
||||
onComplete = undefined,
|
||||
class: imageClass = '',
|
||||
}: Props = $props();
|
||||
|
||||
let {
|
||||
@@ -88,7 +91,7 @@
|
||||
src={url}
|
||||
alt={loaded || errored ? altText : ''}
|
||||
{title}
|
||||
class="object-cover {optionalClasses}"
|
||||
class={['object-cover', optionalClasses, imageClass]}
|
||||
class:opacity-0={!thumbhash && !loaded}
|
||||
draggable="false"
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<script lang="ts">
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { ProjectionType } from '$lib/constants';
|
||||
import { getAssetThumbnailUrl, isSharedLink } from '$lib/utils';
|
||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||
import { timeToSeconds } from '$lib/utils/date-time';
|
||||
import { AssetMediaSize, AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
|
||||
import { locale, playVideoThumbnailOnHover } from '$lib/stores/preferences.store';
|
||||
import { getAssetPlaybackUrl } from '$lib/utils';
|
||||
import { getAssetPlaybackUrl, getAssetThumbnailUrl, isSharedLink } from '$lib/utils';
|
||||
import { timeToSeconds } from '$lib/utils/date-time';
|
||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||
import { AssetMediaSize, AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
|
||||
import {
|
||||
mdiArchiveArrowDownOutline,
|
||||
mdiCameraBurst,
|
||||
@@ -17,13 +16,14 @@
|
||||
mdiRotate360,
|
||||
} from '@mdi/js';
|
||||
|
||||
import { thumbhash } from '$lib/actions/thumbhash';
|
||||
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
||||
import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
|
||||
import { TUNABLES } from '$lib/utils/tunables';
|
||||
import type { ClassValue } from 'svelte/elements';
|
||||
import { fade } from 'svelte/transition';
|
||||
import ImageThumbnail from './image-thumbnail.svelte';
|
||||
import VideoThumbnail from './video-thumbnail.svelte';
|
||||
import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
|
||||
import { TUNABLES } from '$lib/utils/tunables';
|
||||
import { thumbhash } from '$lib/actions/thumbhash';
|
||||
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
||||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
@@ -35,16 +35,16 @@
|
||||
focussed?: boolean;
|
||||
selectionCandidate?: boolean;
|
||||
disabled?: boolean;
|
||||
disableLinkMouseOver?: boolean;
|
||||
readonly?: boolean;
|
||||
showArchiveIcon?: boolean;
|
||||
showStackedIcon?: boolean;
|
||||
disableMouseOver?: boolean;
|
||||
|
||||
imageClass?: ClassValue;
|
||||
dimmed?: boolean;
|
||||
onClick?: ((asset: AssetResponseDto) => void) | undefined;
|
||||
onSelect?: ((asset: AssetResponseDto) => void) | undefined;
|
||||
onMouseEvent?: ((event: { isMouseOver: boolean; selectedGroupIndex: number }) => void) | undefined;
|
||||
handleFocus?: (() => void) | undefined;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -57,15 +57,16 @@
|
||||
focussed = false,
|
||||
selectionCandidate = false,
|
||||
disabled = false,
|
||||
disableLinkMouseOver = false,
|
||||
readonly = false,
|
||||
showArchiveIcon = false,
|
||||
showStackedIcon = true,
|
||||
disableMouseOver = false,
|
||||
onClick = undefined,
|
||||
onSelect = undefined,
|
||||
onMouseEvent = undefined,
|
||||
handleFocus = undefined,
|
||||
class: className = '',
|
||||
imageClass = '',
|
||||
dimmed = false,
|
||||
}: Props = $props();
|
||||
|
||||
let {
|
||||
@@ -145,11 +146,12 @@
|
||||
|
||||
<div
|
||||
data-asset={asset.id}
|
||||
class={[
|
||||
'focus-visible:outline-none flex overflow-hidden',
|
||||
disabled ? 'bg-gray-300' : 'bg-immich-primary/20 dark:bg-immich-dark-primary/20',
|
||||
]}
|
||||
style:width="{width}px"
|
||||
style:height="{height}px"
|
||||
class="focus-visible:outline-none flex overflow-hidden {disabled
|
||||
? 'bg-gray-300'
|
||||
: 'bg-immich-primary/20 dark:bg-immich-dark-primary/20'}"
|
||||
>
|
||||
{#if !loaded && asset.thumbhash}
|
||||
<canvas
|
||||
@@ -168,11 +170,9 @@
|
||||
slow: ??ms
|
||||
-->
|
||||
<div
|
||||
class="group"
|
||||
style:width="{width}px"
|
||||
style:height="{height}px"
|
||||
class:cursor-not-allowed={disabled}
|
||||
class:cursor-pointer={!disabled}
|
||||
class={['group absolute top-[0px] bottom-[0px]', { 'curstor-not-allowed': disabled, 'cursor-pointer': !disabled }]}
|
||||
style:width="inherit"
|
||||
style:height="inherit"
|
||||
onmouseenter={onMouseEnter}
|
||||
onmouseleave={onMouseLeave}
|
||||
use:longPress={{ onLongPress: () => onSelect?.($state.snapshot(asset)) }}
|
||||
@@ -184,20 +184,19 @@
|
||||
onSelect?.(asset);
|
||||
}
|
||||
}}
|
||||
tabindex={0}
|
||||
onclick={handleClick}
|
||||
role="link"
|
||||
bind:this={focussableElement}
|
||||
onfocus={handleFocus}
|
||||
data-testid="container-with-tabindex"
|
||||
tabindex={0}
|
||||
role="link"
|
||||
>
|
||||
{#if !usingMobileDevice && mouseOver && !disableMouseOver}
|
||||
<!-- Select asset button -->
|
||||
{#if !usingMobileDevice && mouseOver && !disableLinkMouseOver}
|
||||
<!-- lazy show the url on mouse over-->
|
||||
<a
|
||||
class="absolute z-30 {className} top-[41px]"
|
||||
class={['absolute z-10 w-full top-0 bottom-0']}
|
||||
style:cursor="unset"
|
||||
style:width="{width}px"
|
||||
style:height="{height}px"
|
||||
href={currentUrlReplaceAssetId(asset.id)}
|
||||
onclick={(evt) => evt.preventDefault()}
|
||||
tabindex={-1}
|
||||
@@ -205,87 +204,98 @@
|
||||
>
|
||||
</a>
|
||||
{/if}
|
||||
<div class="absolute z-20 {className}" style:width="{width}px" style:height="{height}px">
|
||||
<!-- Select asset button -->
|
||||
{#if !readonly && (mouseOver || selected || selectionCandidate)}
|
||||
<button
|
||||
type="button"
|
||||
onclick={onIconClickedHandler}
|
||||
class="absolute p-2 focus:outline-none"
|
||||
class:cursor-not-allowed={disabled}
|
||||
role="checkbox"
|
||||
tabindex={-1}
|
||||
onfocus={handleFocus}
|
||||
aria-checked={selected}
|
||||
{disabled}
|
||||
>
|
||||
{#if disabled}
|
||||
<Icon path={mdiCheckCircle} size="24" class="text-zinc-800" />
|
||||
{:else if selected}
|
||||
<div class="rounded-full bg-[#D9DCEF] dark:bg-[#232932]">
|
||||
<Icon path={mdiCheckCircle} size="24" class="text-immich-primary" />
|
||||
</div>
|
||||
{:else}
|
||||
<Icon path={mdiCheckCircle} size="24" class="text-white/80 hover:text-white" />
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if !readonly && (mouseOver || selected || selectionCandidate)}
|
||||
<button
|
||||
type="button"
|
||||
onclick={onIconClickedHandler}
|
||||
class={['absolute z-20 p-2 focus:outline-none', { 'cursor-not-allowed': disabled }]}
|
||||
role="checkbox"
|
||||
tabindex={-1}
|
||||
onfocus={handleFocus}
|
||||
aria-checked={selected}
|
||||
{disabled}
|
||||
>
|
||||
{#if disabled}
|
||||
<Icon path={mdiCheckCircle} size="24" class="text-zinc-800" />
|
||||
{:else if selected}
|
||||
<div class="rounded-full bg-[#D9DCEF] dark:bg-[#232932]">
|
||||
<Icon path={mdiCheckCircle} size="24" class="text-immich-primary" />
|
||||
</div>
|
||||
{:else}
|
||||
<Icon path={mdiCheckCircle} size="24" class="text-white/80 hover:text-white" />
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="absolute h-full w-full select-none bg-transparent transition-transform"
|
||||
class:scale-[0.85]={selected}
|
||||
class:rounded-xl={selected}
|
||||
class={[
|
||||
'absolute h-full w-full select-none bg-transparent transition-transform',
|
||||
{ 'scale-[0.85]': selected },
|
||||
{ 'rounded-xl': selected },
|
||||
]}
|
||||
>
|
||||
<!-- Gradient overlay on hover -->
|
||||
{#if !usingMobileDevice}
|
||||
<!-- icon overlay -->
|
||||
<div>
|
||||
<!-- Gradient overlay on hover -->
|
||||
{#if !usingMobileDevice && !disabled}
|
||||
<div
|
||||
class={[
|
||||
'absolute h-full w-full bg-gradient-to-b from-black/25 via-[transparent_25%] opacity-0 transition-opacity group-hover:opacity-100',
|
||||
{ 'rounded-xl': selected },
|
||||
]}
|
||||
></div>
|
||||
{/if}
|
||||
<!-- Dimmed support -->
|
||||
|
||||
{#if dimmed && !mouseOver}
|
||||
<div id="a" class={['absolute h-full w-full z-30 bg-gray-700/40', { 'rounded-xl': selected }]}></div>
|
||||
{/if}
|
||||
<!-- Outline on focus -->
|
||||
<div
|
||||
class="absolute z-10 h-full w-full bg-gradient-to-b from-black/25 via-[transparent_25%] opacity-0 transition-opacity group-hover:opacity-100"
|
||||
class:rounded-xl={selected}
|
||||
class={[
|
||||
'absolute size-full group-focus-visible:outline outline-4 -outline-offset-4 outline-immich-primary',
|
||||
{ 'rounded-xl': selected },
|
||||
]}
|
||||
></div>
|
||||
{/if}
|
||||
<!-- Outline on focus -->
|
||||
<div
|
||||
class="absolute size-full group-focus-visible:outline outline-4 -outline-offset-4 outline-immich-primary"
|
||||
></div>
|
||||
|
||||
<!-- Favorite asset star -->
|
||||
{#if !isSharedLink() && asset.isFavorite}
|
||||
<div class="absolute bottom-2 left-2 z-10">
|
||||
<Icon path={mdiHeart} size="24" class="text-white" />
|
||||
</div>
|
||||
{/if}
|
||||
<!-- Favorite asset star -->
|
||||
{#if !isSharedLink() && asset.isFavorite}
|
||||
<div class="absolute bottom-2 left-2 z-10">
|
||||
<Icon path={mdiHeart} size="24" class="text-white" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !isSharedLink() && showArchiveIcon && asset.isArchived}
|
||||
<div class="absolute {asset.isFavorite ? 'bottom-10' : 'bottom-2'} left-2 z-10">
|
||||
<Icon path={mdiArchiveArrowDownOutline} size="24" class="text-white" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if !isSharedLink() && showArchiveIcon && asset.isArchived}
|
||||
<div class={['absolute left-2 z-10', asset.isFavorite ? 'bottom-10' : 'bottom-2']}>
|
||||
<Icon path={mdiArchiveArrowDownOutline} size="24" class="text-white" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if asset.type === AssetTypeEnum.Image && asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR}
|
||||
<div class="absolute right-0 top-0 z-20 flex place-items-center gap-1 text-xs font-medium text-white">
|
||||
<span class="pr-2 pt-2">
|
||||
<Icon path={mdiRotate360} size="24" />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Stacked asset -->
|
||||
|
||||
{#if asset.stack && showStackedIcon}
|
||||
<div
|
||||
class="absolute {asset.type == AssetTypeEnum.Image && asset.livePhotoVideoId == undefined
|
||||
? 'top-0 right-0'
|
||||
: 'top-7 right-1'} z-20 flex place-items-center gap-1 text-xs font-medium text-white"
|
||||
>
|
||||
<span class="pr-2 pt-2 flex place-items-center gap-1">
|
||||
<p>{asset.stack.assetCount.toLocaleString($locale)}</p>
|
||||
<Icon path={mdiCameraBurst} size="24" />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if asset.type === AssetTypeEnum.Image && asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR}
|
||||
<div class="absolute right-0 top-0 z-10 flex place-items-center gap-1 text-xs font-medium text-white">
|
||||
<span class="pr-2 pt-2">
|
||||
<Icon path={mdiRotate360} size="24" />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Stacked asset -->
|
||||
{#if asset.stack && showStackedIcon}
|
||||
<div
|
||||
class={[
|
||||
'absolute z-10 flex place-items-center gap-1 text-xs font-medium text-white',
|
||||
asset.type == AssetTypeEnum.Image && !asset.livePhotoVideoId ? 'top-0 right-0' : 'top-7 right-1',
|
||||
]}
|
||||
>
|
||||
<span class="pr-2 pt-2 flex place-items-center gap-1">
|
||||
<p>{asset.stack.assetCount.toLocaleString($locale)}</p>
|
||||
<Icon path={mdiCameraBurst} size="24" />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<ImageThumbnail
|
||||
class={imageClass}
|
||||
url={getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, cacheKey: asset.thumbhash })}
|
||||
altText={$getAltText(asset)}
|
||||
widthStyle="{width}px"
|
||||
@@ -293,7 +303,6 @@
|
||||
curve={selected}
|
||||
onComplete={() => (loaded = true)}
|
||||
/>
|
||||
|
||||
{#if asset.type === AssetTypeEnum.Video}
|
||||
<div class="absolute top-0 h-full w-full">
|
||||
<VideoThumbnail
|
||||
@@ -304,9 +313,7 @@
|
||||
playbackOnIconHover={!$playVideoThumbnailOnHover}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if asset.type === AssetTypeEnum.Image && asset.livePhotoVideoId}
|
||||
{:else if asset.type === AssetTypeEnum.Image && asset.livePhotoVideoId}
|
||||
<div class="absolute top-0 h-full w-full">
|
||||
<VideoThumbnail
|
||||
url={getAssetPlaybackUrl({ id: asset.livePhotoVideoId, cacheKey: asset.thumbhash })}
|
||||
|
||||
@@ -7,10 +7,17 @@
|
||||
* Target for the skip link to move focus to.
|
||||
*/
|
||||
target?: string;
|
||||
/**
|
||||
* Text for the skip link button.
|
||||
*/
|
||||
text?: string;
|
||||
/**
|
||||
* Breakpoint at which the skip link is visible. Defaults to always being visible.
|
||||
*/
|
||||
breakpoint?: 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
||||
}
|
||||
|
||||
let { target = 'main', text = $t('skip_to_content') }: Props = $props();
|
||||
let { target = 'main', text = $t('skip_to_content'), breakpoint }: Props = $props();
|
||||
|
||||
let isFocused = $state(false);
|
||||
|
||||
@@ -18,6 +25,29 @@
|
||||
const targetEl = document.querySelector<HTMLElement>(target);
|
||||
targetEl?.focus();
|
||||
};
|
||||
|
||||
const getBreakpoint = () => {
|
||||
if (!breakpoint) {
|
||||
return '';
|
||||
}
|
||||
switch (breakpoint) {
|
||||
case 'sm': {
|
||||
return 'hidden sm:block';
|
||||
}
|
||||
case 'md': {
|
||||
return 'hidden md:block';
|
||||
}
|
||||
case 'lg': {
|
||||
return 'hidden lg:block';
|
||||
}
|
||||
case 'xl': {
|
||||
return 'hidden xl:block';
|
||||
}
|
||||
case '2xl': {
|
||||
return 'hidden 2xl:block';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="absolute z-50 top-2 left-2 transition-transform {isFocused ? 'translate-y-0' : '-translate-y-10 sr-only'}">
|
||||
@@ -25,6 +55,7 @@
|
||||
size="sm"
|
||||
rounded="none"
|
||||
onclick={moveFocus}
|
||||
class={getBreakpoint()}
|
||||
onfocus={() => (isFocused = true)}
|
||||
onblur={() => (isFocused = false)}
|
||||
>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<input
|
||||
{...rest}
|
||||
{type}
|
||||
{value}
|
||||
bind:value
|
||||
max={max || fallbackMax}
|
||||
oninput={(e) => (updatedValue = e.currentTarget.value)}
|
||||
onblur={() => (value = updatedValue)}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</header>
|
||||
<main
|
||||
tabindex="-1"
|
||||
class="relative grid h-screen grid-cols-[theme(spacing.18)_auto] overflow-hidden bg-immich-bg max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg md:grid-cols-[theme(spacing.64)_auto]"
|
||||
class="relative grid h-screen grid-cols-[theme(spacing.0)_auto] overflow-hidden bg-immich-bg max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg md:grid-cols-[theme(spacing.64)_auto]"
|
||||
>
|
||||
{#if sidebar}{@render sidebar()}{:else if admin}
|
||||
<AdminSideBar />
|
||||
@@ -62,11 +62,11 @@
|
||||
<section class="relative">
|
||||
{#if title || buttons}
|
||||
<div
|
||||
class="absolute flex h-16 w-full place-items-center justify-between border-b p-4 dark:border-immich-dark-gray dark:text-immich-dark-fg"
|
||||
class="absolute flex h-16 w-full place-items-center justify-between border-b p-2 dark:border-immich-dark-gray dark:text-immich-dark-fg"
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
{#if title}
|
||||
<div class="font-medium" tabindex="-1" id={headerId}>{title}</div>
|
||||
<div class="font-medium outline-none" tabindex="-1" id={headerId}>{title}</div>
|
||||
{/if}
|
||||
{#if description}
|
||||
<p class="text-sm text-gray-400 dark:text-gray-600">{description}</p>
|
||||
@@ -76,7 +76,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="{scrollbarClass} scrollbar-stable absolute {hasTitleClass} w-full overflow-y-auto" use:useActions={use}>
|
||||
<div class="{scrollbarClass} absolute {hasTitleClass} w-full overflow-y-auto" use:useActions={use}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<script lang="ts">
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { AssetBucket, assetSnapshot, assetsSnapshot } from '$lib/stores/assets-store.svelte';
|
||||
import {
|
||||
type AssetStore,
|
||||
type AssetBucket,
|
||||
assetSnapshot,
|
||||
assetsSnapshot,
|
||||
isSelectingAllAssets,
|
||||
} from '$lib/stores/assets-store.svelte';
|
||||
import { navigate } from '$lib/utils/navigation';
|
||||
import { getDateLocaleString } from '$lib/utils/timeline-util';
|
||||
import type { AssetResponseDto } from '@immich/sdk';
|
||||
@@ -22,6 +28,7 @@
|
||||
withStacked: boolean;
|
||||
showArchiveIcon: boolean;
|
||||
bucket: AssetBucket;
|
||||
assetStore: AssetStore;
|
||||
assetInteraction: AssetInteraction;
|
||||
|
||||
onSelect: ({ title, assets }: { title: string; assets: AssetResponseDto[] }) => void;
|
||||
@@ -36,6 +43,7 @@
|
||||
showArchiveIcon,
|
||||
bucket = $bindable(),
|
||||
assetInteraction,
|
||||
assetStore,
|
||||
onSelect,
|
||||
onSelectAssets,
|
||||
onSelectAssetCandidates,
|
||||
@@ -46,9 +54,9 @@
|
||||
|
||||
const transitionDuration = $derived.by(() => (bucket.store.suspendTransitions && !$isUploading ? 0 : 150));
|
||||
const scaleDuration = $derived(transitionDuration === 0 ? 0 : transitionDuration + 100);
|
||||
const onClick = (assets: AssetResponseDto[], groupTitle: string, asset: AssetResponseDto) => {
|
||||
const onClick = (assetStore: AssetStore, assets: AssetResponseDto[], groupTitle: string, asset: AssetResponseDto) => {
|
||||
if (isSelectionMode || assetInteraction.selectionActive) {
|
||||
assetSelectHandler(asset, assets, groupTitle);
|
||||
assetSelectHandler(assetStore, asset, assets, groupTitle);
|
||||
return;
|
||||
}
|
||||
void navigate({ targetRoute: 'current', assetId: asset.id });
|
||||
@@ -56,7 +64,12 @@
|
||||
|
||||
const handleSelectGroup = (title: string, assets: AssetResponseDto[]) => onSelect({ title, assets });
|
||||
|
||||
const assetSelectHandler = (asset: AssetResponseDto, assetsInDateGroup: AssetResponseDto[], groupTitle: string) => {
|
||||
const assetSelectHandler = (
|
||||
assetStore: AssetStore,
|
||||
asset: AssetResponseDto,
|
||||
assetsInDateGroup: AssetResponseDto[],
|
||||
groupTitle: string,
|
||||
) => {
|
||||
onSelectAssets(asset);
|
||||
|
||||
// Check if all assets are selected in a group to toggle the group selection's icon
|
||||
@@ -70,6 +83,12 @@
|
||||
} else {
|
||||
assetInteraction.removeGroupFromMultiselectGroup(groupTitle);
|
||||
}
|
||||
|
||||
if (assetStore.getAssets().length == assetInteraction.selectedAssets.length) {
|
||||
isSelectingAllAssets.set(true);
|
||||
} else {
|
||||
isSelectingAllAssets.set(false);
|
||||
}
|
||||
};
|
||||
|
||||
const assetMouseEventHandler = (groupTitle: string, asset: AssetResponseDto | null) => {
|
||||
@@ -164,8 +183,8 @@
|
||||
{asset}
|
||||
{groupIndex}
|
||||
focussed={assetInteraction.isFocussedAsset(asset.id)}
|
||||
onClick={(asset) => onClick(dateGroup.getAssets(), dateGroup.groupTitle, asset)}
|
||||
onSelect={(asset) => assetSelectHandler(asset, dateGroup.getAssets(), dateGroup.groupTitle)}
|
||||
onClick={(asset) => onClick(assetStore, dateGroup.getAssets(), dateGroup.groupTitle, asset)}
|
||||
onSelect={(asset) => assetSelectHandler(assetStore, asset, dateGroup.getAssets(), dateGroup.groupTitle)}
|
||||
onMouseEvent={() => assetMouseEventHandler(dateGroup.groupTitle, assetSnapshot(asset))}
|
||||
selected={assetInteraction.hasSelectedAsset(asset.id) || dateGroup.bucket.store.albumAssets.has(asset.id)}
|
||||
selectionCandidate={assetInteraction.hasSelectionCandidate(asset.id)}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import type { Action } from '$lib/components/asset-viewer/actions/action';
|
||||
import { AppRoute, AssetAction } from '$lib/constants';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { AssetBucket, assetsSnapshot, AssetStore } from '$lib/stores/assets-store.svelte';
|
||||
import { AssetBucket, assetsSnapshot, AssetStore, isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
|
||||
import { showDeleteModal } from '$lib/stores/preferences.store';
|
||||
import { isSearchEnabled } from '$lib/stores/search.store';
|
||||
import { featureFlags } from '$lib/stores/server-config.store';
|
||||
@@ -456,7 +456,7 @@
|
||||
lastAssetMouseEvent = asset;
|
||||
};
|
||||
|
||||
const handleGroupSelect = (group: string, assets: AssetResponseDto[]) => {
|
||||
const handleGroupSelect = (assetStore: AssetStore, group: string, assets: AssetResponseDto[]) => {
|
||||
if (assetInteraction.selectedGroup.has(group)) {
|
||||
assetInteraction.removeGroupFromMultiselectGroup(group);
|
||||
for (const asset of assets) {
|
||||
@@ -468,6 +468,12 @@
|
||||
handleSelectAsset(asset);
|
||||
}
|
||||
}
|
||||
|
||||
if (assetStore.getAssets().length == assetInteraction.selectedAssets.length) {
|
||||
isSelectingAllAssets.set(true);
|
||||
} else {
|
||||
isSelectingAllAssets.set(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectAssets = async (asset: AssetResponseDto) => {
|
||||
@@ -720,7 +726,7 @@
|
||||
class={[
|
||||
'scrollbar-hidden h-full overflow-y-auto outline-none',
|
||||
{ 'm-0': isEmpty },
|
||||
{ 'ml-4 tall:ml-0': !isEmpty },
|
||||
{ 'ml-0': !isEmpty },
|
||||
{ 'mr-[60px]': !isEmpty && !usingMobileDevice },
|
||||
]}
|
||||
tabindex="-1"
|
||||
@@ -774,10 +780,11 @@
|
||||
{withStacked}
|
||||
{showArchiveIcon}
|
||||
{assetInteraction}
|
||||
{assetStore}
|
||||
{isSelectionMode}
|
||||
{singleSelect}
|
||||
{bucket}
|
||||
onSelect={({ title, assets }) => handleGroupSelect(title, assets)}
|
||||
onSelect={({ title, assets }) => handleGroupSelect(assetStore, title, assets)}
|
||||
onSelectAssetCandidates={handleSelectAssetCandidates}
|
||||
onSelectAssets={handleSelectAssets}
|
||||
/>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<section
|
||||
id="memory-lane"
|
||||
bind:this={memoryLaneElement}
|
||||
class="relative mt-5 overflow-x-scroll overflow-y-hidden whitespace-nowrap transition-all"
|
||||
class="relative mt-5 mx-2 overflow-x-scroll overflow-y-hidden whitespace-nowrap transition-all"
|
||||
style="scrollbar-width:none"
|
||||
use:resizeObserver={({ width }) => (offsetWidth = width)}
|
||||
onscroll={onScroll}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<script lang="ts" module>
|
||||
export const menuButtonId = 'top-menu-button';
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { clickOutside } from '$lib/actions/click-outside';
|
||||
@@ -12,13 +16,14 @@
|
||||
import { handleLogout } from '$lib/utils/auth';
|
||||
import { getAboutInfo, logout, type ServerAboutResponseDto } from '@immich/sdk';
|
||||
import { Button, IconButton } from '@immich/ui';
|
||||
import { mdiHelpCircleOutline, mdiMagnify, mdiTrayArrowUp } from '@mdi/js';
|
||||
import { mdiHelpCircleOutline, mdiMagnify, mdiMenu, mdiTrayArrowUp } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
import ThemeButton from '../theme-button.svelte';
|
||||
import UserAvatar from '../user-avatar.svelte';
|
||||
import AccountInfoPanel from './account-info-panel.svelte';
|
||||
import { isSidebarOpen } from '$lib/stores/side-bar.svelte';
|
||||
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
||||
|
||||
interface Props {
|
||||
@@ -57,11 +62,34 @@
|
||||
>
|
||||
<SkipLink text={$t('skip_to_content')} />
|
||||
<div
|
||||
class="grid h-full grid-cols-[theme(spacing.18)_auto] items-center border-b bg-immich-bg py-2 dark:border-b-immich-dark-gray dark:bg-immich-dark-bg md:grid-cols-[theme(spacing.64)_auto]"
|
||||
class="grid h-full grid-cols-[theme(spacing.32)_auto] items-center border-b bg-immich-bg py-2 dark:border-b-immich-dark-gray dark:bg-immich-dark-bg md:grid-cols-[theme(spacing.64)_auto]"
|
||||
>
|
||||
<a data-sveltekit-preload-data="hover" class="ml-4" href={AppRoute.PHOTOS}>
|
||||
<ImmichLogo class="max-md:h-[48px] h-[50px]" noText={mobileDevice.maxMd} />
|
||||
</a>
|
||||
<div class="flex flex-row gap-1 mx-4 items-center">
|
||||
<div>
|
||||
<IconButton
|
||||
id={menuButtonId}
|
||||
shape="round"
|
||||
color="secondary"
|
||||
variant="ghost"
|
||||
size="medium"
|
||||
aria-label={$t('main_menu')}
|
||||
icon={mdiMenu}
|
||||
onclick={() => {
|
||||
isSidebarOpen.value = !isSidebarOpen.value;
|
||||
}}
|
||||
onmousedown={(event: MouseEvent) => {
|
||||
if (isSidebarOpen.value) {
|
||||
// stops event from reaching the default handler when clicking outside of the sidebar
|
||||
event.stopPropagation();
|
||||
}
|
||||
}}
|
||||
class="md:hidden"
|
||||
/>
|
||||
</div>
|
||||
<a data-sveltekit-preload-data="hover" href={AppRoute.PHOTOS}>
|
||||
<ImmichLogo class="max-md:h-[48px] h-[50px]" noText={mobileDevice.maxMd} />
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex justify-between gap-4 lg:gap-8 pr-6">
|
||||
<div class="hidden w-full max-w-5xl flex-1 tall:pl-0 sm:block">
|
||||
{#if $featureFlags.search}
|
||||
@@ -80,7 +108,6 @@
|
||||
href={AppRoute.SEARCH}
|
||||
id="search-button"
|
||||
class="sm:hidden"
|
||||
title={$t('go_to_search')}
|
||||
aria-label={$t('go_to_search')}
|
||||
/>
|
||||
{/if}
|
||||
@@ -120,7 +147,6 @@
|
||||
color="secondary"
|
||||
variant="ghost"
|
||||
size="medium"
|
||||
title={$t('support_and_feedback')}
|
||||
icon={mdiHelpCircleOutline}
|
||||
onclick={() => (shouldShowHelpPanel = !shouldShowHelpPanel)}
|
||||
aria-label={$t('support_and_feedback')}
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
</script>
|
||||
|
||||
<!-- Individual Purchase Option -->
|
||||
<div class="border border-gray-300 dark:border-gray-800 w-[375px] p-8 rounded-3xl bg-gray-100 dark:bg-gray-900">
|
||||
<div
|
||||
class="border border-gray-300 dark:border-gray-800 w-[min(375px,100%)] p-8 rounded-3xl bg-gray-100 dark:bg-gray-900"
|
||||
>
|
||||
<div class="text-immich-primary dark:text-immich-dark-primary">
|
||||
<Icon path={mdiAccount} size="56" />
|
||||
<p class="font-semibold text-lg mt-1">{$t('purchase_individual_title')}</p>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex gap-6 mt-4 justify-between">
|
||||
<div class="flex flex-col sm:flex-row gap-6 mt-4 justify-between">
|
||||
<ServerPurchaseOptionCard />
|
||||
<UserPurchaseOptionCard />
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
</script>
|
||||
|
||||
<!-- SERVER Purchase Options -->
|
||||
<div class="border border-gray-300 dark:border-gray-800 w-[375px] p-8 rounded-3xl bg-gray-100 dark:bg-gray-900">
|
||||
<div
|
||||
class="border border-gray-300 dark:border-gray-800 w-[min(375px,100%)] p-8 rounded-3xl bg-gray-100 dark:bg-gray-900"
|
||||
>
|
||||
<div class="text-immich-primary dark:text-immich-dark-primary">
|
||||
<Icon path={mdiServer} size="56" />
|
||||
<p class="font-semibold text-lg mt-1">{$t('purchase_server_title')}</p>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<LicenseModal onClose={() => (isOpen = false)} />
|
||||
{/if}
|
||||
|
||||
<div class="hidden md:block license-status pl-4 text-sm">
|
||||
<div class="license-status pl-4 text-sm">
|
||||
{#if $isPurchased && $preferences.purchase.showSupportBadge}
|
||||
<button
|
||||
onclick={() => goto(`${AppRoute.USER_SETTINGS}?isOpen=user-purchase-settings`)}
|
||||
@@ -95,7 +95,7 @@
|
||||
onmouseleave={() => (hoverButton = false)}
|
||||
onfocus={onButtonHover}
|
||||
onblur={() => (hoverButton = false)}
|
||||
class="p-2 flex justify-between place-items-center place-content-center border border-immich-primary/20 dark:border-immich-dark-primary/10 mt-2 rounded-lg shadow-md dark:bg-immich-dark-primary/10 w-full"
|
||||
class="p-2 flex justify-between place-items-center place-content-center border border-immich-primary/20 dark:border-immich-dark-primary/10 mt-2 rounded-lg shadow-md dark:bg-immich-dark-primary/10 min-w-52 w-full"
|
||||
>
|
||||
<div class="flex justify-between w-full place-items-center place-content-center">
|
||||
<div class="flex place-items-center place-content-center gap-1">
|
||||
@@ -110,7 +110,7 @@
|
||||
<div>
|
||||
<Icon
|
||||
path={mdiInformationOutline}
|
||||
class="flex text-immich-primary dark:text-immich-dark-primary font-medium"
|
||||
class="hidden md:flex text-immich-primary dark:text-immich-dark-primary font-medium"
|
||||
size="18"
|
||||
/>
|
||||
</div>
|
||||
@@ -123,7 +123,7 @@
|
||||
{#if showMessage}
|
||||
<dialog
|
||||
open
|
||||
class="w-[500px] absolute bottom-[75px] left-[255px] bg-gray-50 dark:border-gray-800 border border-gray-200 dark:bg-immich-dark-gray dark:text-white text-black rounded-3xl z-10 shadow-2xl px-8 py-6"
|
||||
class="hidden md:block w-[500px] absolute bottom-[75px] left-[255px] bg-gray-50 dark:border-gray-800 border border-gray-200 dark:bg-immich-dark-gray dark:text-white text-black rounded-3xl z-10 shadow-2xl px-8 py-6"
|
||||
transition:fade={{ duration: 150 }}
|
||||
onmouseover={() => (hoverMessage = true)}
|
||||
onmouseleave={() => (hoverMessage = false)}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="text-sm hidden group-hover:sm:flex md:flex pl-5 pr-1 place-items-center place-content-center justify-between"
|
||||
class="text-sm flex md:flex pl-5 pr-1 place-items-center place-content-center justify-between min-w-52 overflow-hidden"
|
||||
>
|
||||
{#if $connected}
|
||||
<div class="flex gap-2 place-items-center place-content-center">
|
||||
|
||||
@@ -62,11 +62,9 @@
|
||||
class="flex w-full place-items-center gap-4 rounded-r-full py-3 transition-[padding] delay-100 duration-100 hover:cursor-pointer hover:bg-immich-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:bg-immich-dark-gray dark:hover:text-immich-dark-primary
|
||||
{isSelected
|
||||
? 'bg-immich-primary/10 text-immich-primary hover:bg-immich-primary/10 dark:bg-immich-dark-primary/10 dark:text-immich-dark-primary'
|
||||
: ''}
|
||||
pl-5 group-hover:sm:px-5 md:px-5
|
||||
"
|
||||
: ''}"
|
||||
>
|
||||
<div class="flex w-full place-items-center gap-4 overflow-hidden truncate">
|
||||
<div class="flex w-full place-items-center gap-4 pl-5 overflow-hidden truncate">
|
||||
<Icon path={icon} size="1.5em" class="shrink-0" flipped={flippedLogo} ariaHidden />
|
||||
<span class="text-sm font-medium">{title}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,56 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import { clickOutside } from '$lib/actions/click-outside';
|
||||
import { focusTrap } from '$lib/actions/focus-trap';
|
||||
import { menuButtonId } from '$lib/components/shared-components/navigation-bar/navigation-bar.svelte';
|
||||
import { isSidebarOpen } from '$lib/stores/side-bar.svelte';
|
||||
import { type Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
const mdBreakpoint = 768;
|
||||
|
||||
let { children }: Props = $props();
|
||||
|
||||
let innerWidth: number = $state(0);
|
||||
|
||||
const closeSidebar = (width: number) => {
|
||||
isSidebarOpen.value = width >= mdBreakpoint;
|
||||
};
|
||||
|
||||
$effect(() => {
|
||||
closeSidebar(innerWidth);
|
||||
});
|
||||
|
||||
const isHidden = $derived(!isSidebarOpen.value && innerWidth < mdBreakpoint);
|
||||
const isExpanded = $derived(isSidebarOpen.value && innerWidth < mdBreakpoint);
|
||||
|
||||
const handleClickOutside = () => {
|
||||
if (!isSidebarOpen.value) {
|
||||
return;
|
||||
}
|
||||
closeSidebar(innerWidth);
|
||||
if (isHidden) {
|
||||
document.querySelector<HTMLButtonElement>(`#${menuButtonId}`)?.focus();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth />
|
||||
<section
|
||||
id="sidebar"
|
||||
tabindex="-1"
|
||||
class="immich-scrollbar group relative z-10 flex w-18 flex-col gap-1 overflow-y-auto bg-immich-bg pt-8 max-md:pt-16 transition-all duration-200 dark:bg-immich-dark-bg hover:sm:w-64 hover:sm:border-r hover:sm:pr-6 hover:sm:shadow-2xl hover:sm:dark:border-r-immich-dark-gray md:w-64 md:pr-6 hover:md:border-none hover:md:shadow-none"
|
||||
class="immich-scrollbar relative z-10 w-0 md:w-[16rem] overflow-y-auto overflow-x-hidden bg-immich-bg pt-8 transition-all duration-200 dark:bg-immich-dark-bg"
|
||||
class:shadow-2xl={isExpanded}
|
||||
class:dark:border-r-immich-dark-gray={isExpanded}
|
||||
class:border-r={isExpanded}
|
||||
class:w-[min(100vw,16rem)]={isSidebarOpen.value}
|
||||
inert={isHidden}
|
||||
use:clickOutside={{ onOutclick: handleClickOutside, onEscape: handleClickOutside }}
|
||||
use:focusTrap={{ active: isExpanded }}
|
||||
>
|
||||
{@render children?.()}
|
||||
<div class="pr-6 flex flex-col gap-1 h-max min-h-full">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -84,10 +84,7 @@
|
||||
bind:isSelected={isSharingSelected}
|
||||
></SideBarLink>
|
||||
|
||||
<div class="text-xs transition-all duration-200 dark:text-immich-dark-fg">
|
||||
<p class="hidden p-6 group-hover:sm:block md:block">{$t('library').toUpperCase()}</p>
|
||||
<hr class="mx-4 mb-[31px] mt-8 block group-hover:sm:hidden md:hidden" />
|
||||
</div>
|
||||
<p class="text-xs p-6 dark:text-immich-dark-fg">{$t('library').toUpperCase()}</p>
|
||||
|
||||
<SideBarLink
|
||||
title={$t('favorites')}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="hidden md:block storage-status p-4 bg-gray-100 dark:bg-immich-dark-primary/10 ml-4 rounded-lg text-sm"
|
||||
class="storage-status p-4 bg-gray-100 dark:bg-immich-dark-primary/10 ml-4 rounded-lg text-sm min-w-52"
|
||||
title={$t('storage_usage', {
|
||||
values: {
|
||||
used: getByteUnitString(usedBytes, $locale, 3),
|
||||
@@ -54,26 +54,24 @@
|
||||
},
|
||||
})}
|
||||
>
|
||||
<div class="hidden group-hover:sm:block md:block">
|
||||
<p class="font-medium text-immich-dark-gray dark:text-white mb-2">{$t('storage')}</p>
|
||||
<p class="font-medium text-immich-dark-gray dark:text-white mb-2">{$t('storage')}</p>
|
||||
|
||||
{#if userInteraction.serverInfo}
|
||||
<p class="text-gray-500 dark:text-gray-300">
|
||||
{$t('storage_usage', {
|
||||
values: {
|
||||
used: getByteUnitString(usedBytes, $locale),
|
||||
available: getByteUnitString(availableBytes, $locale),
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
{#if userInteraction.serverInfo}
|
||||
<p class="text-gray-500 dark:text-gray-300">
|
||||
{$t('storage_usage', {
|
||||
values: {
|
||||
used: getByteUnitString(usedBytes, $locale),
|
||||
available: getByteUnitString(availableBytes, $locale),
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
|
||||
<div class="mt-4 h-[7px] w-full rounded-full bg-gray-200 dark:bg-gray-700">
|
||||
<div class="h-[7px] rounded-full {usageClasses}" style="width: {usedPercentage}%"></div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="mt-2">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mt-4 h-[7px] w-full rounded-full bg-gray-200 dark:bg-gray-700">
|
||||
<div class="h-[7px] rounded-full {usageClasses}" style="width: {usedPercentage}%"></div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="mt-2">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
href={getLink(path)}
|
||||
title={value}
|
||||
class={`flex flex-grow place-items-center pl-2 py-1 text-sm rounded-lg hover:bg-slate-200 dark:hover:bg-slate-800 hover:font-semibold ${isTarget ? 'bg-slate-100 dark:bg-slate-700 font-semibold text-immich-primary dark:text-immich-dark-primary' : 'dark:text-gray-200'}`}
|
||||
data-sveltekit-keepfocus
|
||||
>
|
||||
<button type="button" {onclick} class={Object.values(tree).length === 0 ? 'invisible' : ''}>
|
||||
<Icon path={isOpen ? mdiChevronDown : mdiChevronRight} class="text-gray-400" size={20} />
|
||||
|
||||
@@ -38,7 +38,7 @@ export const colorTheme = persisted<ThemeSetting>('color-theme', initialTheme, {
|
||||
// Locale to use for formatting dates, numbers, etc.
|
||||
export const locale = persisted<string | undefined>('locale', undefined, {
|
||||
serializer: {
|
||||
parse: (text) => text,
|
||||
parse: (text) => (text == '' ? 'en-US' : text),
|
||||
stringify: (object) => object ?? '',
|
||||
},
|
||||
});
|
||||
|
||||
1
web/src/lib/stores/side-bar.svelte.ts
Normal file
1
web/src/lib/stores/side-bar.svelte.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const isSidebarOpen = $state({ value: false });
|
||||
@@ -486,6 +486,10 @@ export const selectAllAssets = async (assetStore: AssetStore, assetInteraction:
|
||||
break; // Cancelled
|
||||
}
|
||||
assetInteraction.selectAssets(assetsSnapshot(bucket.getAssets()));
|
||||
|
||||
for (const dateGroup of bucket.dateGroups) {
|
||||
assetInteraction.addGroupToMultiselectGroup(dateGroup.groupTitle);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const $t = get(t);
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
<UserPageLayout title={data.meta.title}>
|
||||
{#snippet sidebar()}
|
||||
<SideBarSection>
|
||||
<SkipLink target={`#${headerId}`} text={$t('skip_to_folders')} />
|
||||
<SkipLink target={`#${headerId}`} text={$t('skip_to_folders')} breakpoint="md" />
|
||||
<section>
|
||||
<div class="text-xs pl-4 mb-2 dark:text-white">{$t('explorer').toUpperCase()}</div>
|
||||
<div class="h-full">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user