diff --git a/cli/package-lock.json b/cli/package-lock.json
index ef6788e7d6..abab734dd8 100644
--- a/cli/package-lock.json
+++ b/cli/package-lock.json
@@ -1012,9 +1012,9 @@
}
},
"node_modules/@pkgr/core": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.0.tgz",
- "integrity": "sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==",
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.1.tgz",
+ "integrity": "sha512-VzgHzGblFmUeBmmrk55zPyrQIArQN4vujc9shWytaPdB3P7qhi0cpaiKIr7tlCmFv2lYUwnLospIqjL9ZSAhhg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2297,14 +2297,14 @@
}
},
"node_modules/eslint-plugin-prettier": {
- "version": "5.2.5",
- "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.5.tgz",
- "integrity": "sha512-IKKP8R87pJyMl7WWamLgPkloB16dagPIdd2FjBDbyRYPKo93wS/NbCOPh6gH+ieNLC+XZrhJt/kWj0PS/DFdmg==",
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz",
+ "integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
- "synckit": "^0.10.2"
+ "synckit": "^0.11.0"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -3884,20 +3884,20 @@
}
},
"node_modules/synckit": {
- "version": "0.10.3",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.10.3.tgz",
- "integrity": "sha512-R1urvuyiTaWfeCggqEvpDJwAlDVdsT9NM+IP//Tk2x7qHCkSvBk/fwFgw/TLAHzZlrAnnazMcRw0ZD8HlYFTEQ==",
+ "version": "0.11.3",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.3.tgz",
+ "integrity": "sha512-szhWDqNNI9etJUvbZ1/cx1StnZx8yMmFxme48SwR4dty4ioSY50KEZlpv0qAfgc1fpRzuh9hBXEzoCpJ779dLg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@pkgr/core": "^0.2.0",
+ "@pkgr/core": "^0.2.1",
"tslib": "^2.8.1"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
- "url": "https://opencollective.com/unts"
+ "url": "https://opencollective.com/synckit"
}
},
"node_modules/test-exclude": {
diff --git a/e2e/package-lock.json b/e2e/package-lock.json
index c403b2560a..757dc2eaaa 100644
--- a/e2e/package-lock.json
+++ b/e2e/package-lock.json
@@ -1088,9 +1088,9 @@
}
},
"node_modules/@pkgr/core": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.0.tgz",
- "integrity": "sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==",
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.1.tgz",
+ "integrity": "sha512-VzgHzGblFmUeBmmrk55zPyrQIArQN4vujc9shWytaPdB3P7qhi0cpaiKIr7tlCmFv2lYUwnLospIqjL9ZSAhhg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1566,9 +1566,9 @@
}
},
"node_modules/@types/luxon": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.0.tgz",
- "integrity": "sha512-RtEj20xRyG7cRp142MkQpV3GRF8Wo2MtDkKLz65MQs7rM1Lh8bz+HtfPXCCJEYpnDFu6VwAq/Iv2Ikyp9Jw/hw==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz",
+ "integrity": "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==",
"dev": true,
"license": "MIT"
},
@@ -3094,14 +3094,14 @@
}
},
"node_modules/eslint-plugin-prettier": {
- "version": "5.2.5",
- "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.5.tgz",
- "integrity": "sha512-IKKP8R87pJyMl7WWamLgPkloB16dagPIdd2FjBDbyRYPKo93wS/NbCOPh6gH+ieNLC+XZrhJt/kWj0PS/DFdmg==",
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz",
+ "integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
- "synckit": "^0.10.2"
+ "synckit": "^0.11.0"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -6039,20 +6039,20 @@
}
},
"node_modules/synckit": {
- "version": "0.10.3",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.10.3.tgz",
- "integrity": "sha512-R1urvuyiTaWfeCggqEvpDJwAlDVdsT9NM+IP//Tk2x7qHCkSvBk/fwFgw/TLAHzZlrAnnazMcRw0ZD8HlYFTEQ==",
+ "version": "0.11.3",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.3.tgz",
+ "integrity": "sha512-szhWDqNNI9etJUvbZ1/cx1StnZx8yMmFxme48SwR4dty4ioSY50KEZlpv0qAfgc1fpRzuh9hBXEzoCpJ779dLg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@pkgr/core": "^0.2.0",
+ "@pkgr/core": "^0.2.1",
"tslib": "^2.8.1"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
- "url": "https://opencollective.com/unts"
+ "url": "https://opencollective.com/synckit"
}
},
"node_modules/tar": {
diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile
index bde73028f8..25f3c44d9e 100644
--- a/machine-learning/Dockerfile
+++ b/machine-learning/Dockerfile
@@ -1,6 +1,6 @@
ARG DEVICE=cpu
-FROM python:3.11-bookworm@sha256:ebfa8696e47a68cffebb31e370a93ce57c01bc753f246ceaaef72801d1661351 AS builder-cpu
+FROM python:3.11-bookworm@sha256:0a9d314ae6e976351bd37b702bf6b0a89bb58e6304e5df35b960059b12531419 AS builder-cpu
FROM builder-cpu AS builder-openvino
@@ -54,7 +54,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
RUN apt-get update && apt-get install -y --no-install-recommends g++
-COPY --from=ghcr.io/astral-sh/uv:latest@sha256:fb91e82e8643382d5bce074ba0d167677d678faff4bd518dac670476d19b159c /uv /uvx /bin/
+COPY --from=ghcr.io/astral-sh/uv:latest@sha256:0b6dc79013b689f3bc0cbf12807cb1c901beaafe80f2ee10a1d76aa3842afb92 /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 \
@@ -63,11 +63,11 @@ RUN if [ "$DEVICE" = "rocm" ]; then \
uv pip install /opt/onnxruntime_rocm-*.whl; \
fi
-FROM python:3.11-slim-bookworm@sha256:7029b00486ac40bed03e36775b864d3f3d39dcbdf19cd45e6a52d541e6c178f0 AS prod-cpu
+FROM python:3.11-slim-bookworm@sha256:49d73c49616929b0a4f37c50fee0056eb4b0f15de624591e8d9bf84b4dfdd3ce AS prod-cpu
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2
-FROM python:3.11-slim-bookworm@sha256:7029b00486ac40bed03e36775b864d3f3d39dcbdf19cd45e6a52d541e6c178f0 AS prod-openvino
+FROM python:3.11-slim-bookworm@sha256:49d73c49616929b0a4f37c50fee0056eb4b0f15de624591e8d9bf84b4dfdd3ce AS prod-openvino
RUN apt-get update && \
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
diff --git a/machine-learning/uv.lock b/machine-learning/uv.lock
index 0894c74ecf..339e874cc9 100644
--- a/machine-learning/uv.lock
+++ b/machine-learning/uv.lock
@@ -876,7 +876,7 @@ wheels = [
[[package]]
name = "huggingface-hub"
-version = "0.29.3"
+version = "0.30.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "filelock" },
@@ -887,9 +887,9 @@ dependencies = [
{ name = "tqdm" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/e5/f9/851f34b02970e8143d41d4001b2d49e54ef113f273902103823b8bc95ada/huggingface_hub-0.29.3.tar.gz", hash = "sha256:64519a25716e0ba382ba2d3fb3ca082e7c7eb4a2fc634d200e8380006e0760e5", size = 390123 }
+sdist = { url = "https://files.pythonhosted.org/packages/df/22/8eb91736b1dcb83d879bd49050a09df29a57cc5cd9f38e48a4b1c45ee890/huggingface_hub-0.30.2.tar.gz", hash = "sha256:9a7897c5b6fd9dad3168a794a8998d6378210f5b9688d0dfc180b1a228dc2466", size = 400868 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/40/0c/37d380846a2e5c9a3c6a73d26ffbcfdcad5fc3eacf42fdf7cff56f2af634/huggingface_hub-0.29.3-py3-none-any.whl", hash = "sha256:0b25710932ac649c08cdbefa6c6ccb8e88eef82927cacdb048efb726429453aa", size = 468997 },
+ { url = "https://files.pythonhosted.org/packages/93/27/1fb384a841e9661faad1c31cbfa62864f59632e876df5d795234da51c395/huggingface_hub-0.30.2-py3-none-any.whl", hash = "sha256:68ff05969927058cfa41df4f2155d4bb48f5f54f719dd0390103eefa9b191e28", size = 481433 },
]
[[package]]
@@ -1789,7 +1789,7 @@ wheels = [
[[package]]
name = "pydantic"
-version = "2.11.1"
+version = "2.11.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
@@ -1797,96 +1797,96 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/93/a3/698b87a4d4d303d7c5f62ea5fbf7a79cab236ccfbd0a17847b7f77f8163e/pydantic-2.11.1.tar.gz", hash = "sha256:442557d2910e75c991c39f4b4ab18963d57b9b55122c8b2a9cd176d8c29ce968", size = 782817 }
+sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/cc/12/f9221a949f2419e2e23847303c002476c26fbcfd62dc7f3d25d0bec5ca99/pydantic-2.11.1-py3-none-any.whl", hash = "sha256:5b6c415eee9f8123a14d859be0c84363fec6b1feb6b688d6435801230b56e0b8", size = 442648 },
+ { url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591 },
]
[[package]]
name = "pydantic-core"
-version = "2.33.0"
+version = "2.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/b9/05/91ce14dfd5a3a99555fce436318cc0fd1f08c4daa32b3248ad63669ea8b4/pydantic_core-2.33.0.tar.gz", hash = "sha256:40eb8af662ba409c3cbf4a8150ad32ae73514cd7cb1f1a2113af39763dd616b3", size = 434080 }
+sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 }
wheels = [
- { 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 },
+ { url = "https://files.pythonhosted.org/packages/38/ea/5f572806ab4d4223d11551af814d243b0e3e02cc6913def4d1fe4a5ca41c/pydantic_core-2.33.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3077cfdb6125cc8dab61b155fdd714663e401f0e6883f9632118ec12cf42df26", size = 2044021 },
+ { url = "https://files.pythonhosted.org/packages/8c/d1/f86cc96d2aa80e3881140d16d12ef2b491223f90b28b9a911346c04ac359/pydantic_core-2.33.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ffab8b2908d152e74862d276cf5017c81a2f3719f14e8e3e8d6b83fda863927", size = 1861742 },
+ { url = "https://files.pythonhosted.org/packages/37/08/fbd2cd1e9fc735a0df0142fac41c114ad9602d1c004aea340169ae90973b/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5183e4f6a2d468787243ebcd70cf4098c247e60d73fb7d68d5bc1e1beaa0c4db", size = 1910414 },
+ { url = "https://files.pythonhosted.org/packages/7f/73/3ac217751decbf8d6cb9443cec9b9eb0130eeada6ae56403e11b486e277e/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:398a38d323f37714023be1e0285765f0a27243a8b1506b7b7de87b647b517e48", size = 1996848 },
+ { url = "https://files.pythonhosted.org/packages/9a/f5/5c26b265cdcff2661e2520d2d1e9db72d117ea00eb41e00a76efe68cb009/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d3776f0001b43acebfa86f8c64019c043b55cc5a6a2e313d728b5c95b46969", size = 2141055 },
+ { url = "https://files.pythonhosted.org/packages/5d/14/a9c3cee817ef2f8347c5ce0713e91867a0dceceefcb2973942855c917379/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c566dd9c5f63d22226409553531f89de0cac55397f2ab8d97d6f06cfce6d947e", size = 2753806 },
+ { url = "https://files.pythonhosted.org/packages/f2/68/866ce83a51dd37e7c604ce0050ff6ad26de65a7799df89f4db87dd93d1d6/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d5f3acc81452c56895e90643a625302bd6be351e7010664151cc55b7b97f89", size = 2007777 },
+ { url = "https://files.pythonhosted.org/packages/b6/a8/36771f4404bb3e49bd6d4344da4dede0bf89cc1e01f3b723c47248a3761c/pydantic_core-2.33.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3a07fadec2a13274a8d861d3d37c61e97a816beae717efccaa4b36dfcaadcde", size = 2122803 },
+ { url = "https://files.pythonhosted.org/packages/18/9c/730a09b2694aa89360d20756369822d98dc2f31b717c21df33b64ffd1f50/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f99aeda58dce827f76963ee87a0ebe75e648c72ff9ba1174a253f6744f518f65", size = 2086755 },
+ { url = "https://files.pythonhosted.org/packages/54/8e/2dccd89602b5ec31d1c58138d02340ecb2ebb8c2cac3cc66b65ce3edb6ce/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:902dbc832141aa0ec374f4310f1e4e7febeebc3256f00dc359a9ac3f264a45dc", size = 2257358 },
+ { url = "https://files.pythonhosted.org/packages/d1/9c/126e4ac1bfad8a95a9837acdd0963695d69264179ba4ede8b8c40d741702/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fe44d56aa0b00d66640aa84a3cbe80b7a3ccdc6f0b1ca71090696a6d4777c091", size = 2257916 },
+ { url = "https://files.pythonhosted.org/packages/7d/ba/91eea2047e681a6853c81c20aeca9dcdaa5402ccb7404a2097c2adf9d038/pydantic_core-2.33.1-cp310-cp310-win32.whl", hash = "sha256:ed3eb16d51257c763539bde21e011092f127a2202692afaeaccb50db55a31383", size = 1923823 },
+ { url = "https://files.pythonhosted.org/packages/94/c0/fcdf739bf60d836a38811476f6ecd50374880b01e3014318b6e809ddfd52/pydantic_core-2.33.1-cp310-cp310-win_amd64.whl", hash = "sha256:694ad99a7f6718c1a498dc170ca430687a39894a60327f548e02a9c7ee4b6504", size = 1952494 },
+ { url = "https://files.pythonhosted.org/packages/d6/7f/c6298830cb780c46b4f46bb24298d01019ffa4d21769f39b908cd14bbd50/pydantic_core-2.33.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e966fc3caaf9f1d96b349b0341c70c8d6573bf1bac7261f7b0ba88f96c56c24", size = 2044224 },
+ { url = "https://files.pythonhosted.org/packages/a8/65/6ab3a536776cad5343f625245bd38165d6663256ad43f3a200e5936afd6c/pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfd0adeee563d59c598ceabddf2c92eec77abcb3f4a391b19aa7366170bd9e30", size = 1858845 },
+ { url = "https://files.pythonhosted.org/packages/e9/15/9a22fd26ba5ee8c669d4b8c9c244238e940cd5d818649603ca81d1c69861/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91815221101ad3c6b507804178a7bb5cb7b2ead9ecd600041669c8d805ebd595", size = 1910029 },
+ { url = "https://files.pythonhosted.org/packages/d5/33/8cb1a62818974045086f55f604044bf35b9342900318f9a2a029a1bec460/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fea9c1869bb4742d174a57b4700c6dadea951df8b06de40c2fedb4f02931c2e", size = 1997784 },
+ { url = "https://files.pythonhosted.org/packages/c0/ca/49958e4df7715c71773e1ea5be1c74544923d10319173264e6db122543f9/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d20eb4861329bb2484c021b9d9a977566ab16d84000a57e28061151c62b349a", size = 2141075 },
+ { url = "https://files.pythonhosted.org/packages/7b/a6/0b3a167a9773c79ba834b959b4e18c3ae9216b8319bd8422792abc8a41b1/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb935c5591573ae3201640579f30128ccc10739b45663f93c06796854405505", size = 2745849 },
+ { url = "https://files.pythonhosted.org/packages/0b/60/516484135173aa9e5861d7a0663dce82e4746d2e7f803627d8c25dfa5578/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c964fd24e6166420d18fb53996d8c9fd6eac9bf5ae3ec3d03015be4414ce497f", size = 2005794 },
+ { url = "https://files.pythonhosted.org/packages/86/70/05b1eb77459ad47de00cf78ee003016da0cedf8b9170260488d7c21e9181/pydantic_core-2.33.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:681d65e9011f7392db5aa002b7423cc442d6a673c635668c227c6c8d0e5a4f77", size = 2123237 },
+ { url = "https://files.pythonhosted.org/packages/c7/57/12667a1409c04ae7dc95d3b43158948eb0368e9c790be8b095cb60611459/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e100c52f7355a48413e2999bfb4e139d2977a904495441b374f3d4fb4a170961", size = 2086351 },
+ { url = "https://files.pythonhosted.org/packages/57/61/cc6d1d1c1664b58fdd6ecc64c84366c34ec9b606aeb66cafab6f4088974c/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:048831bd363490be79acdd3232f74a0e9951b11b2b4cc058aeb72b22fdc3abe1", size = 2258914 },
+ { url = "https://files.pythonhosted.org/packages/d1/0a/edb137176a1f5419b2ddee8bde6a0a548cfa3c74f657f63e56232df8de88/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bdc84017d28459c00db6f918a7272a5190bec3090058334e43a76afb279eac7c", size = 2257385 },
+ { url = "https://files.pythonhosted.org/packages/26/3c/48ca982d50e4b0e1d9954919c887bdc1c2b462801bf408613ccc641b3daa/pydantic_core-2.33.1-cp311-cp311-win32.whl", hash = "sha256:32cd11c5914d1179df70406427097c7dcde19fddf1418c787540f4b730289896", size = 1923765 },
+ { url = "https://files.pythonhosted.org/packages/33/cd/7ab70b99e5e21559f5de38a0928ea84e6f23fdef2b0d16a6feaf942b003c/pydantic_core-2.33.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ea62419ba8c397e7da28a9170a16219d310d2cf4970dbc65c32faf20d828c83", size = 1950688 },
+ { url = "https://files.pythonhosted.org/packages/4b/ae/db1fc237b82e2cacd379f63e3335748ab88b5adde98bf7544a1b1bd10a84/pydantic_core-2.33.1-cp311-cp311-win_arm64.whl", hash = "sha256:fc903512177361e868bc1f5b80ac8c8a6e05fcdd574a5fb5ffeac5a9982b9e89", size = 1908185 },
+ { url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640 },
+ { url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649 },
+ { url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472 },
+ { url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509 },
+ { url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702 },
+ { url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428 },
+ { url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753 },
+ { url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849 },
+ { url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541 },
+ { url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225 },
+ { url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373 },
+ { url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034 },
+ { url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848 },
+ { url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986 },
+ { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 },
+ { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 },
+ { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 },
+ { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 },
+ { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 },
+ { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 },
+ { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 },
+ { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 },
+ { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 },
+ { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 },
+ { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 },
+ { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 },
+ { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 },
+ { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 },
+ { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 },
+ { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 },
+ { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 },
+ { url = "https://files.pythonhosted.org/packages/9c/c7/8b311d5adb0fe00a93ee9b4e92a02b0ec08510e9838885ef781ccbb20604/pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c834f54f8f4640fd7e4b193f80eb25a0602bba9e19b3cd2fc7ffe8199f5ae02", size = 2041659 },
+ { url = "https://files.pythonhosted.org/packages/8a/d6/4f58d32066a9e26530daaf9adc6664b01875ae0691570094968aaa7b8fcc/pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:049e0de24cf23766f12cc5cc71d8abc07d4a9deb9061b334b62093dedc7cb068", size = 1873294 },
+ { url = "https://files.pythonhosted.org/packages/f7/3f/53cc9c45d9229da427909c751f8ed2bf422414f7664ea4dde2d004f596ba/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a28239037b3d6f16916a4c831a5a0eadf856bdd6d2e92c10a0da3a59eadcf3e", size = 1903771 },
+ { url = "https://files.pythonhosted.org/packages/f0/49/bf0783279ce674eb9903fb9ae43f6c614cb2f1c4951370258823f795368b/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d3da303ab5f378a268fa7d45f37d7d85c3ec19769f28d2cc0c61826a8de21fe", size = 2083558 },
+ { url = "https://files.pythonhosted.org/packages/9c/5b/0d998367687f986c7d8484a2c476d30f07bf5b8b1477649a6092bd4c540e/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25626fb37b3c543818c14821afe0fd3830bc327a43953bc88db924b68c5723f1", size = 2118038 },
+ { url = "https://files.pythonhosted.org/packages/b3/33/039287d410230ee125daee57373ac01940d3030d18dba1c29cd3089dc3ca/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3ab2d36e20fbfcce8f02d73c33a8a7362980cff717926bbae030b93ae46b56c7", size = 2079315 },
+ { url = "https://files.pythonhosted.org/packages/1f/85/6d8b2646d99c062d7da2d0ab2faeb0d6ca9cca4c02da6076376042a20da3/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2f9284e11c751b003fd4215ad92d325d92c9cb19ee6729ebd87e3250072cdcde", size = 2249063 },
+ { url = "https://files.pythonhosted.org/packages/17/d7/c37d208d5738f7b9ad8f22ae8a727d88ebf9c16c04ed2475122cc3f7224a/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:048c01eee07d37cbd066fc512b9d8b5ea88ceeb4e629ab94b3e56965ad655add", size = 2254631 },
+ { url = "https://files.pythonhosted.org/packages/13/e0/bafa46476d328e4553b85ab9b2f7409e7aaef0ce4c937c894821c542d347/pydantic_core-2.33.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5ccd429694cf26af7997595d627dd2637e7932214486f55b8a357edaac9dae8c", size = 2080877 },
+ { url = "https://files.pythonhosted.org/packages/0b/76/1794e440c1801ed35415238d2c728f26cd12695df9057154ad768b7b991c/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a371dc00282c4b84246509a5ddc808e61b9864aa1eae9ecc92bb1268b82db4a", size = 2042858 },
+ { url = "https://files.pythonhosted.org/packages/73/b4/9cd7b081fb0b1b4f8150507cd59d27b275c3e22ad60b35cb19ea0977d9b9/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f59295ecc75a1788af8ba92f2e8c6eeaa5a94c22fc4d151e8d9638814f85c8fc", size = 1873745 },
+ { url = "https://files.pythonhosted.org/packages/e1/d7/9ddb7575d4321e40d0363903c2576c8c0c3280ebea137777e5ab58d723e3/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08530b8ac922003033f399128505f513e30ca770527cc8bbacf75a84fcc2c74b", size = 1904188 },
+ { url = "https://files.pythonhosted.org/packages/d1/a8/3194ccfe461bb08da19377ebec8cb4f13c9bd82e13baebc53c5c7c39a029/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae370459da6a5466978c0eacf90690cb57ec9d533f8e63e564ef3822bfa04fe", size = 2083479 },
+ { url = "https://files.pythonhosted.org/packages/42/c7/84cb569555d7179ca0b3f838cef08f66f7089b54432f5b8599aac6e9533e/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3de2777e3b9f4d603112f78006f4ae0acb936e95f06da6cb1a45fbad6bdb4b5", size = 2118415 },
+ { url = "https://files.pythonhosted.org/packages/3b/67/72abb8c73e0837716afbb58a59cc9e3ae43d1aa8677f3b4bc72c16142716/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a64e81e8cba118e108d7126362ea30e021291b7805d47e4896e52c791be2761", size = 2079623 },
+ { url = "https://files.pythonhosted.org/packages/0b/cd/c59707e35a47ba4cbbf153c3f7c56420c58653b5801b055dc52cccc8e2dc/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:52928d8c1b6bda03cc6d811e8923dffc87a2d3c8b3bfd2ce16471c7147a24850", size = 2250175 },
+ { url = "https://files.pythonhosted.org/packages/84/32/e4325a6676b0bed32d5b084566ec86ed7fd1e9bcbfc49c578b1755bde920/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1b30d92c9412beb5ac6b10a3eb7ef92ccb14e3f2a8d7732e2d739f58b3aa7544", size = 2254674 },
+ { url = "https://files.pythonhosted.org/packages/12/6f/5596dc418f2e292ffc661d21931ab34591952e2843e7168ea5a52591f6ff/pydantic_core-2.33.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f995719707e0e29f0f41a8aa3bcea6e761a36c9136104d3189eafb83f5cec5e5", size = 2080951 },
]
[[package]]
@@ -1960,15 +1960,15 @@ wheels = [
[[package]]
name = "pytest-cov"
-version = "6.0.0"
+version = "6.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "coverage", extra = ["toml"] },
{ name = "pytest" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 }
+sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 },
+ { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841 },
]
[[package]]
@@ -2225,27 +2225,27 @@ wheels = [
[[package]]
name = "ruff"
-version = "0.11.2"
+version = "0.11.4"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/90/61/fb87430f040e4e577e784e325351186976516faef17d6fcd921fe28edfd7/ruff-0.11.2.tar.gz", hash = "sha256:ec47591497d5a1050175bdf4e1a4e6272cddff7da88a2ad595e1e326041d8d94", size = 3857511 }
+sdist = { url = "https://files.pythonhosted.org/packages/e8/5b/3ae20f89777115944e89c2d8c2e795dcc5b9e04052f76d5347e35e0da66e/ruff-0.11.4.tar.gz", hash = "sha256:f45bd2fb1a56a5a85fae3b95add03fb185a0b30cf47f5edc92aa0355ca1d7407", size = 3933063 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/62/99/102578506f0f5fa29fd7e0df0a273864f79af044757aef73d1cae0afe6ad/ruff-0.11.2-py3-none-linux_armv6l.whl", hash = "sha256:c69e20ea49e973f3afec2c06376eb56045709f0212615c1adb0eda35e8a4e477", size = 10113146 },
- { url = "https://files.pythonhosted.org/packages/74/ad/5cd4ba58ab602a579997a8494b96f10f316e874d7c435bcc1a92e6da1b12/ruff-0.11.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2c5424cc1c4eb1d8ecabe6d4f1b70470b4f24a0c0171356290b1953ad8f0e272", size = 10867092 },
- { url = "https://files.pythonhosted.org/packages/fc/3e/d3f13619e1d152c7b600a38c1a035e833e794c6625c9a6cea6f63dbf3af4/ruff-0.11.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ecf20854cc73f42171eedb66f006a43d0a21bfb98a2523a809931cda569552d9", size = 10224082 },
- { url = "https://files.pythonhosted.org/packages/90/06/f77b3d790d24a93f38e3806216f263974909888fd1e826717c3ec956bbcd/ruff-0.11.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c543bf65d5d27240321604cee0633a70c6c25c9a2f2492efa9f6d4b8e4199bb", size = 10394818 },
- { url = "https://files.pythonhosted.org/packages/99/7f/78aa431d3ddebfc2418cd95b786642557ba8b3cb578c075239da9ce97ff9/ruff-0.11.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20967168cc21195db5830b9224be0e964cc9c8ecf3b5a9e3ce19876e8d3a96e3", size = 9952251 },
- { url = "https://files.pythonhosted.org/packages/30/3e/f11186d1ddfaca438c3bbff73c6a2fdb5b60e6450cc466129c694b0ab7a2/ruff-0.11.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:955a9ce63483999d9f0b8f0b4a3ad669e53484232853054cc8b9d51ab4c5de74", size = 11563566 },
- { url = "https://files.pythonhosted.org/packages/22/6c/6ca91befbc0a6539ee133d9a9ce60b1a354db12c3c5d11cfdbf77140f851/ruff-0.11.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:86b3a27c38b8fce73bcd262b0de32e9a6801b76d52cdb3ae4c914515f0cef608", size = 12208721 },
- { url = "https://files.pythonhosted.org/packages/19/b0/24516a3b850d55b17c03fc399b681c6a549d06ce665915721dc5d6458a5c/ruff-0.11.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3b66a03b248c9fcd9d64d445bafdf1589326bee6fc5c8e92d7562e58883e30f", size = 11662274 },
- { url = "https://files.pythonhosted.org/packages/d7/65/76be06d28ecb7c6070280cef2bcb20c98fbf99ff60b1c57d2fb9b8771348/ruff-0.11.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0397c2672db015be5aa3d4dac54c69aa012429097ff219392c018e21f5085147", size = 13792284 },
- { url = "https://files.pythonhosted.org/packages/ce/d2/4ceed7147e05852876f3b5f3fdc23f878ce2b7e0b90dd6e698bda3d20787/ruff-0.11.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869bcf3f9abf6457fbe39b5a37333aa4eecc52a3b99c98827ccc371a8e5b6f1b", size = 11327861 },
- { url = "https://files.pythonhosted.org/packages/c4/78/4935ecba13706fd60ebe0e3dc50371f2bdc3d9bc80e68adc32ff93914534/ruff-0.11.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2a2b50ca35457ba785cd8c93ebbe529467594087b527a08d487cf0ee7b3087e9", size = 10276560 },
- { url = "https://files.pythonhosted.org/packages/81/7f/1b2435c3f5245d410bb5dc80f13ec796454c21fbda12b77d7588d5cf4e29/ruff-0.11.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7c69c74bf53ddcfbc22e6eb2f31211df7f65054bfc1f72288fc71e5f82db3eab", size = 9945091 },
- { url = "https://files.pythonhosted.org/packages/39/c4/692284c07e6bf2b31d82bb8c32f8840f9d0627d92983edaac991a2b66c0a/ruff-0.11.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6e8fb75e14560f7cf53b15bbc55baf5ecbe373dd5f3aab96ff7aa7777edd7630", size = 10977133 },
- { url = "https://files.pythonhosted.org/packages/94/cf/8ab81cb7dd7a3b0a3960c2769825038f3adcd75faf46dd6376086df8b128/ruff-0.11.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:842a472d7b4d6f5924e9297aa38149e5dcb1e628773b70e6387ae2c97a63c58f", size = 11378514 },
- { url = "https://files.pythonhosted.org/packages/d9/3a/a647fa4f316482dacf2fd68e8a386327a33d6eabd8eb2f9a0c3d291ec549/ruff-0.11.2-py3-none-win32.whl", hash = "sha256:aca01ccd0eb5eb7156b324cfaa088586f06a86d9e5314b0eb330cb48415097cc", size = 10319835 },
- { url = "https://files.pythonhosted.org/packages/86/54/3c12d3af58012a5e2cd7ebdbe9983f4834af3f8cbea0e8a8c74fa1e23b2b/ruff-0.11.2-py3-none-win_amd64.whl", hash = "sha256:3170150172a8f994136c0c66f494edf199a0bbea7a409f649e4bc8f4d7084080", size = 11373713 },
- { url = "https://files.pythonhosted.org/packages/d6/d4/dd813703af8a1e2ac33bf3feb27e8a5ad514c9f219df80c64d69807e7f71/ruff-0.11.2-py3-none-win_arm64.whl", hash = "sha256:52933095158ff328f4c77af3d74f0379e34fd52f175144cefc1b192e7ccd32b4", size = 10441990 },
+ { url = "https://files.pythonhosted.org/packages/9c/db/baee59ac88f57527fcbaad3a7b309994e42329c6bc4d4d2b681a3d7b5426/ruff-0.11.4-py3-none-linux_armv6l.whl", hash = "sha256:d9f4a761ecbde448a2d3e12fb398647c7f0bf526dbc354a643ec505965824ed2", size = 10106493 },
+ { url = "https://files.pythonhosted.org/packages/c1/d6/9a0962cbb347f4ff98b33d699bf1193ff04ca93bed4b4222fd881b502154/ruff-0.11.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8c1747d903447d45ca3d40c794d1a56458c51e5cc1bc77b7b64bd2cf0b1626cc", size = 10876382 },
+ { url = "https://files.pythonhosted.org/packages/3a/8f/62bab0c7d7e1ae3707b69b157701b41c1ccab8f83e8501734d12ea8a839f/ruff-0.11.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:51a6494209cacca79e121e9b244dc30d3414dac8cc5afb93f852173a2ecfc906", size = 10237050 },
+ { url = "https://files.pythonhosted.org/packages/09/96/e296965ae9705af19c265d4d441958ed65c0c58fc4ec340c27cc9d2a1f5b/ruff-0.11.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f171605f65f4fc49c87f41b456e882cd0c89e4ac9d58e149a2b07930e1d466f", size = 10424984 },
+ { url = "https://files.pythonhosted.org/packages/e5/56/644595eb57d855afed6e54b852e2df8cd5ca94c78043b2f29bdfb29882d5/ruff-0.11.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebf99ea9af918878e6ce42098981fc8c1db3850fef2f1ada69fb1dcdb0f8e79e", size = 9957438 },
+ { url = "https://files.pythonhosted.org/packages/86/83/9d3f3bed0118aef3e871ded9e5687fb8c5776bde233427fd9ce0a45db2d4/ruff-0.11.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edad2eac42279df12e176564a23fc6f4aaeeb09abba840627780b1bb11a9d223", size = 11547282 },
+ { url = "https://files.pythonhosted.org/packages/40/e6/0c6e4f5ae72fac5ccb44d72c0111f294a5c2c8cc5024afcb38e6bda5f4b3/ruff-0.11.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f103a848be9ff379fc19b5d656c1f911d0a0b4e3e0424f9532ececf319a4296e", size = 12182020 },
+ { url = "https://files.pythonhosted.org/packages/b5/92/4aed0e460aeb1df5ea0c2fbe8d04f9725cccdb25d8da09a0d3f5b8764bf8/ruff-0.11.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:193e6fac6eb60cc97b9f728e953c21cc38a20077ed64f912e9d62b97487f3f2d", size = 11679154 },
+ { url = "https://files.pythonhosted.org/packages/1b/d3/7316aa2609f2c592038e2543483eafbc62a0e1a6a6965178e284808c095c/ruff-0.11.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7af4e5f69b7c138be8dcffa5b4a061bf6ba6a3301f632a6bce25d45daff9bc99", size = 13905985 },
+ { url = "https://files.pythonhosted.org/packages/63/80/734d3d17546e47ff99871f44ea7540ad2bbd7a480ed197fe8a1c8a261075/ruff-0.11.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:126b1bf13154aa18ae2d6c3c5efe144ec14b97c60844cfa6eb960c2a05188222", size = 11348343 },
+ { url = "https://files.pythonhosted.org/packages/04/7b/70fc7f09a0161dce9613a4671d198f609e653d6f4ff9eee14d64c4c240fb/ruff-0.11.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8806daaf9dfa881a0ed603f8a0e364e4f11b6ed461b56cae2b1c0cab0645304", size = 10308487 },
+ { url = "https://files.pythonhosted.org/packages/1a/22/1cdd62dabd678d75842bf4944fd889cf794dc9e58c18cc547f9eb28f95ed/ruff-0.11.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5d94bb1cc2fc94a769b0eb975344f1b1f3d294da1da9ddbb5a77665feb3a3019", size = 9929091 },
+ { url = "https://files.pythonhosted.org/packages/9f/20/40e0563506332313148e783bbc1e4276d657962cc370657b2fff20e6e058/ruff-0.11.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:995071203d0fe2183fc7a268766fd7603afb9996785f086b0d76edee8755c896", size = 10924659 },
+ { url = "https://files.pythonhosted.org/packages/b5/41/eef9b7aac8819d9e942f617f9db296f13d2c4576806d604aba8db5a753f1/ruff-0.11.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a37ca937e307ea18156e775a6ac6e02f34b99e8c23fe63c1996185a4efe0751", size = 11428160 },
+ { url = "https://files.pythonhosted.org/packages/ff/61/c488943414fb2b8754c02f3879de003e26efdd20f38167ded3fb3fc1cda3/ruff-0.11.4-py3-none-win32.whl", hash = "sha256:0e9365a7dff9b93af933dab8aebce53b72d8f815e131796268709890b4a83270", size = 10311496 },
+ { url = "https://files.pythonhosted.org/packages/b6/2b/2a1c8deb5f5dfa3871eb7daa41492c4d2b2824a74d2b38e788617612a66d/ruff-0.11.4-py3-none-win_amd64.whl", hash = "sha256:5a9fa1c69c7815e39fcfb3646bbfd7f528fa8e2d4bebdcf4c2bd0fa037a255fb", size = 11399146 },
+ { url = "https://files.pythonhosted.org/packages/4f/03/3aec4846226d54a37822e4c7ea39489e4abd6f88388fba74e3d4abe77300/ruff-0.11.4-py3-none-win_arm64.whl", hash = "sha256:d435db6b9b93d02934cf61ef332e66af82da6d8c69aefdea5994c89997c7a0fc", size = 10450306 },
]
[[package]]
diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml
index eb81dc267b..58d7f0655a 100644
--- a/mobile/android/app/src/main/AndroidManifest.xml
+++ b/mobile/android/app/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
android:maxSdkVersion="32" />
+
@@ -124,4 +125,4 @@
-
\ No newline at end of file
+
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt
index 8520413cff..e7f787e8d8 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt
@@ -1,25 +1,40 @@
package app.alextran.immich
+import android.content.ContentResolver
+import android.content.ContentUris
+import android.content.ContentValues
import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Environment
+import android.provider.MediaStore
+import android.provider.Settings
import android.util.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.embedding.engine.plugins.activity.ActivityAware
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.Result
+import io.flutter.plugin.common.PluginRegistry
import java.security.MessageDigest
import java.io.FileInputStream
import kotlinx.coroutines.*
/**
- * Android plugin for Dart `BackgroundService`
- *
- * Receives messages/method calls from the foreground Dart side to manage
- * the background service, e.g. start (enqueue), stop (cancel)
+ * Android plugin for Dart `BackgroundService` and file trash operations
*/
-class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
+class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware, PluginRegistry.ActivityResultListener {
private var methodChannel: MethodChannel? = null
+ private var fileTrashChannel: MethodChannel? = null
private var context: Context? = null
+ private var pendingResult: Result? = null
+ private val PERMISSION_REQUEST_CODE = 1001
+ private var activityBinding: ActivityPluginBinding? = null
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
@@ -29,6 +44,10 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
context = ctx
methodChannel = MethodChannel(messenger, "immich/foregroundChannel")
methodChannel?.setMethodCallHandler(this)
+
+ // Add file trash channel
+ fileTrashChannel = MethodChannel(messenger, "file_trash")
+ fileTrashChannel?.setMethodCallHandler(this)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
@@ -38,11 +57,14 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
private fun onDetachedFromEngine() {
methodChannel?.setMethodCallHandler(null)
methodChannel = null
+ fileTrashChannel?.setMethodCallHandler(null)
+ fileTrashChannel = null
}
- override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
+ override fun onMethodCall(call: MethodCall, result: Result) {
val ctx = context!!
when (call.method) {
+ // Existing BackgroundService methods
"enable" -> {
val args = call.arguments>()!!
ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
@@ -114,10 +136,180 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
}
}
+ // File Trash methods moved from MainActivity
+ "moveToTrash" -> {
+ val fileName = call.argument("fileName")
+ if (fileName != null) {
+ if (hasManageStoragePermission()) {
+ val success = moveToTrash(fileName)
+ result.success(success)
+ } else {
+ result.error("PERMISSION_DENIED", "Storage permission required", null)
+ }
+ } else {
+ result.error("INVALID_NAME", "The file name is not specified.", null)
+ }
+ }
+
+ "restoreFromTrash" -> {
+ val fileName = call.argument("fileName")
+ if (fileName != null) {
+ if (hasManageStoragePermission()) {
+ val success = untrashImage(fileName)
+ result.success(success)
+ } else {
+ result.error("PERMISSION_DENIED", "Storage permission required", null)
+ }
+ } else {
+ result.error("INVALID_NAME", "The file name is not specified.", null)
+ }
+ }
+
+ "requestManageStoragePermission" -> {
+ if (!hasManageStoragePermission()) {
+ requestManageStoragePermission(result)
+ } else {
+ Log.e("Manage storage permission", "Permission already granted")
+ result.success(true)
+ }
+ }
+
else -> result.notImplemented()
}
}
+
+ // File Trash methods moved from MainActivity
+ private fun hasManageStoragePermission(): Boolean {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ Environment.isExternalStorageManager()
+ } else {
+ true
+ }
+ }
+
+ private fun requestManageStoragePermission(result: Result) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ pendingResult = result // Store the result callback
+ val activity = activityBinding?.activity ?: return
+
+ val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
+ intent.data = Uri.parse("package:${activity.packageName}")
+ activity.startActivityForResult(intent, PERMISSION_REQUEST_CODE)
+ } else {
+ result.success(true)
+ }
+ }
+
+ private fun moveToTrash(fileName: String): Boolean {
+ val contentResolver = context?.contentResolver ?: return false
+ val uri = getFileUri(fileName)
+ Log.e("FILE_URI", uri.toString())
+ return uri?.let { moveToTrash(it) } ?: false
+ }
+
+ private fun moveToTrash(contentUri: Uri): Boolean {
+ val contentResolver = context?.contentResolver ?: return false
+ return try {
+ val values = ContentValues().apply {
+ put(MediaStore.MediaColumns.IS_TRASHED, 1) // Move to trash
+ }
+ val updated = contentResolver.update(contentUri, values, null, null)
+ updated > 0
+ } catch (e: Exception) {
+ Log.e("TrashError", "Error moving to trash", e)
+ false
+ }
+ }
+
+ private fun getFileUri(fileName: String): Uri? {
+ val contentResolver = context?.contentResolver ?: return null
+ val contentUri = MediaStore.Files.getContentUri("external")
+ val projection = arrayOf(MediaStore.Images.Media._ID)
+ val selection = "${MediaStore.Images.Media.DISPLAY_NAME} = ?"
+ val selectionArgs = arrayOf(fileName)
+ var fileUri: Uri? = null
+
+ contentResolver.query(contentUri, projection, selection, selectionArgs, null)?.use { cursor ->
+ if (cursor.moveToFirst()) {
+ val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID))
+ fileUri = ContentUris.withAppendedId(contentUri, id)
+ }
+ }
+ return fileUri
+ }
+
+ private fun untrashImage(name: String): Boolean {
+ val contentResolver = context?.contentResolver ?: return false
+ val uri = getTrashedFileUri(contentResolver, name)
+ Log.e("FILE_URI", uri.toString())
+ return uri?.let { untrashImage(it) } ?: false
+ }
+
+ private fun untrashImage(contentUri: Uri): Boolean {
+ val contentResolver = context?.contentResolver ?: return false
+ return try {
+ val values = ContentValues().apply {
+ put(MediaStore.MediaColumns.IS_TRASHED, 0) // Restore file
+ }
+ val updated = contentResolver.update(contentUri, values, null, null)
+ updated > 0
+ } catch (e: Exception) {
+ Log.e("TrashError", "Error restoring file", e)
+ false
+ }
+ }
+
+ private fun getTrashedFileUri(contentResolver: ContentResolver, fileName: String): Uri? {
+ val contentUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
+ val projection = arrayOf(MediaStore.Files.FileColumns._ID)
+
+ val queryArgs = Bundle().apply {
+ putString(ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Files.FileColumns.DISPLAY_NAME} = ?")
+ putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(fileName))
+ putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY)
+ }
+
+ contentResolver.query(contentUri, projection, queryArgs, null)?.use { cursor ->
+ if (cursor.moveToFirst()) {
+ val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID))
+ return ContentUris.withAppendedId(contentUri, id)
+ }
+ }
+ return null
+ }
+
+ // ActivityAware implementation
+ override fun onAttachedToActivity(binding: ActivityPluginBinding) {
+ activityBinding = binding
+ binding.addActivityResultListener(this)
+ }
+
+ override fun onDetachedFromActivityForConfigChanges() {
+ activityBinding?.removeActivityResultListener(this)
+ activityBinding = null
+ }
+
+ override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
+ activityBinding = binding
+ binding.addActivityResultListener(this)
+ }
+
+ override fun onDetachedFromActivity() {
+ activityBinding?.removeActivityResultListener(this)
+ activityBinding = null
+ }
+
+ // ActivityResultListener implementation
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
+ if (requestCode == PERMISSION_REQUEST_CODE) {
+ val granted = hasManageStoragePermission()
+ pendingResult?.success(granted)
+ pendingResult = null
+ return true
+ }
+ return false
+ }
}
private const val TAG = "BackgroundServicePlugin"
-private const val BUFFER_SIZE = 2 * 1024 * 1024;
+private const val BUFFER_SIZE = 2 * 1024 * 1024
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt
index 4ffb490c77..2b6bf81148 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt
@@ -2,14 +2,12 @@ package app.alextran.immich
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
-import android.os.Bundle
-import android.content.Intent
+import androidx.annotation.NonNull
class MainActivity : FlutterActivity() {
-
- override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
+ override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine.plugins.add(BackgroundServicePlugin())
+ // No need to set up method channel here as it's now handled in the plugin
}
-
}
diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json
index e3b6916e74..3aa2f1b475 100644
--- a/mobile/assets/i18n/en-US.json
+++ b/mobile/assets/i18n/en-US.json
@@ -23,6 +23,8 @@
"advanced_settings_tile_title": "Advanced",
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
"advanced_settings_troubleshooting_title": "Troubleshooting",
+ "advanced_settings_sync_remote_deletions_title": "Sync remote deletions [EXPERIMENTAL]",
+ "advanced_settings_sync_remote_deletions_subtitle": "Automatically delete or restore an asset on this device when that action is taken on the web",
"album_info_card_backup_album_excluded": "EXCLUDED",
"album_info_card_backup_album_included": "INCLUDED",
"albums": "Albums",
diff --git a/mobile/lib/interfaces/local_files_manager.interface.dart b/mobile/lib/interfaces/local_files_manager.interface.dart
new file mode 100644
index 0000000000..c8b83a7c93
--- /dev/null
+++ b/mobile/lib/interfaces/local_files_manager.interface.dart
@@ -0,0 +1,5 @@
+abstract interface class ILocalFilesManager {
+ Future moveToTrash(String fileName);
+ Future restoreFromTrash(String fileName);
+ Future requestManageStoragePermission();
+}
diff --git a/mobile/lib/providers/websocket.provider.dart b/mobile/lib/providers/websocket.provider.dart
index f92d2c8421..72dbda8b6f 100644
--- a/mobile/lib/providers/websocket.provider.dart
+++ b/mobile/lib/providers/websocket.provider.dart
@@ -23,6 +23,7 @@ enum PendingAction {
assetDelete,
assetUploaded,
assetHidden,
+ assetTrash,
}
class PendingChange {
@@ -160,7 +161,7 @@ class WebsocketNotifier extends StateNotifier {
socket.on('on_upload_success', _handleOnUploadSuccess);
socket.on('on_config_update', _handleOnConfigUpdate);
socket.on('on_asset_delete', _handleOnAssetDelete);
- socket.on('on_asset_trash', _handleServerUpdates);
+ socket.on('on_asset_trash', _handleOnAssetTrash);
socket.on('on_asset_restore', _handleServerUpdates);
socket.on('on_asset_update', _handleServerUpdates);
socket.on('on_asset_stack_update', _handleServerUpdates);
@@ -207,6 +208,26 @@ class WebsocketNotifier extends StateNotifier {
_debounce.run(handlePendingChanges);
}
+ Future _handlePendingTrashes() async {
+ final trashChanges = state.pendingChanges
+ .where((c) => c.action == PendingAction.assetTrash)
+ .toList();
+ if (trashChanges.isNotEmpty) {
+ List remoteIds = trashChanges
+ .expand((a) => (a.value as List).map((e) => e.toString()))
+ .toList();
+
+ await _ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds);
+ await _ref.read(assetProvider.notifier).getAllAsset();
+
+ state = state.copyWith(
+ pendingChanges: state.pendingChanges
+ .whereNot((c) => trashChanges.contains(c))
+ .toList(),
+ );
+ }
+ }
+
Future _handlePendingDeletes() async {
final deleteChanges = state.pendingChanges
.where((c) => c.action == PendingAction.assetDelete)
@@ -267,6 +288,7 @@ class WebsocketNotifier extends StateNotifier {
await _handlePendingUploaded();
await _handlePendingDeletes();
await _handlingPendingHidden();
+ await _handlePendingTrashes();
}
void _handleOnConfigUpdate(dynamic _) {
@@ -285,6 +307,10 @@ class WebsocketNotifier extends StateNotifier {
void _handleOnAssetDelete(dynamic data) =>
addPendingChange(PendingAction.assetDelete, data);
+ void _handleOnAssetTrash(dynamic data) {
+ addPendingChange(PendingAction.assetTrash, data);
+ }
+
void _handleOnAssetHidden(dynamic data) =>
addPendingChange(PendingAction.assetHidden, data);
diff --git a/mobile/lib/repositories/local_files_manager.repository.dart b/mobile/lib/repositories/local_files_manager.repository.dart
new file mode 100644
index 0000000000..522d7e7a05
--- /dev/null
+++ b/mobile/lib/repositories/local_files_manager.repository.dart
@@ -0,0 +1,23 @@
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/interfaces/local_files_manager.interface.dart';
+import 'package:immich_mobile/utils/local_files_manager.dart';
+
+final localFilesManagerRepositoryProvider =
+ Provider((ref) => LocalFilesManagerRepository());
+
+class LocalFilesManagerRepository implements ILocalFilesManager {
+ @override
+ Future moveToTrash(String fileName) async {
+ return await LocalFilesManager.moveToTrash(fileName);
+ }
+
+ @override
+ Future restoreFromTrash(String fileName) async {
+ return await LocalFilesManager.restoreFromTrash(fileName);
+ }
+
+ @override
+ Future requestManageStoragePermission() async {
+ return await LocalFilesManager.requestManageStoragePermission();
+ }
+}
diff --git a/mobile/lib/services/app_settings.service.dart b/mobile/lib/services/app_settings.service.dart
index cc57b8d3a3..6413b69fce 100644
--- a/mobile/lib/services/app_settings.service.dart
+++ b/mobile/lib/services/app_settings.service.dart
@@ -61,6 +61,7 @@ enum AppSettingsEnum {
0,
),
advancedTroubleshooting(StoreKey.advancedTroubleshooting, null, false),
+ manageLocalMediaAndroid(StoreKey.manageLocalMediaAndroid, null, false),
logLevel(StoreKey.logLevel, null, 5), // Level.INFO = 5
preferRemoteImage(StoreKey.preferRemoteImage, null, false),
loopVideo(StoreKey.loopVideo, "loopVideo", true),
diff --git a/mobile/lib/services/sync.service.dart b/mobile/lib/services/sync.service.dart
index 1e3c2a070b..0574dc283b 100644
--- a/mobile/lib/services/sync.service.dart
+++ b/mobile/lib/services/sync.service.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:io';
import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -16,6 +17,8 @@ import 'package:immich_mobile/interfaces/album_api.interface.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/etag.interface.dart';
+import 'package:immich_mobile/interfaces/local_files_manager.interface.dart';
+import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/interfaces/partner.interface.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/providers/infrastructure/exif.provider.dart';
@@ -25,6 +28,8 @@ import 'package:immich_mobile/repositories/album_api.repository.dart';
import 'package:immich_mobile/repositories/album_media.repository.dart';
import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/repositories/etag.repository.dart';
+import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
+import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/repositories/partner.repository.dart';
import 'package:immich_mobile/repositories/partner_api.repository.dart';
import 'package:immich_mobile/services/entity.service.dart';
@@ -48,6 +53,8 @@ final syncServiceProvider = Provider(
ref.watch(userRepositoryProvider),
ref.watch(userServiceProvider),
ref.watch(etagRepositoryProvider),
+ ref.watch(appSettingsServiceProvider),
+ ref.watch(localFilesManagerRepositoryProvider),
ref.watch(partnerApiRepositoryProvider),
ref.watch(userApiRepositoryProvider),
),
@@ -69,6 +76,8 @@ class SyncService {
final IUserApiRepository _userApiRepository;
final AsyncMutex _lock = AsyncMutex();
final Logger _log = Logger('SyncService');
+ final AppSettingsService _appSettingsService;
+ final ILocalFilesManager _localFilesManager;
SyncService(
this._hashService,
@@ -82,6 +91,8 @@ class SyncService {
this._userRepository,
this._userService,
this._eTagRepository,
+ this._appSettingsService,
+ this._localFilesManager,
this._partnerApiRepository,
this._userApiRepository,
);
@@ -238,8 +249,19 @@ class SyncService {
return null;
}
+ Future _moveToTrashMatchedAssets(Iterable idsToDelete) async {
+ final List localAssets = await _assetRepository.getAllLocal();
+ final List matchedAssets = localAssets
+ .where((asset) => idsToDelete.contains(asset.remoteId))
+ .toList();
+
+ for (var asset in matchedAssets) {
+ _localFilesManager.moveToTrash(asset.fileName);
+ }
+ }
+
/// Deletes remote-only assets, updates merged assets to be local-only
- Future handleRemoteAssetRemoval(List idsToDelete) {
+ Future handleRemoteAssetRemoval(List idsToDelete) async {
return _assetRepository.transaction(() async {
await _assetRepository.deleteAllByRemoteId(
idsToDelete,
@@ -249,6 +271,12 @@ class SyncService {
idsToDelete,
state: AssetState.merged,
);
+ if (Platform.isAndroid &&
+ _appSettingsService.getSetting(
+ AppSettingsEnum.manageLocalMediaAndroid,
+ )) {
+ await _moveToTrashMatchedAssets(idsToDelete);
+ }
if (merged.isEmpty) return;
for (final Asset asset in merged) {
asset.remoteId = null;
@@ -790,9 +818,27 @@ class SyncService {
return (existing, toUpsert);
}
+ Future _toggleTrashStatusForAssets(List assetsList) async {
+ for (var asset in assetsList) {
+ if (asset.isTrashed) {
+ _localFilesManager.moveToTrash(asset.fileName);
+ } else {
+ _localFilesManager.restoreFromTrash(asset.fileName);
+ }
+ }
+ }
+
/// Inserts or updates the assets in the database with their ExifInfo (if any)
Future upsertAssetsWithExif(List assets) async {
if (assets.isEmpty) return;
+
+ if (Platform.isAndroid &&
+ _appSettingsService.getSetting(
+ AppSettingsEnum.manageLocalMediaAndroid,
+ )) {
+ _toggleTrashStatusForAssets(assets);
+ }
+
final exifInfos = assets.map((e) => e.exifInfo).nonNulls.toList();
try {
await _assetRepository.transaction(() async {
diff --git a/mobile/lib/utils/local_files_manager.dart b/mobile/lib/utils/local_files_manager.dart
new file mode 100644
index 0000000000..da9308c3cf
--- /dev/null
+++ b/mobile/lib/utils/local_files_manager.dart
@@ -0,0 +1,39 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+
+class LocalFilesManager {
+ static const MethodChannel _channel = MethodChannel('file_trash');
+
+ static Future moveToTrash(String fileName) async {
+ try {
+ final bool success =
+ await _channel.invokeMethod('moveToTrash', {'fileName': fileName});
+ return success;
+ } on PlatformException catch (e) {
+ debugPrint('Error moving to trash: ${e.message}');
+ return false;
+ }
+ }
+
+ static Future restoreFromTrash(String fileName) async {
+ try {
+ final bool success = await _channel
+ .invokeMethod('restoreFromTrash', {'fileName': fileName});
+ return success;
+ } on PlatformException catch (e) {
+ debugPrint('Error restoring file: ${e.message}');
+ return false;
+ }
+ }
+
+ static Future requestManageStoragePermission() async {
+ try {
+ final bool success =
+ await _channel.invokeMethod('requestManageStoragePermission');
+ return success;
+ } on PlatformException catch (e) {
+ debugPrint('Error requesting permission: ${e.message}');
+ return false;
+ }
+ }
+}
diff --git a/mobile/lib/utils/url_helper.dart b/mobile/lib/utils/url_helper.dart
index 6b355e362f..187026b53c 100644
--- a/mobile/lib/utils/url_helper.dart
+++ b/mobile/lib/utils/url_helper.dart
@@ -1,5 +1,6 @@
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
+import 'package:punycode/punycode.dart';
String sanitizeUrl(String url) {
// Add schema if none is set
@@ -11,13 +12,80 @@ String sanitizeUrl(String url) {
}
String? getServerUrl() {
- final serverUrl = Store.tryGet(StoreKey.serverEndpoint);
+ final serverUrl = punycodeDecodeUrl(Store.tryGet(StoreKey.serverEndpoint));
final serverUri = serverUrl != null ? Uri.tryParse(serverUrl) : null;
if (serverUri == null) {
return null;
}
- return serverUri.hasPort
- ? "${serverUri.scheme}://${serverUri.host}:${serverUri.port}"
- : "${serverUri.scheme}://${serverUri.host}";
+ return Uri.decodeFull(
+ serverUri.hasPort
+ ? "${serverUri.scheme}://${serverUri.host}:${serverUri.port}"
+ : "${serverUri.scheme}://${serverUri.host}",
+ );
+}
+
+/// Converts a Unicode URL to its ASCII-compatible encoding (Punycode).
+///
+/// This is especially useful for internationalized domain names (IDNs),
+/// where parts of the URL (typically the host) contain non-ASCII characters.
+///
+/// Example:
+/// ```dart
+/// final encodedUrl = punycodeEncodeUrl('https://bücher.de');
+/// print(encodedUrl); // Outputs: https://xn--bcher-kva.de
+/// ```
+///
+/// Notes:
+/// - If the input URL is invalid, an empty string is returned.
+/// - Only the host part of the URL is converted to Punycode; the scheme,
+/// path, and port remain unchanged.
+///
+String punycodeEncodeUrl(String serverUrl) {
+ final serverUri = Uri.tryParse(serverUrl);
+ if (serverUri == null || serverUri.host.isEmpty) return '';
+
+ final encodedHost = Uri.decodeComponent(serverUri.host).split('.').map(
+ (segment) {
+ // If segment is already ASCII, then return as it is.
+ if (segment.runes.every((c) => c < 0x80)) return segment;
+ return 'xn--${punycodeEncode(segment)}';
+ },
+ ).join('.');
+
+ return serverUri.replace(host: encodedHost).toString();
+}
+
+/// Decodes an ASCII-compatible (Punycode) URL back to its original Unicode representation.
+///
+/// This method is useful for converting internationalized domain names (IDNs)
+/// that were previously encoded with Punycode back to their human-readable Unicode form.
+///
+/// Example:
+/// ```dart
+/// final decodedUrl = punycodeDecodeUrl('https://xn--bcher-kva.de');
+/// print(decodedUrl); // Outputs: https://bücher.de
+/// ```
+///
+/// Notes:
+/// - If the input URL is invalid the method returns `null`.
+/// - Only the host part of the URL is decoded. The scheme and port (if any) are preserved.
+/// - The method assumes that the input URL only contains: scheme, host, port (optional).
+/// - Query parameters, fragments, and user info are not handled (by design, as per constraints).
+///
+String? punycodeDecodeUrl(String? serverUrl) {
+ final serverUri = serverUrl != null ? Uri.tryParse(serverUrl) : null;
+ if (serverUri == null || serverUri.host.isEmpty) return null;
+
+ final decodedHost = serverUri.host.split('.').map(
+ (segment) {
+ if (segment.toLowerCase().startsWith('xn--')) {
+ return punycodeDecode(segment.substring(4));
+ }
+ // If segment is not punycode encoded, then return as it is.
+ return segment;
+ },
+ ).join('.');
+
+ return Uri.decodeFull(serverUri.replace(host: decodedHost).toString());
}
diff --git a/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart b/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart
index 1c0f9a2b56..c6e85418ca 100644
--- a/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart
+++ b/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart
@@ -8,25 +8,25 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
+import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/collection_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/scroll_notifier.provider.dart';
+import 'package:immich_mobile/providers/asset_viewer/scroll_to_date_notifier.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
+import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
+import 'package:immich_mobile/providers/tab.provider.dart';
+import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_drag_region.dart';
+import 'package:immich_mobile/widgets/asset_grid/control_bottom_app_bar.dart';
import 'package:immich_mobile/widgets/asset_grid/thumbnail_image.dart';
import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
-import 'package:fluttertoast/fluttertoast.dart';
-import 'package:immich_mobile/widgets/asset_grid/control_bottom_app_bar.dart';
-import 'package:immich_mobile/routing/router.dart';
-import 'package:immich_mobile/entities/asset.entity.dart';
-import 'package:immich_mobile/providers/asset_viewer/scroll_to_date_notifier.provider.dart';
-import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
-import 'package:immich_mobile/providers/tab.provider.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'asset_grid_data_structure.dart';
@@ -107,6 +107,8 @@ class ImmichAssetGridViewState extends ConsumerState {
final Set _draggedAssets =
HashSet(equals: (a, b) => a.id == b.id, hashCode: (a) => a.id);
+ ScrollPhysics? _scrollPhysics;
+
Set _getSelectedAssets() {
return Set.from(_selectedAssets);
}
@@ -265,6 +267,7 @@ class ImmichAssetGridViewState extends ConsumerState {
),
itemBuilder: _itemBuilder,
itemPositionsListener: _itemPositionsListener,
+ physics: _scrollPhysics,
itemScrollController: _itemScrollController,
scrollOffsetController: _scrollOffsetController,
itemCount: widget.renderList.elements.length +
@@ -439,6 +442,7 @@ class ImmichAssetGridViewState extends ConsumerState {
void _setDragStartIndex(AssetIndex index) {
setState(() {
+ _scrollPhysics = const ClampingScrollPhysics();
_dragAnchorAssetIndex = index.rowIndex;
_dragAnchorSectionIndex = index.sectionIndex;
_dragging = true;
@@ -446,6 +450,12 @@ class ImmichAssetGridViewState extends ConsumerState {
}
void _stopDrag() {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ // Update the physics post frame to prevent sudden change in physics on iOS.
+ setState(() {
+ _scrollPhysics = null;
+ });
+ });
setState(() {
_dragging = false;
_draggedAssets.clear();
diff --git a/mobile/lib/widgets/asset_viewer/description_input.dart b/mobile/lib/widgets/asset_viewer/description_input.dart
index 778212eabe..3ac60fd613 100644
--- a/mobile/lib/widgets/asset_viewer/description_input.dart
+++ b/mobile/lib/widgets/asset_viewer/description_input.dart
@@ -34,17 +34,24 @@ class DescriptionInput extends HookConsumerWidget {
final owner = ref.watch(currentUserProvider);
final hasError = useState(false);
final assetWithExif = ref.watch(assetDetailProvider(asset));
+ final hasDescription = useState(false);
+ final isOwner = fastHash(owner?.id ?? '') == asset.ownerId;
useEffect(
() {
- assetService
- .getDescription(asset)
- .then((value) => controller.text = value);
+ assetService.getDescription(asset).then((value) {
+ controller.text = value;
+ hasDescription.value = value.isNotEmpty;
+ });
return null;
},
[assetWithExif.value],
);
+ if (!isOwner && !hasDescription.value) {
+ return const SizedBox.shrink();
+ }
+
submitDescription(String description) async {
hasError.value = false;
try {
@@ -82,7 +89,7 @@ class DescriptionInput extends HookConsumerWidget {
}
return TextField(
- enabled: fastHash(owner?.id ?? '') == asset.ownerId,
+ enabled: isOwner,
focusNode: focusNode,
onTap: () => isFocus.value = true,
onChanged: (value) {
diff --git a/mobile/lib/widgets/forms/login/login_form.dart b/mobile/lib/widgets/forms/login/login_form.dart
index a6da172f0e..ab532987a7 100644
--- a/mobile/lib/widgets/forms/login/login_form.dart
+++ b/mobile/lib/widgets/forms/login/login_form.dart
@@ -1,4 +1,5 @@
import 'dart:io';
+
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -7,18 +8,18 @@ import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
-import 'package:immich_mobile/providers/oauth.provider.dart';
-import 'package:immich_mobile/providers/gallery_permission.provider.dart';
-import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart';
+import 'package:immich_mobile/providers/gallery_permission.provider.dart';
+import 'package:immich_mobile/providers/oauth.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
+import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/provider_utils.dart';
+import 'package:immich_mobile/utils/url_helper.dart';
import 'package:immich_mobile/utils/version_compatibility.dart';
import 'package:immich_mobile/widgets/common/immich_logo.dart';
import 'package:immich_mobile/widgets/common/immich_title_text.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
-import 'package:immich_mobile/utils/url_helper.dart';
import 'package:immich_mobile/widgets/forms/login/email_input.dart';
import 'package:immich_mobile/widgets/forms/login/loading_icon.dart';
import 'package:immich_mobile/widgets/forms/login/login_button.dart';
@@ -82,7 +83,8 @@ class LoginForm extends HookConsumerWidget {
/// Fetch the server login credential and enables oAuth login if necessary
/// Returns true if successful, false otherwise
Future getServerAuthSettings() async {
- final serverUrl = sanitizeUrl(serverEndpointController.text);
+ final sanitizeServerUrl = sanitizeUrl(serverEndpointController.text);
+ final serverUrl = punycodeEncodeUrl(sanitizeServerUrl);
// Guard empty URL
if (serverUrl.isEmpty) {
diff --git a/mobile/lib/widgets/settings/advanced_settings.dart b/mobile/lib/widgets/settings/advanced_settings.dart
index a2e0e5b95c..98c8728298 100644
--- a/mobile/lib/widgets/settings/advanced_settings.dart
+++ b/mobile/lib/widgets/settings/advanced_settings.dart
@@ -1,11 +1,13 @@
import 'dart:io';
+import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/log.service.dart';
import 'package:immich_mobile/providers/user.provider.dart';
+import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/utils/http_ssl_cert_override.dart';
@@ -25,6 +27,8 @@ class AdvancedSettings extends HookConsumerWidget {
final advancedTroubleshooting =
useAppSettingsState(AppSettingsEnum.advancedTroubleshooting);
+ final manageLocalMediaAndroid =
+ useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid);
final levelId = useAppSettingsState(AppSettingsEnum.logLevel);
final preferRemote = useAppSettingsState(AppSettingsEnum.preferRemoteImage);
final allowSelfSignedSSLCert =
@@ -40,6 +44,16 @@ class AdvancedSettings extends HookConsumerWidget {
LogService.I.setlogLevel(Level.LEVELS[levelId.value].toLogLevel()),
);
+ Future checkAndroidVersion() async {
+ if (Platform.isAndroid) {
+ DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
+ AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
+ int sdkVersion = androidInfo.version.sdkInt;
+ return sdkVersion >= 30;
+ }
+ return false;
+ }
+
final advancedSettings = [
SettingsSwitchListTile(
enabled: true,
@@ -47,6 +61,29 @@ class AdvancedSettings extends HookConsumerWidget {
title: "advanced_settings_troubleshooting_title".tr(),
subtitle: "advanced_settings_troubleshooting_subtitle".tr(),
),
+ FutureBuilder(
+ future: checkAndroidVersion(),
+ builder: (context, snapshot) {
+ if (snapshot.hasData && snapshot.data == true) {
+ return SettingsSwitchListTile(
+ enabled: true,
+ valueNotifier: manageLocalMediaAndroid,
+ title: "advanced_settings_sync_remote_deletions_title".tr(),
+ subtitle: "advanced_settings_sync_remote_deletions_subtitle".tr(),
+ onChanged: (value) async {
+ if (value) {
+ final result = await ref
+ .read(localFilesManagerRepositoryProvider)
+ .requestManageStoragePermission();
+ manageLocalMediaAndroid.value = result;
+ }
+ },
+ );
+ } else {
+ return const SizedBox.shrink();
+ }
+ },
+ ),
SettingsSliderListTile(
text: "advanced_settings_log_level_title".tr(args: [logLevel]),
valueNotifier: levelId,
diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml
index e939c65836..73f60d9337 100644
--- a/mobile/pubspec.yaml
+++ b/mobile/pubspec.yaml
@@ -51,6 +51,7 @@ dependencies:
permission_handler: ^11.4.0
photo_manager: ^3.6.4
photo_manager_image_provider: ^2.2.0
+ punycode: ^1.0.0
riverpod_annotation: ^2.6.1
scrollable_positioned_list: ^0.3.8
share_handler: ^0.0.22
diff --git a/mobile/test/modules/shared/sync_service_test.dart b/mobile/test/modules/shared/sync_service_test.dart
index eab6b6f61a..47bc1b9544 100644
--- a/mobile/test/modules/shared/sync_service_test.dart
+++ b/mobile/test/modules/shared/sync_service_test.dart
@@ -60,6 +60,9 @@ void main() {
final MockAlbumMediaRepository albumMediaRepository =
MockAlbumMediaRepository();
final MockAlbumApiRepository albumApiRepository = MockAlbumApiRepository();
+ final MockAppSettingService appSettingService = MockAppSettingService();
+ final MockLocalFilesManagerRepository localFilesManagerRepository =
+ MockLocalFilesManagerRepository();
final MockPartnerApiRepository partnerApiRepository =
MockPartnerApiRepository();
final MockUserApiRepository userApiRepository = MockUserApiRepository();
@@ -106,6 +109,8 @@ void main() {
userRepository,
userService,
eTagRepository,
+ appSettingService,
+ localFilesManagerRepository,
partnerApiRepository,
userApiRepository,
);
diff --git a/mobile/test/modules/utils/url_helper_test.dart b/mobile/test/modules/utils/url_helper_test.dart
new file mode 100644
index 0000000000..840ac91f1f
--- /dev/null
+++ b/mobile/test/modules/utils/url_helper_test.dart
@@ -0,0 +1,138 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:immich_mobile/utils/url_helper.dart';
+
+void main() {
+ group('punycodeEncodeUrl', () {
+ test('should return empty string for invalid URL', () {
+ expect(punycodeEncodeUrl('not a url'), equals(''));
+ });
+
+ test('should handle empty input', () {
+ expect(punycodeEncodeUrl(''), equals(''));
+ });
+
+ test('should return ASCII-only URL unchanged', () {
+ const url = 'https://example.com';
+ expect(punycodeEncodeUrl(url), equals(url));
+ });
+
+ test('should encode single-segment Unicode host', () {
+ const url = 'https://bücher';
+ const expected = 'https://xn--bcher-kva';
+ expect(punycodeEncodeUrl(url), equals(expected));
+ });
+
+ test('should encode multi-segment Unicode host', () {
+ const url = 'https://bücher.de';
+ const expected = 'https://xn--bcher-kva.de';
+ expect(punycodeEncodeUrl(url), equals(expected));
+ });
+
+ test(
+ 'should encode multi-segment Unicode host with multiple non-ASCII segments',
+ () {
+ const url = 'https://bücher.münchen';
+ const expected = 'https://xn--bcher-kva.xn--mnchen-3ya';
+ expect(punycodeEncodeUrl(url), equals(expected));
+ });
+
+ test('should handle URL with port', () {
+ const url = 'https://bücher.de:8080';
+ const expected = 'https://xn--bcher-kva.de:8080';
+ expect(punycodeEncodeUrl(url), equals(expected));
+ });
+
+ test('should handle URL with path', () {
+ const url = 'https://bücher.de/path/to/resource';
+ const expected = 'https://xn--bcher-kva.de/path/to/resource';
+ expect(punycodeEncodeUrl(url), equals(expected));
+ });
+
+ test('should handle URL with port and path', () {
+ const url = 'https://bücher.de:3000/path';
+ const expected = 'https://xn--bcher-kva.de:3000/path';
+ expect(punycodeEncodeUrl(url), equals(expected));
+ });
+
+ test('should not encode ASCII segment in multi-segment host', () {
+ const url = 'https://shop.bücher.de';
+ const expected = 'https://shop.xn--bcher-kva.de';
+ expect(punycodeEncodeUrl(url), equals(expected));
+ });
+
+ test('should handle host with hyphen in Unicode segment', () {
+ const url = 'https://bü-cher.de';
+ const expected = 'https://xn--b-cher-3ya.de';
+ expect(punycodeEncodeUrl(url), equals(expected));
+ });
+
+ test('should handle host with numbers in Unicode segment', () {
+ const url = 'https://bücher123.de';
+ const expected = 'https://xn--bcher123-65a.de';
+ expect(punycodeEncodeUrl(url), equals(expected));
+ });
+
+ test('should encode the domain of the original issue poster :)', () {
+ const url = 'https://фото.большойчлен.рф/';
+ const expected = 'https://xn--n1aalg.xn--90ailhbncb6fh7b.xn--p1ai/';
+ expect(punycodeEncodeUrl(url), expected);
+ });
+ });
+
+ group('punycodeDecodeUrl', () {
+ test('should return null for null input', () {
+ expect(punycodeDecodeUrl(null), isNull);
+ });
+
+ test('should return null for an invalid URL', () {
+ // "not a url" should fail to parse.
+ expect(punycodeDecodeUrl('not a url'), isNull);
+ });
+
+ test('should return null for a URL with empty host', () {
+ // "https://" is a valid scheme but with no host.
+ expect(punycodeDecodeUrl('https://'), isNull);
+ });
+
+ test('should return ASCII-only URL unchanged', () {
+ const url = 'https://example.com';
+ expect(punycodeDecodeUrl(url), equals(url));
+ });
+
+ test('should decode a single-segment Punycode domain', () {
+ const input = 'https://xn--bcher-kva.de';
+ const expected = 'https://bücher.de';
+ expect(punycodeDecodeUrl(input), equals(expected));
+ });
+
+ test('should decode a multi-segment Punycode domain', () {
+ const input = 'https://shop.xn--bcher-kva.de';
+ const expected = 'https://shop.bücher.de';
+ expect(punycodeDecodeUrl(input), equals(expected));
+ });
+
+ test('should decode URL with port', () {
+ const input = 'https://xn--bcher-kva.de:8080';
+ const expected = 'https://bücher.de:8080';
+ expect(punycodeDecodeUrl(input), equals(expected));
+ });
+
+ test('should decode domains with uppercase punycode prefix correctly', () {
+ const input = 'https://XN--BCHER-KVA.de';
+ const expected = 'https://bücher.de';
+ expect(punycodeDecodeUrl(input), equals(expected));
+ });
+
+ test('should handle mixed segments with no punycode in some parts', () {
+ const input = 'https://news.xn--bcher-kva.de';
+ const expected = 'https://news.bücher.de';
+ expect(punycodeDecodeUrl(input), equals(expected));
+ });
+
+ test('should decode the domain of the original issue poster :)', () {
+ const url = 'https://xn--n1aalg.xn--90ailhbncb6fh7b.xn--p1ai/';
+ const expected = 'https://фото.большойчлен.рф/';
+ expect(punycodeDecodeUrl(url), expected);
+ });
+ });
+}
diff --git a/mobile/test/repository.mocks.dart b/mobile/test/repository.mocks.dart
index 1c698297dc..7d444a66b6 100644
--- a/mobile/test/repository.mocks.dart
+++ b/mobile/test/repository.mocks.dart
@@ -4,6 +4,7 @@ import 'package:immich_mobile/interfaces/album_api.interface.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/asset_api.interface.dart';
+import 'package:immich_mobile/interfaces/asset_api.interface.dart';
import 'package:immich_mobile/interfaces/asset_media.interface.dart';
import 'package:immich_mobile/interfaces/auth.interface.dart';
import 'package:immich_mobile/interfaces/auth_api.interface.dart';
@@ -31,6 +32,11 @@ class MockBackupAlbumRepository extends Mock
class MockAssetApiRepository extends Mock implements IAssetApiRepository {}
+class MockBackupAlbumRepository extends Mock
+ implements IBackupAlbumRepository {}
+
+class MockAssetApiRepository extends Mock implements IAssetApiRepository {}
+
class MockAssetMediaRepository extends Mock implements IAssetMediaRepository {}
class MockFileMediaRepository extends Mock implements IFileMediaRepository {}
@@ -41,6 +47,9 @@ class MockAuthApiRepository extends Mock implements IAuthApiRepository {}
class MockAuthRepository extends Mock implements IAuthRepository {}
+class MockPartnerRepository extends Mock implements IPartnerRepository {}
+
class MockPartnerApiRepository extends Mock implements IPartnerApiRepository {}
-class MockPartnerRepository extends Mock implements IPartnerRepository {}
+class MockLocalFilesManagerRepository extends Mock
+ implements ILocalFilesManager {}
diff --git a/mobile/test/service.mocks.dart b/mobile/test/service.mocks.dart
index d31a7e5d50..34d80aac9d 100644
--- a/mobile/test/service.mocks.dart
+++ b/mobile/test/service.mocks.dart
@@ -1,4 +1,5 @@
import 'package:immich_mobile/services/album.service.dart';
+import 'package:immich_mobile/services/album.service.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/background.service.dart';
import 'package:immich_mobile/services/backup.service.dart';
@@ -15,6 +16,10 @@ class MockAlbumService extends Mock implements AlbumService {}
class MockBackupService extends Mock implements BackupService {}
+class MockAlbumService extends Mock implements AlbumService {}
+
+class MockBackupService extends Mock implements BackupService {}
+
class MockSyncService extends Mock implements SyncService {}
class MockHashService extends Mock implements HashService {}
diff --git a/server/package-lock.json b/server/package-lock.json
index 6c5bc4adf5..de6e0c7065 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -23,7 +23,7 @@
"@opentelemetry/context-async-hooks": "^2.0.0",
"@opentelemetry/exporter-prometheus": "^0.200.0",
"@opentelemetry/sdk-node": "^0.200.0",
- "@react-email/components": "^0.0.34",
+ "@react-email/components": "^0.0.35",
"@socket.io/redis-adapter": "^8.3.0",
"archiver": "^7.0.0",
"async-lock": "^1.4.0",
@@ -152,13 +152,13 @@
}
},
"node_modules/@angular-devkit/schematics": {
- "version": "19.1.8",
- "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.1.8.tgz",
- "integrity": "sha512-2JGUMD3zjfY8G4RYpypm2/1YEO+O4DtFycUvptIpsBYyULgnEbJ3tlp2oRiXI2vp9tC8IyWqa/swlA8DTI6ZYQ==",
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.6.tgz",
+ "integrity": "sha512-YTAxNnT++5eflx19OUHmOWu597/TbTel+QARiZCv1xQw99+X8DCKKOUXtqBRd53CAHlREDI33Rn/JLY3NYgMLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-devkit/core": "19.1.8",
+ "@angular-devkit/core": "19.2.6",
"jsonc-parser": "3.3.1",
"magic-string": "0.30.17",
"ora": "5.4.1",
@@ -171,15 +171,15 @@
}
},
"node_modules/@angular-devkit/schematics-cli": {
- "version": "19.1.8",
- "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-19.1.8.tgz",
- "integrity": "sha512-sHblN9EuiJgKwJVYc+nhpU+GlVkAJHJc7lBR8YSoaugNGcCMkWn4f7rJnJDywL/CEOHBICnyWZKfTCMsMyg1Cw==",
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-19.2.6.tgz",
+ "integrity": "sha512-OCLVk1YbTWfaZwpKPnd+9A34eMAZIRjntdugGvfw21ok9dUA8gICGDhfYATSfnU8/AbVQMTPK5sgG0xhUEm3UA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-devkit/core": "19.1.8",
- "@angular-devkit/schematics": "19.1.8",
- "@inquirer/prompts": "7.2.1",
+ "@angular-devkit/core": "19.2.6",
+ "@angular-devkit/schematics": "19.2.6",
+ "@inquirer/prompts": "7.3.2",
"ansi-colors": "4.1.3",
"symbol-observable": "4.0.0",
"yargs-parser": "21.1.1"
@@ -194,9 +194,9 @@
}
},
"node_modules/@angular-devkit/schematics-cli/node_modules/@angular-devkit/core": {
- "version": "19.1.8",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.1.8.tgz",
- "integrity": "sha512-j1zHKvOsGwu5YwAZGuzi835R9vcW7PkfxmSRIJeVl+vawgk31K3zFb4UPH8AY/NPWYqXIAnwpka3HC1+JrWLWA==",
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.6.tgz",
+ "integrity": "sha512-WFgiYhrDMq83UNaGRAneIM7CYYdBozD+yYA9BjoU8AgBLKtrvn6S8ZcjKAk5heoHtY/u8pEb0mwDTz9gxFmJZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -221,31 +221,6 @@
}
}
},
- "node_modules/@angular-devkit/schematics-cli/node_modules/@inquirer/prompts": {
- "version": "7.2.1",
- "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.2.1.tgz",
- "integrity": "sha512-v2JSGri6/HXSfoGIwuKEn8sNCQK6nsB2BNpy2lSX6QH9bsECrMv93QHnj5+f+1ZWpF/VNioIV2B/PDox8EvGuQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@inquirer/checkbox": "^4.0.4",
- "@inquirer/confirm": "^5.1.1",
- "@inquirer/editor": "^4.2.1",
- "@inquirer/expand": "^4.0.4",
- "@inquirer/input": "^4.1.1",
- "@inquirer/number": "^3.0.4",
- "@inquirer/password": "^4.0.4",
- "@inquirer/rawlist": "^4.0.4",
- "@inquirer/search": "^3.0.4",
- "@inquirer/select": "^4.0.4"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "@types/node": ">=18"
- }
- },
"node_modules/@angular-devkit/schematics-cli/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
@@ -325,9 +300,9 @@
}
},
"node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": {
- "version": "19.1.8",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.1.8.tgz",
- "integrity": "sha512-j1zHKvOsGwu5YwAZGuzi835R9vcW7PkfxmSRIJeVl+vawgk31K3zFb4UPH8AY/NPWYqXIAnwpka3HC1+JrWLWA==",
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.6.tgz",
+ "integrity": "sha512-WFgiYhrDMq83UNaGRAneIM7CYYdBozD+yYA9BjoU8AgBLKtrvn6S8ZcjKAk5heoHtY/u8pEb0mwDTz9gxFmJZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -716,9 +691,9 @@
}
},
"node_modules/@emnapi/runtime": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
- "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.0.tgz",
+ "integrity": "sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -1761,15 +1736,15 @@
}
},
"node_modules/@inquirer/checkbox": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.2.tgz",
- "integrity": "sha512-PL9ixC5YsPXzXhAZFUPmkXGxfgjkdfZdPEPPmt4kFwQ4LBMDG9n/nHXYRGGZSKZJs+d1sGKWgS2GiPzVRKUdtQ==",
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.5.tgz",
+ "integrity": "sha512-swPczVU+at65xa5uPfNP9u3qx/alNwiaykiI/ExpsmMSQW55trmZcwhYWzw/7fj+n6Q8z1eENvR7vFfq9oPSAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@inquirer/core": "^10.1.7",
- "@inquirer/figures": "^1.0.10",
- "@inquirer/type": "^3.0.4",
+ "@inquirer/core": "^10.1.10",
+ "@inquirer/figures": "^1.0.11",
+ "@inquirer/type": "^3.0.6",
"ansi-escapes": "^4.3.2",
"yoctocolors-cjs": "^2.1.2"
},
@@ -1786,14 +1761,14 @@
}
},
"node_modules/@inquirer/confirm": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.6.tgz",
- "integrity": "sha512-6ZXYK3M1XmaVBZX6FCfChgtponnL0R6I7k8Nu+kaoNkT828FVZTcca1MqmWQipaW2oNREQl5AaPCUOOCVNdRMw==",
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.9.tgz",
+ "integrity": "sha512-NgQCnHqFTjF7Ys2fsqK2WtnA8X1kHyInyG+nMIuHowVTIgIuS10T4AznI/PvbqSpJqjCUqNBlKGh1v3bwLFL4w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@inquirer/core": "^10.1.7",
- "@inquirer/type": "^3.0.4"
+ "@inquirer/core": "^10.1.10",
+ "@inquirer/type": "^3.0.6"
},
"engines": {
"node": ">=18"
@@ -1808,14 +1783,14 @@
}
},
"node_modules/@inquirer/core": {
- "version": "10.1.7",
- "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.7.tgz",
- "integrity": "sha512-AA9CQhlrt6ZgiSy6qoAigiA1izOa751ugX6ioSjqgJ+/Gd+tEN/TORk5sUYNjXuHWfW0r1n/a6ak4u/NqHHrtA==",
+ "version": "10.1.10",
+ "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.10.tgz",
+ "integrity": "sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@inquirer/figures": "^1.0.10",
- "@inquirer/type": "^3.0.4",
+ "@inquirer/figures": "^1.0.11",
+ "@inquirer/type": "^3.0.6",
"ansi-escapes": "^4.3.2",
"cli-width": "^4.1.0",
"mute-stream": "^2.0.0",
@@ -1836,14 +1811,14 @@
}
},
"node_modules/@inquirer/editor": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.7.tgz",
- "integrity": "sha512-gktCSQtnSZHaBytkJKMKEuswSk2cDBuXX5rxGFv306mwHfBPjg5UAldw9zWGoEyvA9KpRDkeM4jfrx0rXn0GyA==",
+ "version": "4.2.10",
+ "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.10.tgz",
+ "integrity": "sha512-5GVWJ+qeI6BzR6TIInLP9SXhWCEcvgFQYmcRG6d6RIlhFjM5TyG18paTGBgRYyEouvCmzeco47x9zX9tQEofkw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@inquirer/core": "^10.1.7",
- "@inquirer/type": "^3.0.4",
+ "@inquirer/core": "^10.1.10",
+ "@inquirer/type": "^3.0.6",
"external-editor": "^3.1.0"
},
"engines": {
@@ -1859,14 +1834,14 @@
}
},
"node_modules/@inquirer/expand": {
- "version": "4.0.9",
- "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.9.tgz",
- "integrity": "sha512-Xxt6nhomWTAmuSX61kVgglLjMEFGa+7+F6UUtdEUeg7fg4r9vaFttUUKrtkViYYrQBA5Ia1tkOJj2koP9BuLig==",
+ "version": "4.0.12",
+ "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.12.tgz",
+ "integrity": "sha512-jV8QoZE1fC0vPe6TnsOfig+qwu7Iza1pkXoUJ3SroRagrt2hxiL+RbM432YAihNR7m7XnU0HWl/WQ35RIGmXHw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@inquirer/core": "^10.1.7",
- "@inquirer/type": "^3.0.4",
+ "@inquirer/core": "^10.1.10",
+ "@inquirer/type": "^3.0.6",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
@@ -1882,9 +1857,9 @@
}
},
"node_modules/@inquirer/figures": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.10.tgz",
- "integrity": "sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw==",
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.11.tgz",
+ "integrity": "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1892,14 +1867,14 @@
}
},
"node_modules/@inquirer/input": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.6.tgz",
- "integrity": "sha512-1f5AIsZuVjPT4ecA8AwaxDFNHny/tSershP/cTvTDxLdiIGTeILNcKozB0LaYt6mojJLUbOYhpIxicaYf7UKIQ==",
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.9.tgz",
+ "integrity": "sha512-mshNG24Ij5KqsQtOZMgj5TwEjIf+F2HOESk6bjMwGWgcH5UBe8UoljwzNFHqdMbGYbgAf6v2wU/X9CAdKJzgOA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@inquirer/core": "^10.1.7",
- "@inquirer/type": "^3.0.4"
+ "@inquirer/core": "^10.1.10",
+ "@inquirer/type": "^3.0.6"
},
"engines": {
"node": ">=18"
@@ -1914,14 +1889,14 @@
}
},
"node_modules/@inquirer/number": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.9.tgz",
- "integrity": "sha512-iN2xZvH3tyIYXLXBvlVh0npk1q/aVuKXZo5hj+K3W3D4ngAEq/DkLpofRzx6oebTUhBvOgryZ+rMV0yImKnG3w==",
+ "version": "3.0.12",
+ "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.12.tgz",
+ "integrity": "sha512-7HRFHxbPCA4e4jMxTQglHJwP+v/kpFsCf2szzfBHy98Wlc3L08HL76UDiA87TOdX5fwj2HMOLWqRWv9Pnn+Z5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@inquirer/core": "^10.1.7",
- "@inquirer/type": "^3.0.4"
+ "@inquirer/core": "^10.1.10",
+ "@inquirer/type": "^3.0.6"
},
"engines": {
"node": ">=18"
@@ -1936,14 +1911,14 @@
}
},
"node_modules/@inquirer/password": {
- "version": "4.0.9",
- "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.9.tgz",
- "integrity": "sha512-xBEoOw1XKb0rIN208YU7wM7oJEHhIYkfG7LpTJAEW913GZeaoQerzf5U/LSHI45EVvjAdgNXmXgH51cUXKZcJQ==",
+ "version": "4.0.12",
+ "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.12.tgz",
+ "integrity": "sha512-FlOB0zvuELPEbnBYiPaOdJIaDzb2PmJ7ghi/SVwIHDDSQ2K4opGBkF+5kXOg6ucrtSUQdLhVVY5tycH0j0l+0g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@inquirer/core": "^10.1.7",
- "@inquirer/type": "^3.0.4",
+ "@inquirer/core": "^10.1.10",
+ "@inquirer/type": "^3.0.6",
"ansi-escapes": "^4.3.2"
},
"engines": {
@@ -1989,14 +1964,14 @@
}
},
"node_modules/@inquirer/rawlist": {
- "version": "4.0.9",
- "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.9.tgz",
- "integrity": "sha512-+5t6ebehKqgoxV8fXwE49HkSF2Rc9ijNiVGEQZwvbMI61/Q5RcD+jWD6Gs1tKdz5lkI8GRBL31iO0HjGK1bv+A==",
+ "version": "4.0.12",
+ "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.12.tgz",
+ "integrity": "sha512-wNPJZy8Oc7RyGISPxp9/MpTOqX8lr0r+lCCWm7hQra+MDtYRgINv1hxw7R+vKP71Bu/3LszabxOodfV/uTfsaA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@inquirer/core": "^10.1.7",
- "@inquirer/type": "^3.0.4",
+ "@inquirer/core": "^10.1.10",
+ "@inquirer/type": "^3.0.6",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
@@ -2012,15 +1987,15 @@
}
},
"node_modules/@inquirer/search": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.9.tgz",
- "integrity": "sha512-DWmKztkYo9CvldGBaRMr0ETUHgR86zE6sPDVOHsqz4ISe9o1LuiWfgJk+2r75acFclA93J/lqzhT0dTjCzHuoA==",
+ "version": "3.0.12",
+ "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.12.tgz",
+ "integrity": "sha512-H/kDJA3kNlnNIjB8YsaXoQI0Qccgf0Na14K1h8ExWhNmUg2E941dyFPrZeugihEa9AZNW5NdsD/NcvUME83OPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@inquirer/core": "^10.1.7",
- "@inquirer/figures": "^1.0.10",
- "@inquirer/type": "^3.0.4",
+ "@inquirer/core": "^10.1.10",
+ "@inquirer/figures": "^1.0.11",
+ "@inquirer/type": "^3.0.6",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
@@ -2036,15 +2011,15 @@
}
},
"node_modules/@inquirer/select": {
- "version": "4.0.9",
- "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.0.9.tgz",
- "integrity": "sha512-BpJyJe7Dkhv2kz7yG7bPSbJLQuu/rqyNlF1CfiiFeFwouegfH+zh13KDyt6+d9DwucKo7hqM3wKLLyJxZMO+Xg==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.1.1.tgz",
+ "integrity": "sha512-IUXzzTKVdiVNMA+2yUvPxWsSgOG4kfX93jOM4Zb5FgujeInotv5SPIJVeXQ+fO4xu7tW8VowFhdG5JRmmCyQ1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@inquirer/core": "^10.1.7",
- "@inquirer/figures": "^1.0.10",
- "@inquirer/type": "^3.0.4",
+ "@inquirer/core": "^10.1.10",
+ "@inquirer/figures": "^1.0.11",
+ "@inquirer/type": "^3.0.6",
"ansi-escapes": "^4.3.2",
"yoctocolors-cjs": "^2.1.2"
},
@@ -2061,9 +2036,9 @@
}
},
"node_modules/@inquirer/type": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.4.tgz",
- "integrity": "sha512-2MNFrDY8jkFYc9Il9DgLsHhMzuHnOYM1+CUYVWbzu9oT0hC7V7EcYvdCKeoll/Fcci04A+ERZ9wcc7cQ8lTkIA==",
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.6.tgz",
+ "integrity": "sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2411,22 +2386,22 @@
}
},
"node_modules/@nestjs/cli": {
- "version": "11.0.5",
- "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.5.tgz",
- "integrity": "sha512-ab/d8Ple+dMSQ4pC7RSNjhntpT8gFQQE8y/F/ilaplp7zPGpuxbayRtYbsA/wc1UkJHORDckrqFc8Jh8mrTq2A==",
+ "version": "11.0.6",
+ "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.6.tgz",
+ "integrity": "sha512-Xco8pTdWHCpTXPTYMkUGAE+C7JXvAv38oVUaQeL81o7UOAi39w8p456r+IjONN/7ekjzakWnqepDzuTtH5Xk5w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-devkit/core": "19.1.8",
- "@angular-devkit/schematics": "19.1.8",
- "@angular-devkit/schematics-cli": "19.1.8",
- "@inquirer/prompts": "7.3.2",
+ "@angular-devkit/core": "19.2.6",
+ "@angular-devkit/schematics": "19.2.6",
+ "@angular-devkit/schematics-cli": "19.2.6",
+ "@inquirer/prompts": "7.4.1",
"@nestjs/schematics": "^11.0.1",
- "ansis": "3.16.0",
+ "ansis": "3.17.0",
"chokidar": "4.0.3",
"cli-table3": "0.6.5",
"commander": "4.1.1",
- "fork-ts-checker-webpack-plugin": "9.0.2",
+ "fork-ts-checker-webpack-plugin": "9.1.0",
"glob": "11.0.1",
"node-emoji": "1.11.0",
"ora": "5.4.1",
@@ -2457,9 +2432,9 @@
}
},
"node_modules/@nestjs/cli/node_modules/@angular-devkit/core": {
- "version": "19.1.8",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.1.8.tgz",
- "integrity": "sha512-j1zHKvOsGwu5YwAZGuzi835R9vcW7PkfxmSRIJeVl+vawgk31K3zFb4UPH8AY/NPWYqXIAnwpka3HC1+JrWLWA==",
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.6.tgz",
+ "integrity": "sha512-WFgiYhrDMq83UNaGRAneIM7CYYdBozD+yYA9BjoU8AgBLKtrvn6S8ZcjKAk5heoHtY/u8pEb0mwDTz9gxFmJZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2484,6 +2459,36 @@
}
}
},
+ "node_modules/@nestjs/cli/node_modules/@inquirer/prompts": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.4.1.tgz",
+ "integrity": "sha512-UlmM5FVOZF0gpoe1PT/jN4vk8JmpIWBlMvTL8M+hlvPmzN89K6z03+IFmyeu/oFCenwdwHDr2gky7nIGSEVvlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/checkbox": "^4.1.5",
+ "@inquirer/confirm": "^5.1.9",
+ "@inquirer/editor": "^4.2.10",
+ "@inquirer/expand": "^4.0.12",
+ "@inquirer/input": "^4.1.9",
+ "@inquirer/number": "^3.0.12",
+ "@inquirer/password": "^4.0.12",
+ "@inquirer/rawlist": "^4.0.12",
+ "@inquirer/search": "^3.0.12",
+ "@inquirer/select": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@nestjs/cli/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
@@ -2573,9 +2578,9 @@
}
},
"node_modules/@nestjs/common": {
- "version": "11.0.12",
- "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.0.12.tgz",
- "integrity": "sha512-6PXxmDe2iYmb57xacnxzpW1NAxRZ7Gf+acMT7/hmRB/4KpZiFU/cNvLWwgbM2BL5QSzQulOwY6ny5bbKnPpB+A==",
+ "version": "11.0.13",
+ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.0.13.tgz",
+ "integrity": "sha512-cXqXJPQTcJIYqT8GtBYqjYY9sklCBqp/rh9z1R40E60gWnsU598YIQWkojSFRI9G7lT/+uF+jqSrg/CMPBk7QQ==",
"license": "MIT",
"dependencies": {
"iterare": "1.2.1",
@@ -2602,9 +2607,9 @@
}
},
"node_modules/@nestjs/core": {
- "version": "11.0.12",
- "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.0.12.tgz",
- "integrity": "sha512-micQrbh9iL0PuYVx2vsUojuNmMUyqoMCuj7eGAUhvjiZUh4DBLPdxYmJEayCT/equHSiw9vNC95Vm0JigVZ44g==",
+ "version": "11.0.13",
+ "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.0.13.tgz",
+ "integrity": "sha512-1xjrsYjff4sg4MfvF+/NInOq+7oI1D1vK8Yj9wkrbBH1dM+h2At71tccbFfl/eJUt4ckZlH+XmROnt/T0daYcA==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -2676,14 +2681,14 @@
}
},
"node_modules/@nestjs/platform-express": {
- "version": "11.0.12",
- "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.0.12.tgz",
- "integrity": "sha512-Jze6dY1q1BBAjFuPQT9CLjYFl5IxMSQQxD+xs6cV+4EIysHxgSFZMJqiTpknZTFgPneyp0zF1TtQAjxBshnwlg==",
+ "version": "11.0.13",
+ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.0.13.tgz",
+ "integrity": "sha512-SaxfIDORX1oV8T6nxr/pltnW2g+3fCRPs5YwO0jBj2d8sC03Axjwlxp/ASg2mf6xvOSBD6ZbhjVLVVDZymyFXQ==",
"license": "MIT",
"dependencies": {
"cors": "2.8.5",
- "express": "5.0.1",
- "multer": "1.4.5-lts.1",
+ "express": "5.1.0",
+ "multer": "1.4.5-lts.2",
"path-to-regexp": "8.2.0",
"tslib": "2.8.1"
},
@@ -2697,9 +2702,9 @@
}
},
"node_modules/@nestjs/platform-socket.io": {
- "version": "11.0.12",
- "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.0.12.tgz",
- "integrity": "sha512-/irtuxzIHQqUTMazQpAHQXv/Dz2/hS0EhX+ZS4e4CfF8f6ly+pEqOrP3TNY2NjDkYs8T+ulXyuKgfJvT9p+U9w==",
+ "version": "11.0.13",
+ "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.0.13.tgz",
+ "integrity": "sha512-51afyhv4FnCgm4WD+BbJqzie/jBTlTamaiaTrQE7Zw5eZ23jH/QPxj6QYV2gkaVAkXUrMDTGdOWhKszjOjR68Q==",
"license": "MIT",
"dependencies": {
"socket.io": "4.8.1",
@@ -2729,14 +2734,14 @@
}
},
"node_modules/@nestjs/schematics": {
- "version": "11.0.2",
- "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.2.tgz",
- "integrity": "sha512-C4KM3BHBG6tRX8t5UrHdUq8Y49asEfJUora/fBXge3UTAnxKGlXc20p5s2Q0Q1+l+1YaXqTrKGSIbYXdPX8r9g==",
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.3.tgz",
+ "integrity": "sha512-enz9Otg1GafzmtpDRB1bs44/kipVKzmoQoJ296rRQMZPivQUBxFlRSwrR+e1jB09n5UVqCf8tUAQnRzxBR5AKw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-devkit/core": "19.2.0",
- "@angular-devkit/schematics": "19.2.0",
+ "@angular-devkit/core": "19.2.6",
+ "@angular-devkit/schematics": "19.2.6",
"comment-json": "4.2.5",
"jsonc-parser": "3.3.1",
"pluralize": "8.0.0"
@@ -2746,9 +2751,9 @@
}
},
"node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": {
- "version": "19.2.0",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.0.tgz",
- "integrity": "sha512-qd2nYoHZOYWRsu4MjXG8KiDtfM9ZDRR2rDGa+rDZ3CYAsngCrPmqOebun10dncUjwAidX49P4S2U2elOmX3VYQ==",
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.6.tgz",
+ "integrity": "sha512-WFgiYhrDMq83UNaGRAneIM7CYYdBozD+yYA9BjoU8AgBLKtrvn6S8ZcjKAk5heoHtY/u8pEb0mwDTz9gxFmJZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2773,25 +2778,6 @@
}
}
},
- "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics": {
- "version": "19.2.0",
- "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.0.tgz",
- "integrity": "sha512-cGGqUGqBXIGJkeL65l70y0BflDAu/0Zi/ohbYat3hvadFfumRJnVElVfJ59JtWO7FfKQjxcwCVTyuQ/tevX/9A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@angular-devkit/core": "19.2.0",
- "jsonc-parser": "3.3.1",
- "magic-string": "0.30.17",
- "ora": "5.4.1",
- "rxjs": "7.8.1"
- },
- "engines": {
- "node": "^18.19.1 || ^20.11.1 || >=22.0.0",
- "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
- "yarn": ">= 1.13.0"
- }
- },
"node_modules/@nestjs/schematics/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
@@ -2871,9 +2857,9 @@
}
},
"node_modules/@nestjs/swagger": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.1.0.tgz",
- "integrity": "sha512-+GQ+q1ASTBvGi0DYHukWi8NVVVLszedwLLqHdLRnJh8rjokt8YTDb7roImvT/YMmYgPvaWBv/4JYdZH4FueLPQ==",
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.1.1.tgz",
+ "integrity": "sha512-k7jEiocSQ5bL6RSnEjQ1h4uT4fErgshWQIhaVjyvufIEyBfH0Fv0Q2lihH2QLqeDjBkrH5bW0Twbqf3SlLOwCw==",
"license": "MIT",
"dependencies": {
"@microsoft/tsdoc": "0.15.1",
@@ -2881,7 +2867,7 @@
"js-yaml": "4.1.0",
"lodash": "4.17.21",
"path-to-regexp": "8.2.0",
- "swagger-ui-dist": "5.20.1"
+ "swagger-ui-dist": "5.20.5"
},
"peerDependencies": {
"@fastify/static": "^8.0.0",
@@ -2904,9 +2890,9 @@
}
},
"node_modules/@nestjs/testing": {
- "version": "11.0.12",
- "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.0.12.tgz",
- "integrity": "sha512-jl1McTqrY+zRBFIWcFMVwesY2v++mAdHrrzXsLxatgkf6wRVh6te1MQ6LikgQ6qz4P5qzVV6EiXQVLGvARe5Xw==",
+ "version": "11.0.13",
+ "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.0.13.tgz",
+ "integrity": "sha512-9E9HxD3EmiQky+pqYvpV0cHKlxYJJqHm2GmXoKHF72Raa0JTfQpamnLl6TPjDy2XOqA7oSSBDnEwku8vZ46Cdw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2932,9 +2918,9 @@
}
},
"node_modules/@nestjs/websockets": {
- "version": "11.0.12",
- "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.0.12.tgz",
- "integrity": "sha512-2DCHKMdNGdjgj1/H3Rd323HHsSogjM3sZMjrSpWJIACDQTLtHFdNiUgGk5OAHniDIgLatzXDrnikfv6zmhPb+w==",
+ "version": "11.0.13",
+ "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.0.13.tgz",
+ "integrity": "sha512-QvWImf/2+UHzw+OCDkrdJ9y3sH4thcbHxCgTlr9EiGOR9z85M14IIHhLpx4fse0xAqHYw/FDyCOLpszwiiZnFA==",
"license": "MIT",
"dependencies": {
"iterare": "1.2.1",
@@ -4495,9 +4481,9 @@
}
},
"node_modules/@pkgr/core": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.0.tgz",
- "integrity": "sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==",
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.1.tgz",
+ "integrity": "sha512-VzgHzGblFmUeBmmrk55zPyrQIArQN4vujc9shWytaPdB3P7qhi0cpaiKIr7tlCmFv2lYUwnLospIqjL9ZSAhhg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4638,9 +4624,9 @@
}
},
"node_modules/@react-email/components": {
- "version": "0.0.34",
- "resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.34.tgz",
- "integrity": "sha512-9aUJJ4Yu5Cd5++2GHwdkmOHCghi0vPP/aZwMCGNNTovBTDCI3mc8YIUrDR7JfscrdkPK4s/E9AoD5lX6d/zITA==",
+ "version": "0.0.35",
+ "resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.35.tgz",
+ "integrity": "sha512-if1kLih4pfARgsXacs9eD9O3BVtRWxKRz1jjSWWiyk32eeFJLtWjBaoF8nsxQxk4w5nfqjAHVFBrxXQceB7xDQ==",
"license": "MIT",
"dependencies": {
"@react-email/body": "0.0.11",
@@ -4662,7 +4648,7 @@
"@react-email/row": "0.0.12",
"@react-email/section": "0.0.16",
"@react-email/tailwind": "1.0.4",
- "@react-email/text": "0.1.0"
+ "@react-email/text": "0.1.1"
},
"engines": {
"node": ">=18.0.0"
@@ -4861,9 +4847,9 @@
}
},
"node_modules/@react-email/text": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.0.tgz",
- "integrity": "sha512-LG+gEuxpoIiOojkv40iktP8UVjkJVZ+ksEEuf7zRvrcwLcVuzYyirlWdkGr4Vu/AhsD4FDRoxDWlWvLTx+WHUg==",
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.1.tgz",
+ "integrity": "sha512-Zo9tSEzkO3fODLVH1yVhzVCiwETfeEL5wU93jXKWo2DHoMuiZ9Iabaso3T0D0UjhrCB1PBMeq2YiejqeToTyIQ==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -5249,9 +5235,9 @@
"license": "MIT"
},
"node_modules/@swc/core": {
- "version": "1.11.15",
- "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.15.tgz",
- "integrity": "sha512-SqXjJrwydXA2OVVAFv9EdCb2kkhEM2+b4ajereGzFSQuK2FN/SlKPklvFMh9sj1sG0tgXwyLGSMgyn3FUx83DA==",
+ "version": "1.11.16",
+ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.16.tgz",
+ "integrity": "sha512-wgjrJqVUss8Lxqilg0vkiE0tkEKU3mZkoybQM1Ehy+PKWwwB6lFAwKi20cAEFlSSWo8jFR8hRo19ZELAoLDowg==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
@@ -5267,16 +5253,16 @@
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
- "@swc/core-darwin-arm64": "1.11.15",
- "@swc/core-darwin-x64": "1.11.15",
- "@swc/core-linux-arm-gnueabihf": "1.11.15",
- "@swc/core-linux-arm64-gnu": "1.11.15",
- "@swc/core-linux-arm64-musl": "1.11.15",
- "@swc/core-linux-x64-gnu": "1.11.15",
- "@swc/core-linux-x64-musl": "1.11.15",
- "@swc/core-win32-arm64-msvc": "1.11.15",
- "@swc/core-win32-ia32-msvc": "1.11.15",
- "@swc/core-win32-x64-msvc": "1.11.15"
+ "@swc/core-darwin-arm64": "1.11.16",
+ "@swc/core-darwin-x64": "1.11.16",
+ "@swc/core-linux-arm-gnueabihf": "1.11.16",
+ "@swc/core-linux-arm64-gnu": "1.11.16",
+ "@swc/core-linux-arm64-musl": "1.11.16",
+ "@swc/core-linux-x64-gnu": "1.11.16",
+ "@swc/core-linux-x64-musl": "1.11.16",
+ "@swc/core-win32-arm64-msvc": "1.11.16",
+ "@swc/core-win32-ia32-msvc": "1.11.16",
+ "@swc/core-win32-x64-msvc": "1.11.16"
},
"peerDependencies": {
"@swc/helpers": "*"
@@ -5288,9 +5274,9 @@
}
},
"node_modules/@swc/core-darwin-arm64": {
- "version": "1.11.15",
- "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.15.tgz",
- "integrity": "sha512-mMoQy6TrYrvhrpi70eD01uu4WeB+Wy+9To5b95gHcyiAMRyd7afnFHo9OcPynk0Ep01PvReiB6hL2hYfNcDKvw==",
+ "version": "1.11.16",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.16.tgz",
+ "integrity": "sha512-l6uWMU+MUdfLHCl3dJgtVEdsUHPskoA4BSu0L1hh9SGBwPZ8xeOz8iLIqZM27lTuXxL4KsYH6GQR/OdQ/vhLtg==",
"cpu": [
"arm64"
],
@@ -5305,9 +5291,9 @@
}
},
"node_modules/@swc/core-darwin-x64": {
- "version": "1.11.15",
- "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.15.tgz",
- "integrity": "sha512-yBWcP5v3OXq1Nxamqh1+qecty3TFRlxAMNXMBzq/Rv6Fu9eOAU6lTSfozO0BaOoETTzorlR2/3Jn+3amyviQMw==",
+ "version": "1.11.16",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.16.tgz",
+ "integrity": "sha512-TH0IW8Ao1WZ4ARFHIh29dAQHYBEl4YnP74n++rjppmlCjY+8v3s5nXMA7IqxO3b5LVHyggWtU4+46DXTyMJM7g==",
"cpu": [
"x64"
],
@@ -5322,9 +5308,9 @@
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
- "version": "1.11.15",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.15.tgz",
- "integrity": "sha512-OprUQ0AvIiA2FCZqDYcnZ1nZhiCABqJPGgC9KwX8p8tC+t1mYkAeboik23S9gxzwGQImMNYYojGbNGTmLATLrA==",
+ "version": "1.11.16",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.16.tgz",
+ "integrity": "sha512-2IxD9t09oNZrbv37p4cJ9cTHMUAK6qNiShi9s2FJ9LcqSnZSN4iS4hvaaX6KZuG54d58vWnMU7yycjkdOTQcMg==",
"cpu": [
"arm"
],
@@ -5339,9 +5325,9 @@
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
- "version": "1.11.15",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.15.tgz",
- "integrity": "sha512-Uq3FjjKEw1CTtFpz7Mi+CC//4KQODQ8vXFx7J/cBO6nj+/Os9J1huyqa1LljlBTCeDXTpeC7qlqO6swZ0HPaJw==",
+ "version": "1.11.16",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.16.tgz",
+ "integrity": "sha512-AYkN23DOiPh1bf3XBf/xzZQDKSsgZTxlbyTyUIhprLJpAAAT0ZCGAUcS5mHqydk0nWQ13ABUymodvHoroutNzw==",
"cpu": [
"arm64"
],
@@ -5356,9 +5342,9 @@
}
},
"node_modules/@swc/core-linux-arm64-musl": {
- "version": "1.11.15",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.15.tgz",
- "integrity": "sha512-G5orst6QzXyTXgOTnjrkYaLaK3emMXBWkQ7CDFyZNCGo6Fztn0vzYcCmr31Cvqs66BsM0sdGbcrBd5br8g/pJg==",
+ "version": "1.11.16",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.16.tgz",
+ "integrity": "sha512-n/nWXDRCIhM51dDGELfBcTMNnCiFatE7LDvsbYxb7DJt1HGjaCNvHHCKURb/apJTh/YNtWfgFap9dbsTgw8yPA==",
"cpu": [
"arm64"
],
@@ -5373,9 +5359,9 @@
}
},
"node_modules/@swc/core-linux-x64-gnu": {
- "version": "1.11.15",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.15.tgz",
- "integrity": "sha512-T0iR9yUcGyo1yLudL73jKbPS4AYo2iAWWH2I9u7QYiRTXPduwkH0nETNr+nsWBsYdMu+H2g169rCiGhhx6FPHw==",
+ "version": "1.11.16",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.16.tgz",
+ "integrity": "sha512-xr182YQrF47n7Awxj+/ruI21bYw+xO/B26KFVnb+i3ezF9NOhqoqTX+33RL1ZLA/uFTq8ksPZO/y+ZVS/odtQA==",
"cpu": [
"x64"
],
@@ -5390,9 +5376,9 @@
}
},
"node_modules/@swc/core-linux-x64-musl": {
- "version": "1.11.15",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.15.tgz",
- "integrity": "sha512-2d8pHehwsHdQ71PRLeJ/XM69t5LCMzf1KZQDTVJTOSWRbuKGArtD+md5lVzTu458gt+JawdUgFdkdHtF7ke0AA==",
+ "version": "1.11.16",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.16.tgz",
+ "integrity": "sha512-k2JBfiwWfXCIKrBRjFO9/vEdLSYq0QLJ+iNSLdfrejZ/aENNkbEg8O7O2GKUSb30RBacn6k8HMfJrcPLFiEyCQ==",
"cpu": [
"x64"
],
@@ -5407,9 +5393,9 @@
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
- "version": "1.11.15",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.15.tgz",
- "integrity": "sha512-Vz5xg03VdYftMvruvziV1doU7B64rQ8rw72bKf2+yflt1gU7BlLk4DPu2IZlUc0Xk8lrVcEDiheXATbHexKsmw==",
+ "version": "1.11.16",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.16.tgz",
+ "integrity": "sha512-taOb5U+abyEhQgex+hr6cI48BoqSvSdfmdirWcxprIEUBHCxa1dSriVwnJRAJOFI9T+5BEz88by6rgbB9MjbHA==",
"cpu": [
"arm64"
],
@@ -5424,9 +5410,9 @@
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
- "version": "1.11.15",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.15.tgz",
- "integrity": "sha512-R9jS92ubQgHQfyNVCMnuQfNPeBgAs3QaWC+DqPbhXtOyWUdSGcImbHMDCxShDj+nn8J7bPeb7L4sZqr6gBkZnQ==",
+ "version": "1.11.16",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.16.tgz",
+ "integrity": "sha512-b7yYggM9LBDiMY+XUt5kYWvs5sn0U3PXSOGvF3CbLufD/N/YQiDcYON2N3lrWHYL8aYnwbuZl45ojmQHSQPcdA==",
"cpu": [
"ia32"
],
@@ -5441,9 +5427,9 @@
}
},
"node_modules/@swc/core-win32-x64-msvc": {
- "version": "1.11.15",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.15.tgz",
- "integrity": "sha512-UpSX492qVVTJQkRBYw3qC49ae4QRHwuC1cDgA47XBP0l31vjR83r3qEYue1Nn173etzGzbDJnygyLpqv/ieCCA==",
+ "version": "1.11.16",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.16.tgz",
+ "integrity": "sha512-/ibq/YDc3B5AROkpOKPGxVkSyCKOg+ml8k11RxrW7FAPy6a9y5y9KPcWIqV74Ahq4RuaMNslTQqHWAGSm0xJsQ==",
"cpu": [
"x64"
],
@@ -5483,23 +5469,23 @@
}
},
"node_modules/@testcontainers/postgresql": {
- "version": "10.23.0",
- "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.23.0.tgz",
- "integrity": "sha512-PKuv7cSWxOxW4aOEuw1XyYb7tS8rcPEmg2ez97WTLLnVZj4JaoPJqFDSEJ2OSj8s3+6HqLC6hXDCMFmYhP63/A==",
+ "version": "10.24.0",
+ "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.24.0.tgz",
+ "integrity": "sha512-vRgwRxblOMhzNrUOmiXjvjOn8efqI3eyDT4KLh5kgmpGjE+Wz5LtCrhTmT4hMv5KPeZmftx+1OhQQrfyBqSvtg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "testcontainers": "^10.23.0"
+ "testcontainers": "^10.24.0"
}
},
"node_modules/@testcontainers/redis": {
- "version": "10.23.0",
- "resolved": "https://registry.npmjs.org/@testcontainers/redis/-/redis-10.23.0.tgz",
- "integrity": "sha512-rMkEdCsjhAPFuagHfI28q/Uvq6Wj/uN0qJxa6bwvenc6qhbzqYlK8iguj3M/cYs5ItDgxZ9J6HxhZKzkc3U1iQ==",
+ "version": "10.24.0",
+ "resolved": "https://registry.npmjs.org/@testcontainers/redis/-/redis-10.24.0.tgz",
+ "integrity": "sha512-Il3PqqgwEPRGkpVzX9BxtIzWKTHbiOixD3bYQvsgYqE5upLE1FsTnj62SgCI86/Db2MXHQ+XS0rLncnBP2lfKg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "testcontainers": "^10.23.0"
+ "testcontainers": "^10.24.0"
}
},
"node_modules/@turf/boolean-point-in-polygon": {
@@ -5912,9 +5898,9 @@
"license": "MIT"
},
"node_modules/@types/react": {
- "version": "19.0.12",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.12.tgz",
- "integrity": "sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA==",
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.0.tgz",
+ "integrity": "sha512-UaicktuQI+9UKyA4njtDOGBD/67t8YEBt2xdfqu8+gP9hqPUPsiXlNPcpS2gVdjmis5GKPG3fCxbQLVgxsQZ8w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6823,9 +6809,9 @@
}
},
"node_modules/ansis": {
- "version": "3.16.0",
- "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.16.0.tgz",
- "integrity": "sha512-sU7d/tfZiYrsIAXbdL/CNZld5bCkruzwT5KmqmadCJYxuLxHAOBjidxD5+iLmN/6xEfjcQq1l7OpsiCBlc4LzA==",
+ "version": "3.17.0",
+ "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz",
+ "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==",
"license": "ISC",
"engines": {
"node": ">=14"
@@ -7298,16 +7284,16 @@
}
},
"node_modules/body-parser": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz",
- "integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
+ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.0",
"http-errors": "^2.0.0",
- "iconv-lite": "^0.5.2",
+ "iconv-lite": "^0.6.3",
"on-finished": "^2.4.1",
"qs": "^6.14.0",
"raw-body": "^3.0.0",
@@ -7317,21 +7303,6 @@
"node": ">=18"
}
},
- "node_modules/body-parser/node_modules/qs": {
- "version": "6.14.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
- "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "side-channel": "^1.1.0"
- },
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -8419,16 +8390,6 @@
"node": ">= 0.8"
}
},
- "node_modules/destroy": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
- "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
- }
- },
"node_modules/detect-europe-js": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/detect-europe-js/-/detect-europe-js-0.1.2.tgz",
@@ -9100,14 +9061,14 @@
}
},
"node_modules/eslint-plugin-prettier": {
- "version": "5.2.5",
- "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.5.tgz",
- "integrity": "sha512-IKKP8R87pJyMl7WWamLgPkloB16dagPIdd2FjBDbyRYPKo93wS/NbCOPh6gH+ieNLC+XZrhJt/kWj0PS/DFdmg==",
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz",
+ "integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
- "synckit": "^0.10.2"
+ "synckit": "^0.11.0"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -9412,46 +9373,45 @@
}
},
"node_modules/express": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz",
- "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
- "body-parser": "^2.0.1",
+ "body-parser": "^2.2.0",
"content-disposition": "^1.0.0",
- "content-type": "~1.0.4",
- "cookie": "0.7.1",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
- "debug": "4.3.6",
- "depd": "2.0.0",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "finalhandler": "^2.0.0",
- "fresh": "2.0.0",
- "http-errors": "2.0.0",
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
- "methods": "~1.1.2",
"mime-types": "^3.0.0",
- "on-finished": "2.4.1",
- "once": "1.4.0",
- "parseurl": "~1.3.3",
- "proxy-addr": "~2.0.7",
- "qs": "6.13.0",
- "range-parser": "~1.2.1",
- "router": "^2.0.0",
- "safe-buffer": "5.2.1",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
"send": "^1.1.0",
- "serve-static": "^2.1.0",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "type-is": "^2.0.0",
- "utils-merge": "1.0.1",
- "vary": "~1.1.2"
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/express/node_modules/cookie": {
@@ -9472,29 +9432,6 @@
"node": ">=6.6.0"
}
},
- "node_modules/express/node_modules/debug": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
- "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
- "license": "MIT",
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/express/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "license": "MIT"
- },
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -9792,15 +9729,15 @@
}
},
"node_modules/fork-ts-checker-webpack-plugin": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz",
- "integrity": "sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==",
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz",
+ "integrity": "sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.16.7",
"chalk": "^4.1.2",
- "chokidar": "^3.5.3",
+ "chokidar": "^4.0.1",
"cosmiconfig": "^8.2.0",
"deepmerge": "^4.2.2",
"fs-extra": "^10.0.0",
@@ -9812,14 +9749,43 @@
"tapable": "^2.2.1"
},
"engines": {
- "node": ">=12.13.0",
- "yarn": ">=1.0.0"
+ "node": ">=14.21.3"
},
"peerDependencies": {
"typescript": ">3.6.0",
"webpack": "^5.11.0"
}
},
+ "node_modules/fork-ts-checker-webpack-plugin/node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/fork-ts-checker-webpack-plugin/node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
@@ -10593,12 +10559,12 @@
}
},
"node_modules/iconv-lite": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz",
- "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==",
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
@@ -11791,15 +11757,6 @@
"node": ">= 8"
}
},
- "node_modules/methods": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -11826,21 +11783,21 @@
}
},
"node_modules/mime-db": {
- "version": "1.53.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz",
- "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz",
- "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
- "mime-db": "^1.53.0"
+ "mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
@@ -12009,9 +11966,9 @@
}
},
"node_modules/multer": {
- "version": "1.4.5-lts.1",
- "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
- "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
+ "version": "1.4.5-lts.2",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
+ "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
"license": "MIT",
"dependencies": {
"append-field": "^1.0.0",
@@ -13563,12 +13520,12 @@
}
},
"node_modules/qs": {
- "version": "6.13.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
- "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
"license": "BSD-3-Clause",
"dependencies": {
- "side-channel": "^1.0.6"
+ "side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
@@ -13652,18 +13609,6 @@
"node": ">= 0.8"
}
},
- "node_modules/raw-body/node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
@@ -14354,11 +14299,13 @@
}
},
"node_modules/router": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz",
- "integrity": "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
@@ -14507,19 +14454,18 @@
}
},
"node_modules/send": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz",
- "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.5",
- "destroy": "^1.2.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
- "fresh": "^0.5.2",
+ "fresh": "^2.0.0",
"http-errors": "^2.0.0",
- "mime-types": "^2.1.35",
+ "mime-types": "^3.0.1",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
@@ -14529,36 +14475,6 @@
"node": ">= 18"
}
},
- "node_modules/send/node_modules/fresh": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/send/node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/send/node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/serialize-javascript": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
@@ -14570,15 +14486,15 @@
}
},
"node_modules/serve-static": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz",
- "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
"license": "MIT",
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
- "send": "^1.0.0"
+ "send": "^1.2.0"
},
"engines": {
"node": ">= 18"
@@ -15559,9 +15475,9 @@
}
},
"node_modules/swagger-ui-dist": {
- "version": "5.20.1",
- "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.1.tgz",
- "integrity": "sha512-qBPCis2w8nP4US7SvUxdJD3OwKcqiWeZmjN2VWhq2v+ESZEXOP/7n4DeiOiiZcGYTKMHAHUUrroHaTsjUWTEGw==",
+ "version": "5.20.5",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.5.tgz",
+ "integrity": "sha512-7DqzFVHAW5MRhmWRDgd2Xr7RQUGaJv+7RfGmwChlOxz+tMLBmvHDz3vuVgaoj2CWNpTHxIm8aTsCBeJVxNrpjA==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
@@ -15578,20 +15494,20 @@
}
},
"node_modules/synckit": {
- "version": "0.10.3",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.10.3.tgz",
- "integrity": "sha512-R1urvuyiTaWfeCggqEvpDJwAlDVdsT9NM+IP//Tk2x7qHCkSvBk/fwFgw/TLAHzZlrAnnazMcRw0ZD8HlYFTEQ==",
+ "version": "0.11.3",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.3.tgz",
+ "integrity": "sha512-szhWDqNNI9etJUvbZ1/cx1StnZx8yMmFxme48SwR4dty4ioSY50KEZlpv0qAfgc1fpRzuh9hBXEzoCpJ779dLg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@pkgr/core": "^0.2.0",
+ "@pkgr/core": "^0.2.1",
"tslib": "^2.8.1"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
- "url": "https://opencollective.com/unts"
+ "url": "https://opencollective.com/synckit"
}
},
"node_modules/systeminformation": {
@@ -16050,9 +15966,9 @@
}
},
"node_modules/testcontainers": {
- "version": "10.23.0",
- "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.23.0.tgz",
- "integrity": "sha512-sZeij9mAyR9ixlaAmxU/DNb5LQ2duGCBDVjLaI975QGsX3sWatsBMDr4rqnP3IBemLynp+azZBMEfw75YsXMMg==",
+ "version": "10.24.0",
+ "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.24.0.tgz",
+ "integrity": "sha512-akkNb3LO2IhxnJzl5kj6dDt2c5q0bWHSTUSLSsqqLuZkaJTYCyWCE76uSzJLGpCkASV7Bw4XOOKvn4Tu0GHeFA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -16068,7 +15984,7 @@
"proper-lockfile": "^4.1.2",
"properties-reader": "^2.3.0",
"ssh-remote-port-forward": "^1.0.4",
- "tar-fs": "^3.0.6",
+ "tar-fs": "^3.0.7",
"tmp": "^0.2.3",
"undici": "^5.28.5"
}
@@ -16356,9 +16272,9 @@
}
},
"node_modules/type-is": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz",
- "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
@@ -16376,24 +16292,24 @@
"license": "MIT"
},
"node_modules/typeorm": {
- "version": "0.3.21",
- "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.21.tgz",
- "integrity": "sha512-lh4rUWl1liZGjyPTWpwcK8RNI5x4ekln+/JJOox1wCd7xbucYDOXWD+1cSzTN3L0wbTGxxOtloM5JlxbOxEufA==",
+ "version": "0.3.22",
+ "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.22.tgz",
+ "integrity": "sha512-P/Tsz3UpJ9+K0oryC0twK5PO27zejLYYwMsE8SISfZc1lVHX+ajigiOyWsKbuXpEFMjD9z7UjLzY3+ElVOMMDA==",
"license": "MIT",
"dependencies": {
"@sqltools/formatter": "^1.2.5",
- "ansis": "^3.9.0",
+ "ansis": "^3.17.0",
"app-root-path": "^3.1.0",
"buffer": "^6.0.3",
- "dayjs": "^1.11.9",
- "debug": "^4.3.4",
- "dotenv": "^16.0.3",
+ "dayjs": "^1.11.13",
+ "debug": "^4.4.0",
+ "dotenv": "^16.4.7",
"glob": "^10.4.5",
"sha.js": "^2.4.11",
"sql-highlight": "^6.0.0",
- "tslib": "^2.5.0",
- "uuid": "^11.0.5",
- "yargs": "^17.6.2"
+ "tslib": "^2.8.1",
+ "uuid": "^11.1.0",
+ "yargs": "^17.7.2"
},
"bin": {
"typeorm": "cli.js",
@@ -16407,12 +16323,12 @@
"url": "https://opencollective.com/typeorm"
},
"peerDependencies": {
- "@google-cloud/spanner": "^5.18.0",
+ "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0",
"@sap/hana-client": "^2.12.25",
"better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
"hdb-pool": "^0.1.6",
"ioredis": "^5.0.4",
- "mongodb": "^5.8.0",
+ "mongodb": "^5.8.0 || ^6.0.0",
"mssql": "^9.1.1 || ^10.0.1 || ^11.0.1",
"mysql2": "^2.2.5 || ^3.0.1",
"oracledb": "^6.3.0",
@@ -16424,7 +16340,7 @@
"sql.js": "^1.4.0",
"sqlite3": "^5.0.3",
"ts-node": "^10.7.0",
- "typeorm-aurora-data-api-driver": "^2.0.0"
+ "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0"
},
"peerDependenciesMeta": {
"@google-cloud/spanner": {
@@ -16854,15 +16770,6 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
- "node_modules/utils-merge": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4.0"
- }
- },
"node_modules/utimes": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/utimes/-/utimes-5.2.1.tgz",
diff --git a/server/package.json b/server/package.json
index d6c782b2ac..257258234c 100644
--- a/server/package.json
+++ b/server/package.json
@@ -49,7 +49,7 @@
"@opentelemetry/context-async-hooks": "^2.0.0",
"@opentelemetry/exporter-prometheus": "^0.200.0",
"@opentelemetry/sdk-node": "^0.200.0",
- "@react-email/components": "^0.0.34",
+ "@react-email/components": "^0.0.35",
"@socket.io/redis-adapter": "^8.3.0",
"archiver": "^7.0.0",
"async-lock": "^1.4.0",
diff --git a/server/src/database.ts b/server/src/database.ts
index 7fd791c59c..45e7cad490 100644
--- a/server/src/database.ts
+++ b/server/src/database.ts
@@ -1,5 +1,5 @@
-import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
-import { AssetStatus, AssetType, Permission, UserStatus } from 'src/enum';
+import { AssetStatus, AssetType, MemoryType, Permission, UserStatus } from 'src/enum';
+import { OnThisDayData, UserMetadataItem } from 'src/types';
export type AuthUser = {
id: string;
@@ -29,6 +29,19 @@ export type AuthApiKey = {
permissions: Permission[];
};
+export type Activity = {
+ id: string;
+ createdAt: Date;
+ updatedAt: Date;
+ albumId: string;
+ userId: string;
+ user: User;
+ assetId: string | null;
+ comment: string | null;
+ isLiked: boolean;
+ updateId: string;
+};
+
export type ApiKey = {
id: string;
name: string;
@@ -38,6 +51,31 @@ export type ApiKey = {
permissions: Permission[];
};
+export type Tag = {
+ id: string;
+ value: string;
+ createdAt: Date;
+ updatedAt: Date;
+ color: string | null;
+ parentId: string | null;
+};
+
+export type Memory = {
+ id: string;
+ createdAt: Date;
+ updatedAt: Date;
+ deletedAt: Date | null;
+ memoryAt: Date;
+ seenAt: Date | null;
+ showAt: Date | null;
+ hideAt: Date | null;
+ type: MemoryType;
+ data: OnThisDayData;
+ ownerId: string;
+ isSaved: boolean;
+ assets: Asset[];
+};
+
export type User = {
id: string;
name: string;
@@ -57,7 +95,7 @@ export type UserAdmin = User & {
quotaSizeInBytes: number | null;
quotaUsageInBytes: number;
status: UserStatus;
- metadata: UserMetadataEntity[];
+ metadata: UserMetadataItem[];
};
export type Asset = {
@@ -92,6 +130,13 @@ export type Asset = {
type: AssetType;
};
+export type SidecarWriteAsset = {
+ id: string;
+ sidecarPath: string | null;
+ originalPath: string;
+ tags: Array<{ value: string }>;
+};
+
export type AuthSharedLink = {
id: string;
expiresAt: Date | null;
@@ -117,6 +162,28 @@ export type Partner = {
inTimeline: boolean;
};
+export type Place = {
+ admin1Code: string | null;
+ admin1Name: string | null;
+ admin2Code: string | null;
+ admin2Name: string | null;
+ alternateNames: string | null;
+ countryCode: string;
+ id: number;
+ latitude: number;
+ longitude: number;
+ modificationDate: Date;
+ name: string;
+};
+
+export type Session = {
+ id: string;
+ createdAt: Date;
+ updatedAt: Date;
+ deviceOS: string;
+ deviceType: string;
+};
+
const userColumns = ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'] as const;
export const columns = {
@@ -140,6 +207,7 @@ export const columns = {
'shared_links.password',
],
user: userColumns,
+ userWithPrefix: ['users.id', 'users.name', 'users.email', 'users.profileImagePath', 'users.profileChangedAt'],
userAdmin: [
...userColumns,
'createdAt',
diff --git a/server/src/db.d.ts b/server/src/db.d.ts
index ca6d1813e4..727b0d51e4 100644
--- a/server/src/db.d.ts
+++ b/server/src/db.d.ts
@@ -4,7 +4,18 @@
*/
import type { ColumnType } from 'kysely';
-import { AssetType, MemoryType, Permission, SyncEntityType } from 'src/enum';
+import {
+ AlbumUserRole,
+ AssetFileType,
+ AssetOrder,
+ AssetStatus,
+ AssetType,
+ MemoryType,
+ Permission,
+ SharedLinkType,
+ SourceType,
+ SyncEntityType,
+} from 'src/enum';
import { UserTable } from 'src/schema/tables/user.table';
import { OnThisDayData } from 'src/types';
@@ -12,8 +23,6 @@ export type ArrayType = ArrayTypeImpl extends (infer U)[] ? U[] : ArrayTyp
export type ArrayTypeImpl = T extends ColumnType ? ColumnType : T[];
-export type AssetsStatusEnum = 'active' | 'deleted' | 'trashed';
-
export type Generated =
T extends ColumnType ? ColumnType : ColumnType;
@@ -31,8 +40,6 @@ export type JsonPrimitive = boolean | number | string | null;
export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
-export type Sourcetype = 'exif' | 'machine-learning' | 'manual';
-
export type Timestamp = ColumnType;
export interface Activity {
@@ -58,7 +65,7 @@ export interface Albums {
description: Generated;
id: Generated;
isActivityEnabled: Generated;
- order: Generated;
+ order: Generated;
ownerId: string;
updatedAt: Generated;
updateId: Generated;
@@ -72,7 +79,7 @@ export interface AlbumsAssetsAssets {
export interface AlbumsSharedUsersUsers {
albumsId: string;
- role: Generated;
+ role: Generated;
usersId: string;
}
@@ -98,7 +105,7 @@ export interface AssetFaces {
imageHeight: Generated;
imageWidth: Generated;
personId: string | null;
- sourceType: Generated;
+ sourceType: Generated;
}
export interface AssetFiles {
@@ -106,7 +113,7 @@ export interface AssetFiles {
createdAt: Generated;
id: Generated;
path: string;
- type: string;
+ type: AssetFileType;
updatedAt: Generated;
updateId: Generated;
}
@@ -152,7 +159,7 @@ export interface Assets {
ownerId: string;
sidecarPath: string | null;
stackId: string | null;
- status: Generated;
+ status: Generated;
thumbhash: Buffer | null;
type: AssetType;
updatedAt: Generated;
@@ -350,7 +357,7 @@ export interface SharedLinks {
key: Buffer;
password: string | null;
showExif: Generated;
- type: string;
+ type: SharedLinkType;
userId: string;
}
diff --git a/server/src/dtos/activity.dto.ts b/server/src/dtos/activity.dto.ts
index 9a0307f46b..a97116cf35 100644
--- a/server/src/dtos/activity.dto.ts
+++ b/server/src/dtos/activity.dto.ts
@@ -1,8 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty, IsString, ValidateIf } from 'class-validator';
+import { Activity } from 'src/database';
import { mapUser, UserResponseDto } from 'src/dtos/user.dto';
-import { UserEntity } from 'src/entities/user.entity';
-import { ActivityItem } from 'src/types';
import { Optional, ValidateUUID } from 'src/validation';
export enum ReactionType {
@@ -68,13 +67,13 @@ export class ActivityCreateDto extends ActivityDto {
comment?: string;
}
-export const mapActivity = (activity: ActivityItem): ActivityResponseDto => {
+export const mapActivity = (activity: Activity): ActivityResponseDto => {
return {
id: activity.id,
assetId: activity.assetId,
createdAt: activity.createdAt,
comment: activity.comment,
type: activity.isLiked ? ReactionType.LIKE : ReactionType.COMMENT,
- user: mapUser(activity.user as unknown as UserEntity),
+ user: mapUser(activity.user),
};
};
diff --git a/server/src/dtos/memory.dto.ts b/server/src/dtos/memory.dto.ts
index 36f4631ef5..b3054d7a4c 100644
--- a/server/src/dtos/memory.dto.ts
+++ b/server/src/dtos/memory.dto.ts
@@ -1,11 +1,11 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsEnum, IsInt, IsObject, IsPositive, ValidateNested } from 'class-validator';
+import { Memory } from 'src/database';
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { AssetEntity } from 'src/entities/asset.entity';
import { MemoryType } from 'src/enum';
-import { MemoryItem } from 'src/types';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
class MemoryBaseDto {
@@ -89,7 +89,7 @@ export class MemoryResponseDto {
assets!: AssetResponseDto[];
}
-export const mapMemory = (entity: MemoryItem, auth: AuthDto): MemoryResponseDto => {
+export const mapMemory = (entity: Memory, auth: AuthDto): MemoryResponseDto => {
return {
id: entity.id,
createdAt: entity.createdAt,
diff --git a/server/src/dtos/search.dto.ts b/server/src/dtos/search.dto.ts
index 67ab059e11..4330d2a6bf 100644
--- a/server/src/dtos/search.dto.ts
+++ b/server/src/dtos/search.dto.ts
@@ -1,6 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsEnum, IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
+import { Place } from 'src/database';
import { PropertyLifecycle } from 'src/decorators';
import { AlbumResponseDto } from 'src/dtos/album.dto';
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
@@ -233,9 +234,12 @@ export function mapPlaces(place: SearchPlacesItem): PlacesResponseDto {
longitude: place.longitude,
admin1name: place.admin1Name ?? undefined,
admin2name: place.admin2Name ?? undefined,
+ admin1name: place.admin1Name ?? undefined,
+ admin2name: place.admin2Name ?? undefined,
};
}
+
export enum SearchSuggestionType {
COUNTRY = 'country',
STATE = 'state',
diff --git a/server/src/dtos/session.dto.ts b/server/src/dtos/session.dto.ts
index dab1bf62b5..b54264a5b4 100644
--- a/server/src/dtos/session.dto.ts
+++ b/server/src/dtos/session.dto.ts
@@ -1,4 +1,4 @@
-import { SessionItem } from 'src/types';
+import { Session } from 'src/database';
export class SessionResponseDto {
id!: string;
@@ -9,7 +9,7 @@ export class SessionResponseDto {
deviceOS!: string;
}
-export const mapSession = (entity: SessionItem, currentId?: string): SessionResponseDto => ({
+export const mapSession = (entity: Session, currentId?: string): SessionResponseDto => ({
id: entity.id,
createdAt: entity.createdAt.toISOString(),
updatedAt: entity.updatedAt.toISOString(),
diff --git a/server/src/dtos/tag.dto.ts b/server/src/dtos/tag.dto.ts
index e62cf21636..a35801d07e 100644
--- a/server/src/dtos/tag.dto.ts
+++ b/server/src/dtos/tag.dto.ts
@@ -1,7 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsHexColor, IsNotEmpty, IsString } from 'class-validator';
-import { TagEntity } from 'src/entities/tag.entity';
-import { TagItem } from 'src/types';
+import { Tag } from 'src/database';
import { Optional, ValidateHexColor, ValidateUUID } from 'src/validation';
export class TagCreateDto {
@@ -52,7 +51,7 @@ export class TagResponseDto {
color?: string;
}
-export function mapTag(entity: TagItem | TagEntity): TagResponseDto {
+export function mapTag(entity: Tag): TagResponseDto {
return {
id: entity.id,
parentId: entity.parentId ?? undefined,
diff --git a/server/src/dtos/user-preferences.dto.ts b/server/src/dtos/user-preferences.dto.ts
index 5a393a2d71..fe92838fdb 100644
--- a/server/src/dtos/user-preferences.dto.ts
+++ b/server/src/dtos/user-preferences.dto.ts
@@ -1,8 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsDateString, IsEnum, IsInt, IsPositive, ValidateNested } from 'class-validator';
-import { UserPreferences } from 'src/entities/user-metadata.entity';
import { UserAvatarColor } from 'src/enum';
+import { UserPreferences } from 'src/types';
import { Optional, ValidateBoolean } from 'src/validation';
class AvatarUpdate {
diff --git a/server/src/dtos/user.dto.ts b/server/src/dtos/user.dto.ts
index afcd13f0e9..851d4d3921 100644
--- a/server/src/dtos/user.dto.ts
+++ b/server/src/dtos/user.dto.ts
@@ -2,9 +2,9 @@ import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { IsBoolean, IsEmail, IsNotEmpty, IsNumber, IsString, Min } from 'class-validator';
import { User, UserAdmin } from 'src/database';
-import { UserMetadataEntity, UserMetadataItem } from 'src/entities/user-metadata.entity';
import { UserEntity } from 'src/entities/user.entity';
import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum';
+import { UserMetadataItem } from 'src/types';
import { getPreferences } from 'src/utils/preferences';
import { Optional, ValidateBoolean, toEmail, toSanitized } from 'src/validation';
@@ -143,8 +143,9 @@ export class UserAdminResponseDto extends UserResponseDto {
}
export function mapUserAdmin(entity: UserEntity | UserAdmin): UserAdminResponseDto {
- const license = (entity.metadata as UserMetadataItem[])?.find(
- (item): item is UserMetadataEntity => item.key === UserMetadataKey.LICENSE,
+ const metadata = entity.metadata || [];
+ const license = metadata.find(
+ (item): item is UserMetadataItem => item.key === UserMetadataKey.LICENSE,
)?.value;
return {
...mapUser(entity),
diff --git a/server/src/entities/asset.entity.ts b/server/src/entities/asset.entity.ts
index 836fc409af..ef27e0db5f 100644
--- a/server/src/entities/asset.entity.ts
+++ b/server/src/entities/asset.entity.ts
@@ -1,5 +1,6 @@
import { DeduplicateJoinsPlugin, ExpressionBuilder, Kysely, SelectQueryBuilder, sql } from 'kysely';
import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
+import { Tag } from 'src/database';
import { DB } from 'src/db';
import { AlbumEntity } from 'src/entities/album.entity';
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
@@ -7,9 +8,7 @@ import { AssetFileEntity } from 'src/entities/asset-files.entity';
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
import { ExifEntity } from 'src/entities/exif.entity';
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
-import { SmartSearchEntity } from 'src/entities/smart-search.entity';
import { StackEntity } from 'src/entities/stack.entity';
-import { TagEntity } from 'src/entities/tag.entity';
import { UserEntity } from 'src/entities/user.entity';
import { AssetFileType, AssetStatus, AssetType } from 'src/enum';
import { TimeBucketSize } from 'src/repositories/asset.repository';
@@ -50,8 +49,7 @@ export class AssetEntity {
originalFileName!: string;
sidecarPath!: string | null;
exifInfo?: ExifEntity;
- smartSearch?: SmartSearchEntity;
- tags!: TagEntity[];
+ tags?: Tag[];
sharedLinks!: SharedLinkEntity[];
albums?: AlbumEntity[];
faces!: AssetFaceEntity[];
@@ -97,9 +95,9 @@ export function withFiles(eb: ExpressionBuilder, type?: AssetFileT
return jsonArrayFrom(
eb
.selectFrom('asset_files')
- .selectAll()
+ .selectAll('asset_files')
.whereRef('asset_files.assetId', '=', 'assets.id')
- .$if(!!type, (qb) => qb.where('type', '=', type!)),
+ .$if(!!type, (qb) => qb.where('asset_files.type', '=', type!)),
).as('files');
}
diff --git a/server/src/entities/move.entity.ts b/server/src/entities/move.entity.ts
deleted file mode 100644
index 0570d98edc..0000000000
--- a/server/src/entities/move.entity.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { PathType } from 'src/enum';
-
-export class MoveEntity {
- id!: string;
- entityId!: string;
- pathType!: PathType;
- oldPath!: string;
- newPath!: string;
-}
diff --git a/server/src/entities/session.entity.ts b/server/src/entities/session.entity.ts
deleted file mode 100644
index 45856ff2af..0000000000
--- a/server/src/entities/session.entity.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { ExpressionBuilder } from 'kysely';
-import { DB } from 'src/db';
-import { UserEntity } from 'src/entities/user.entity';
-
-export class SessionEntity {
- id!: string;
- token!: string;
- userId!: string;
- user!: UserEntity;
- createdAt!: Date;
- updatedAt!: Date;
- updateId!: string;
- deviceType!: string;
- deviceOS!: string;
-}
-
-const userColumns = [
- 'id',
- 'email',
- 'createdAt',
- 'profileImagePath',
- 'isAdmin',
- 'shouldChangePassword',
- 'deletedAt',
- 'oauthId',
- 'updatedAt',
- 'storageLabel',
- 'name',
- 'quotaSizeInBytes',
- 'quotaUsageInBytes',
- 'status',
- 'profileChangedAt',
-] as const;
-
-export const withUser = (eb: ExpressionBuilder) => {
- return eb
- .selectFrom('users')
- .select(userColumns)
- .select((eb) =>
- eb
- .selectFrom('user_metadata')
- .whereRef('users.id', '=', 'user_metadata.userId')
- .select((eb) => eb.fn('array_agg', [eb.table('user_metadata')]).as('metadata'))
- .as('metadata'),
- )
- .whereRef('users.id', '=', 'sessions.userId')
- .where('users.deletedAt', 'is', null)
- .as('user');
-};
diff --git a/server/src/entities/smart-search.entity.ts b/server/src/entities/smart-search.entity.ts
deleted file mode 100644
index e8a8f27cb1..0000000000
--- a/server/src/entities/smart-search.entity.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { AssetEntity } from 'src/entities/asset.entity';
-
-export class SmartSearchEntity {
- asset?: AssetEntity;
- assetId!: string;
- embedding!: string;
-}
diff --git a/server/src/entities/tag.entity.ts b/server/src/entities/tag.entity.ts
deleted file mode 100644
index 01235085a4..0000000000
--- a/server/src/entities/tag.entity.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { AssetEntity } from 'src/entities/asset.entity';
-import { UserEntity } from 'src/entities/user.entity';
-
-export class TagEntity {
- id!: string;
- value!: string;
- createdAt!: Date;
- updatedAt!: Date;
- updateId?: string;
- color!: string | null;
- parentId?: string;
- parent?: TagEntity;
- children?: TagEntity[];
- user?: UserEntity;
- userId!: string;
- assets?: AssetEntity[];
-}
diff --git a/server/src/entities/user-metadata.entity.ts b/server/src/entities/user-metadata.entity.ts
deleted file mode 100644
index 065f4deac3..0000000000
--- a/server/src/entities/user-metadata.entity.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import { UserEntity } from 'src/entities/user.entity';
-import { UserAvatarColor, UserMetadataKey } from 'src/enum';
-import { DeepPartial } from 'src/types';
-import { HumanReadableSize } from 'src/utils/bytes';
-
-export type UserMetadataItem = {
- key: T;
- value: UserMetadata[T];
-};
-
-export class UserMetadataEntity implements UserMetadataItem {
- userId!: string;
- user?: UserEntity;
- key!: T;
- value!: UserMetadata[T];
-}
-
-export interface UserPreferences {
- folders: {
- enabled: boolean;
- sidebarWeb: boolean;
- };
- memories: {
- enabled: boolean;
- };
- people: {
- enabled: boolean;
- sidebarWeb: boolean;
- };
- ratings: {
- enabled: boolean;
- };
- sharedLinks: {
- enabled: boolean;
- sidebarWeb: boolean;
- };
- tags: {
- enabled: boolean;
- sidebarWeb: boolean;
- };
- avatar: {
- color: UserAvatarColor;
- };
- emailNotifications: {
- enabled: boolean;
- albumInvite: boolean;
- albumUpdate: boolean;
- };
- download: {
- archiveSize: number;
- includeEmbeddedVideos: boolean;
- };
- purchase: {
- showSupportBadge: boolean;
- hideBuyButtonUntil: string;
- };
-}
-
-export const getDefaultPreferences = (user: { email: string }): UserPreferences => {
- const values = Object.values(UserAvatarColor);
- const randomIndex = Math.floor(
- [...user.email].map((letter) => letter.codePointAt(0) ?? 0).reduce((a, b) => a + b, 0) % values.length,
- );
-
- return {
- folders: {
- enabled: false,
- sidebarWeb: false,
- },
- memories: {
- enabled: true,
- },
- people: {
- enabled: true,
- sidebarWeb: false,
- },
- sharedLinks: {
- enabled: true,
- sidebarWeb: false,
- },
- ratings: {
- enabled: false,
- },
- tags: {
- enabled: false,
- sidebarWeb: false,
- },
- avatar: {
- color: values[randomIndex],
- },
- emailNotifications: {
- enabled: true,
- albumInvite: true,
- albumUpdate: true,
- },
- download: {
- archiveSize: HumanReadableSize.GiB * 4,
- includeEmbeddedVideos: false,
- },
- purchase: {
- showSupportBadge: true,
- hideBuyButtonUntil: new Date(2022, 1, 12).toISOString(),
- },
- };
-};
-
-export interface UserMetadata extends Record> {
- [UserMetadataKey.PREFERENCES]: DeepPartial;
- [UserMetadataKey.LICENSE]: { licenseKey: string; activationKey: string; activatedAt: string };
-}
diff --git a/server/src/entities/user.entity.ts b/server/src/entities/user.entity.ts
index 5035f96274..96c574c83d 100644
--- a/server/src/entities/user.entity.ts
+++ b/server/src/entities/user.entity.ts
@@ -2,8 +2,8 @@ import { ExpressionBuilder } from 'kysely';
import { jsonArrayFrom } from 'kysely/helpers/postgres';
import { DB } from 'src/db';
import { AssetEntity } from 'src/entities/asset.entity';
-import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
import { UserStatus } from 'src/enum';
+import { UserMetadataItem } from 'src/types';
export class UserEntity {
id!: string;
@@ -23,7 +23,7 @@ export class UserEntity {
assets!: AssetEntity[];
quotaSizeInBytes!: number | null;
quotaUsageInBytes!: number;
- metadata!: UserMetadataEntity[];
+ metadata!: UserMetadataItem[];
profileChangedAt!: Date;
}
diff --git a/server/src/queries/activity.repository.sql b/server/src/queries/activity.repository.sql
index 0ddb91c692..3d4d667de6 100644
--- a/server/src/queries/activity.repository.sql
+++ b/server/src/queries/activity.repository.sql
@@ -3,6 +3,38 @@
-- ActivityRepository.search
select
"activity".*,
+ to_json("user") as "user"
+from
+ "activity"
+ inner join "users" on "users"."id" = "activity"."userId"
+ and "users"."deletedAt" is null
+ inner join lateral (
+ select
+ "users"."id",
+ "users"."name",
+ "users"."email",
+ "users"."profileImagePath",
+ "users"."profileChangedAt"
+ from
+ (
+ select
+ 1
+ ) as "dummy"
+ ) as "user" on true
+ left join "assets" on "assets"."id" = "activity"."assetId"
+ and "assets"."deletedAt" is null
+where
+ "activity"."albumId" = $1
+order by
+ "activity"."createdAt" asc
+
+-- ActivityRepository.create
+insert into
+ "activity" ("albumId", "userId")
+values
+ ($1, $2)
+returning
+ *,
(
select
to_json(obj)
@@ -18,17 +50,13 @@ select
"users"
where
"users"."id" = "activity"."userId"
- and "users"."deletedAt" is null
) as obj
) as "user"
-from
- "activity"
- left join "assets" on "assets"."id" = "activity"."assetId"
- and "assets"."deletedAt" is null
+
+-- ActivityRepository.delete
+delete from "activity"
where
- "activity"."albumId" = $1
-order by
- "activity"."createdAt" asc
+ "id" = $1::uuid
-- ActivityRepository.getStatistics
select
diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql
index b2fdf976df..d840a7693c 100644
--- a/server/src/queries/asset.repository.sql
+++ b/server/src/queries/asset.repository.sql
@@ -179,6 +179,63 @@ from
where
"livePhotoVideoId" = $1::uuid
+-- AssetRepository.getAssetForSearchDuplicatesJob
+select
+ "id",
+ "type",
+ "ownerId",
+ "duplicateId",
+ "stackId",
+ "isVisible",
+ "smart_search"."embedding",
+ (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ "asset_files".*
+ from
+ "asset_files"
+ where
+ "asset_files"."assetId" = "assets"."id"
+ and "asset_files"."type" = $1
+ ) as agg
+ ) as "files"
+from
+ "assets"
+ left join "smart_search" on "assets"."id" = "smart_search"."assetId"
+where
+ "assets"."id" = $2::uuid
+limit
+ $3
+
+-- AssetRepository.getAssetForSidecarWriteJob
+select
+ "id",
+ "sidecarPath",
+ "originalPath",
+ (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ "tags"."value"
+ from
+ "tags"
+ inner join "tag_asset" on "tags"."id" = "tag_asset"."tagsId"
+ where
+ "assets"."id" = "tag_asset"."assetsId"
+ ) as agg
+ ) as "tags"
+from
+ "assets"
+where
+ "assets"."id" = $1::uuid
+limit
+ $2
+
-- AssetRepository.getById
select
"assets".*
diff --git a/server/src/queries/session.repository.sql b/server/src/queries/session.repository.sql
index 3d115615fd..eea2356897 100644
--- a/server/src/queries/session.repository.sql
+++ b/server/src/queries/session.repository.sql
@@ -38,41 +38,11 @@ where
-- SessionRepository.getByUserId
select
- "sessions".*,
- to_json("user") as "user"
+ "sessions".*
from
"sessions"
- inner join lateral (
- select
- "id",
- "email",
- "createdAt",
- "profileImagePath",
- "isAdmin",
- "shouldChangePassword",
- "deletedAt",
- "oauthId",
- "updatedAt",
- "storageLabel",
- "name",
- "quotaSizeInBytes",
- "quotaUsageInBytes",
- "status",
- "profileChangedAt",
- (
- select
- array_agg("user_metadata") as "metadata"
- from
- "user_metadata"
- where
- "users"."id" = "user_metadata"."userId"
- ) as "metadata"
- from
- "users"
- where
- "users"."id" = "sessions"."userId"
- and "users"."deletedAt" is null
- ) as "user" on true
+ inner join "users" on "users"."id" = "sessions"."userId"
+ and "users"."deletedAt" is null
where
"sessions"."userId" = $1
order by
diff --git a/server/src/repositories/activity.repository.ts b/server/src/repositories/activity.repository.ts
index 48def82f49..e266022b05 100644
--- a/server/src/repositories/activity.repository.ts
+++ b/server/src/repositories/activity.repository.ts
@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
-import { ExpressionBuilder, Insertable, Kysely } from 'kysely';
+import { Insertable, Kysely, NotNull, sql } from 'kysely';
import { jsonObjectFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
@@ -14,16 +14,6 @@ export interface ActivitySearch {
isLiked?: boolean;
}
-const withUser = (eb: ExpressionBuilder) => {
- return jsonObjectFrom(
- eb
- .selectFrom('users')
- .select(columns.user)
- .whereRef('users.id', '=', 'activity.userId')
- .where('users.deletedAt', 'is', null),
- ).as('user');
-};
-
@Injectable()
export class ActivityRepository {
constructor(@InjectKysely() private db: Kysely) {}
@@ -35,7 +25,16 @@ export class ActivityRepository {
return this.db
.selectFrom('activity')
.selectAll('activity')
- .select(withUser)
+ .innerJoin('users', (join) => join.onRef('users.id', '=', 'activity.userId').on('users.deletedAt', 'is', null))
+ .innerJoinLateral(
+ (eb) =>
+ eb
+ .selectFrom(sql`(select 1)`.as('dummy'))
+ .select(columns.userWithPrefix)
+ .as('user'),
+ (join) => join.onTrue(),
+ )
+ .select((eb) => eb.fn.toJson('user').as('user'))
.leftJoin('assets', (join) => join.onRef('assets.id', '=', 'activity.assetId').on('assets.deletedAt', 'is', null))
.$if(!!userId, (qb) => qb.where('activity.userId', '=', userId!))
.$if(assetId === null, (qb) => qb.where('assetId', 'is', null))
@@ -46,10 +45,22 @@ export class ActivityRepository {
.execute();
}
+ @GenerateSql({ params: [{ albumId: DummyValue.UUID, userId: DummyValue.UUID }] })
async create(activity: Insertable) {
- return this.save(activity);
+ return this.db
+ .insertInto('activity')
+ .values(activity)
+ .returningAll()
+ .returning((eb) =>
+ jsonObjectFrom(eb.selectFrom('users').whereRef('users.id', '=', 'activity.userId').select(columns.user)).as(
+ 'user',
+ ),
+ )
+ .$narrowType<{ user: NotNull }>()
+ .executeTakeFirstOrThrow();
}
+ @GenerateSql({ params: [DummyValue.UUID] })
async delete(id: string) {
await this.db.deleteFrom('activity').where('id', '=', asUuid(id)).execute();
}
@@ -72,15 +83,4 @@ export class ActivityRepository {
return count as number;
}
-
- private async save(entity: Insertable) {
- const { id } = await this.db.insertInto('activity').values(entity).returning('id').executeTakeFirstOrThrow();
-
- return this.db
- .selectFrom('activity')
- .selectAll('activity')
- .select(withUser)
- .where('activity.id', '=', asUuid(id))
- .executeTakeFirstOrThrow();
- }
}
diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts
index 77154bbd1d..3b71cf84fd 100644
--- a/server/src/repositories/asset.repository.ts
+++ b/server/src/repositories/asset.repository.ts
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { Insertable, Kysely, Selectable, UpdateResult, Updateable, sql } from 'kysely';
+import { jsonArrayFrom } from 'kysely/helpers/postgres';
import { isEmpty, isUndefined, omitBy } from 'lodash';
import { InjectKysely } from 'nestjs-kysely';
import { AssetFiles, AssetJobStatus, Assets, DB, Exif } from 'src/db';
@@ -475,6 +476,47 @@ export class AssetRepository {
return count as number;
}
+ @GenerateSql({ params: [DummyValue.UUID] })
+ getAssetForSearchDuplicatesJob(id: string) {
+ return this.db
+ .selectFrom('assets')
+ .where('assets.id', '=', asUuid(id))
+ .leftJoin('smart_search', 'assets.id', 'smart_search.assetId')
+ .select((eb) => [
+ 'id',
+ 'type',
+ 'ownerId',
+ 'duplicateId',
+ 'stackId',
+ 'isVisible',
+ 'smart_search.embedding',
+ withFiles(eb, AssetFileType.PREVIEW),
+ ])
+ .limit(1)
+ .executeTakeFirst();
+ }
+
+ @GenerateSql({ params: [DummyValue.UUID] })
+ getAssetForSidecarWriteJob(id: string) {
+ return this.db
+ .selectFrom('assets')
+ .where('assets.id', '=', asUuid(id))
+ .select((eb) => [
+ 'id',
+ 'sidecarPath',
+ 'originalPath',
+ jsonArrayFrom(
+ eb
+ .selectFrom('tags')
+ .select(['tags.value'])
+ .innerJoin('tag_asset', 'tags.id', 'tag_asset.tagsId')
+ .whereRef('assets.id', '=', 'tag_asset.assetsId'),
+ ).as('tags'),
+ ])
+ .limit(1)
+ .executeTakeFirst();
+ }
+
@GenerateSql({ params: [DummyValue.UUID] })
getById(
id: string,
diff --git a/server/src/repositories/move.repository.ts b/server/src/repositories/move.repository.ts
index 706e23cef7..21c52aec65 100644
--- a/server/src/repositories/move.repository.ts
+++ b/server/src/repositories/move.repository.ts
@@ -3,49 +3,38 @@ import { Insertable, Kysely, sql, Updateable } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB, MoveHistory } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
-import { MoveEntity } from 'src/entities/move.entity';
import { AssetPathType, PathType } from 'src/enum';
-export type MoveCreate = Pick & Partial;
-
@Injectable()
export class MoveRepository {
constructor(@InjectKysely() private db: Kysely) {}
- create(entity: Insertable): Promise {
- return this.db
- .insertInto('move_history')
- .values(entity)
- .returningAll()
- .executeTakeFirstOrThrow() as Promise;
+ create(entity: Insertable) {
+ return this.db.insertInto('move_history').values(entity).returningAll().executeTakeFirstOrThrow();
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
- getByEntity(entityId: string, pathType: PathType): Promise {
+ getByEntity(entityId: string, pathType: PathType) {
return this.db
.selectFrom('move_history')
.selectAll()
.where('entityId', '=', entityId)
.where('pathType', '=', pathType)
- .executeTakeFirst() as Promise;
+ .executeTakeFirst();
}
- update(id: string, entity: Updateable): Promise {
+ update(id: string, entity: Updateable) {
return this.db
.updateTable('move_history')
.set(entity)
.where('id', '=', id)
.returningAll()
- .executeTakeFirstOrThrow() as unknown as Promise;
+ .executeTakeFirstOrThrow();
}
@GenerateSql({ params: [DummyValue.UUID] })
- delete(id: string): Promise {
- return this.db
- .deleteFrom('move_history')
- .where('id', '=', id)
- .returningAll()
- .executeTakeFirstOrThrow() as unknown as Promise;
+ delete(id: string) {
+ return this.db.deleteFrom('move_history').where('id', '=', id).returningAll().executeTakeFirstOrThrow();
}
@GenerateSql()
diff --git a/server/src/repositories/session.repository.ts b/server/src/repositories/session.repository.ts
index 85ea5f890e..742807dc9c 100644
--- a/server/src/repositories/session.repository.ts
+++ b/server/src/repositories/session.repository.ts
@@ -5,7 +5,6 @@ import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { DB, Sessions } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
-import { withUser } from 'src/entities/session.entity';
import { asUuid } from 'src/utils/database';
export type SessionSearchOptions = { updatedBefore: Date };
@@ -45,9 +44,8 @@ export class SessionRepository {
getByUserId(userId: string) {
return this.db
.selectFrom('sessions')
- .innerJoinLateral(withUser, (join) => join.onTrue())
+ .innerJoin('users', (join) => join.onRef('users.id', '=', 'sessions.userId').on('users.deletedAt', 'is', null))
.selectAll('sessions')
- .select((eb) => eb.fn.toJson('user').as('user'))
.where('sessions.userId', '=', userId)
.orderBy('sessions.updatedAt', 'desc')
.orderBy('sessions.createdAt', 'desc')
diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts
index c254085fd2..5912f60687 100644
--- a/server/src/repositories/user.repository.ts
+++ b/server/src/repositories/user.repository.ts
@@ -5,10 +5,10 @@ import { InjectKysely } from 'nestjs-kysely';
import { columns, UserAdmin } from 'src/database';
import { DB, UserMetadata as DbUserMetadata } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
-import { UserMetadata, UserMetadataItem } from 'src/entities/user-metadata.entity';
import { UserEntity, withMetadata } from 'src/entities/user.entity';
import { AssetType, UserStatus } from 'src/enum';
import { UserTable } from 'src/schema/tables/user.table';
+import { UserMetadata, UserMetadataItem } from 'src/types';
import { asUuid } from 'src/utils/database';
type Upsert = Insertable;
diff --git a/server/src/schema/tables/user-metadata.table.ts b/server/src/schema/tables/user-metadata.table.ts
index e71b3bf9f9..6d03acaf80 100644
--- a/server/src/schema/tables/user-metadata.table.ts
+++ b/server/src/schema/tables/user-metadata.table.ts
@@ -1,7 +1,7 @@
-import { UserMetadata, UserMetadataItem } from 'src/entities/user-metadata.entity';
import { UserMetadataKey } from 'src/enum';
import { UserTable } from 'src/schema/tables/user.table';
import { Column, ForeignKeyColumn, PrimaryColumn, Table } from 'src/sql-tools';
+import { UserMetadata, UserMetadataItem } from 'src/types';
@Table('user_metadata')
export class UserMetadataTable implements UserMetadataItem {
diff --git a/server/src/services/activity.service.ts b/server/src/services/activity.service.ts
index feb1074fb2..6e3c3d7083 100644
--- a/server/src/services/activity.service.ts
+++ b/server/src/services/activity.service.ts
@@ -1,4 +1,5 @@
import { Injectable } from '@nestjs/common';
+import { Activity } from 'src/database';
import {
ActivityCreateDto,
ActivityDto,
@@ -13,7 +14,6 @@ import {
import { AuthDto } from 'src/dtos/auth.dto';
import { Permission } from 'src/enum';
import { BaseService } from 'src/services/base.service';
-import { ActivityItem } from 'src/types';
@Injectable()
export class ActivityService extends BaseService {
@@ -43,7 +43,7 @@ export class ActivityService extends BaseService {
albumId: dto.albumId,
};
- let activity: ActivityItem | undefined;
+ let activity: Activity | undefined;
let duplicate = false;
if (dto.type === ReactionType.LIKE) {
diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts
index 994912f2c7..cbe81f1c0d 100644
--- a/server/src/services/album.service.ts
+++ b/server/src/services/album.service.ts
@@ -140,7 +140,7 @@ export class AlbumService extends BaseService {
order: dto.order,
});
- return mapAlbumWithoutAssets(updatedAlbum);
+ return mapAlbumWithoutAssets({ ...updatedAlbum, assets: album.assets });
}
async delete(auth: AuthDto, id: string): Promise {
diff --git a/server/src/services/api-key.service.ts b/server/src/services/api-key.service.ts
index 5459b56889..33861d82cd 100644
--- a/server/src/services/api-key.service.ts
+++ b/server/src/services/api-key.service.ts
@@ -1,9 +1,9 @@
import { BadRequestException, Injectable } from '@nestjs/common';
+import { ApiKey } from 'src/database';
import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto, APIKeyUpdateDto } from 'src/dtos/api-key.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { Permission } from 'src/enum';
import { BaseService } from 'src/services/base.service';
-import { ApiKeyItem } from 'src/types';
import { isGranted } from 'src/utils/access';
@Injectable()
@@ -58,7 +58,7 @@ export class ApiKeyService extends BaseService {
return keys.map((key) => this.map(key));
}
- private map(entity: ApiKeyItem): APIKeyResponseDto {
+ private map(entity: ApiKey): APIKeyResponseDto {
return {
id: entity.id,
name: entity.name,
diff --git a/server/src/services/auth.service.spec.ts b/server/src/services/auth.service.spec.ts
index b1bd3332bf..3c8bfa7d95 100644
--- a/server/src/services/auth.service.spec.ts
+++ b/server/src/services/auth.service.spec.ts
@@ -1,10 +1,10 @@
import { BadRequestException, ForbiddenException, UnauthorizedException } from '@nestjs/common';
+import { DateTime } from 'luxon';
import { AuthDto, SignUpDto } from 'src/dtos/auth.dto';
-import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
import { UserEntity } from 'src/entities/user.entity';
import { AuthType, Permission } from 'src/enum';
import { AuthService } from 'src/services/auth.service';
-import { sessionStub } from 'test/fixtures/session.stub';
+import { UserMetadataItem } from 'src/types';
import { sharedLinkStub } from 'test/fixtures/shared-link.stub';
import { systemConfigStub } from 'test/fixtures/system-config.stub';
import { userStub } from 'test/fixtures/user.stub';
@@ -97,17 +97,19 @@ describe('AuthService', () => {
});
it('should successfully log the user in', async () => {
- mocks.user.getByEmail.mockResolvedValue(userStub.user1);
- mocks.session.create.mockResolvedValue(sessionStub.valid);
+ const user = { ...factory.user(), password: 'immich_password' } as UserEntity;
+ const session = factory.session();
+ mocks.user.getByEmail.mockResolvedValue(user);
+ mocks.session.create.mockResolvedValue(session);
await expect(sut.login(fixtures.login, loginDetails)).resolves.toEqual({
accessToken: 'cmFuZG9tLWJ5dGVz',
- userId: 'user-id',
- userEmail: 'immich@test.com',
- name: 'immich_name',
- profileImagePath: '',
- isAdmin: false,
- shouldChangePassword: false,
+ userId: user.id,
+ userEmail: user.email,
+ name: user.name,
+ profileImagePath: user.profileImagePath,
+ isAdmin: user.isAdmin,
+ shouldChangePassword: user.shouldChangePassword,
});
expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1);
@@ -228,7 +230,7 @@ describe('AuthService', () => {
...dto,
id: 'admin',
createdAt: new Date('2021-01-01'),
- metadata: [] as UserMetadataEntity[],
+ metadata: [] as UserMetadataItem[],
} as UserEntity);
await expect(sut.adminSignUp(dto)).resolves.toMatchObject({
@@ -256,8 +258,14 @@ describe('AuthService', () => {
});
it('should validate using authorization header', async () => {
- mocks.user.get.mockResolvedValue(userStub.user1);
- mocks.session.getByToken.mockResolvedValue(sessionStub.valid as any);
+ const session = factory.session();
+ const sessionWithToken = {
+ id: session.id,
+ updatedAt: session.updatedAt,
+ user: factory.authUser(),
+ };
+
+ mocks.session.getByToken.mockResolvedValue(sessionWithToken);
await expect(
sut.authenticate({
@@ -266,8 +274,8 @@ describe('AuthService', () => {
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' },
}),
).resolves.toEqual({
- user: userStub.user1,
- session: sessionStub.valid,
+ user: sessionWithToken.user,
+ session: { id: session.id },
});
});
});
@@ -371,7 +379,14 @@ describe('AuthService', () => {
});
it('should return an auth dto', async () => {
- mocks.session.getByToken.mockResolvedValue(sessionStub.valid as any);
+ const session = factory.session();
+ const sessionWithToken = {
+ id: session.id,
+ updatedAt: session.updatedAt,
+ user: factory.authUser(),
+ };
+
+ mocks.session.getByToken.mockResolvedValue(sessionWithToken);
await expect(
sut.authenticate({
@@ -380,13 +395,20 @@ describe('AuthService', () => {
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' },
}),
).resolves.toEqual({
- user: userStub.user1,
- session: sessionStub.valid,
+ user: sessionWithToken.user,
+ session: { id: session.id },
});
});
it('should throw if admin route and not an admin', async () => {
- mocks.session.getByToken.mockResolvedValue(sessionStub.valid as any);
+ const session = factory.session();
+ const sessionWithToken = {
+ id: session.id,
+ updatedAt: session.updatedAt,
+ user: factory.authUser(),
+ };
+
+ mocks.session.getByToken.mockResolvedValue(sessionWithToken);
await expect(
sut.authenticate({
@@ -398,8 +420,15 @@ describe('AuthService', () => {
});
it('should update when access time exceeds an hour', async () => {
- mocks.session.getByToken.mockResolvedValue(sessionStub.inactive as any);
- mocks.session.update.mockResolvedValue(sessionStub.valid);
+ const session = factory.session({ updatedAt: DateTime.now().minus({ hours: 2 }).toJSDate() });
+ const sessionWithToken = {
+ id: session.id,
+ updatedAt: session.updatedAt,
+ user: factory.authUser(),
+ };
+
+ mocks.session.getByToken.mockResolvedValue(sessionWithToken);
+ mocks.session.update.mockResolvedValue(session);
await expect(
sut.authenticate({
@@ -408,7 +437,8 @@ describe('AuthService', () => {
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' },
}),
).resolves.toBeDefined();
- expect(mocks.session.update.mock.calls[0][1]).toMatchObject({ id: 'not_active', updatedAt: expect.any(Date) });
+
+ expect(mocks.session.update).toHaveBeenCalled();
});
});
@@ -506,7 +536,7 @@ describe('AuthService', () => {
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled);
mocks.user.getByEmail.mockResolvedValue(userStub.user1);
mocks.user.update.mockResolvedValue(userStub.user1);
- mocks.session.create.mockResolvedValue(sessionStub.valid);
+ mocks.session.create.mockResolvedValue(factory.session());
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
oauthResponse,
@@ -535,7 +565,7 @@ describe('AuthService', () => {
mocks.user.getByEmail.mockResolvedValue(void 0);
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
mocks.user.create.mockResolvedValue(userStub.user1);
- mocks.session.create.mockResolvedValue(sessionStub.valid);
+ mocks.session.create.mockResolvedValue(factory.session());
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
oauthResponse,
@@ -550,7 +580,7 @@ describe('AuthService', () => {
mocks.user.getByEmail.mockResolvedValue(void 0);
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
mocks.user.create.mockResolvedValue(userStub.user1);
- mocks.session.create.mockResolvedValue(sessionStub.valid);
+ mocks.session.create.mockResolvedValue(factory.session());
mocks.oauth.getProfile.mockResolvedValue({ sub, email: undefined });
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).rejects.toBeInstanceOf(
@@ -572,7 +602,7 @@ describe('AuthService', () => {
it(`should use the mobile redirect override for a url of ${url}`, async () => {
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithMobileOverride);
mocks.user.getByOAuthId.mockResolvedValue(userStub.user1);
- mocks.session.create.mockResolvedValue(sessionStub.valid);
+ mocks.session.create.mockResolvedValue(factory.session());
await sut.callback({ url }, loginDetails);
diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts
index 235f20e705..4110427b0c 100644
--- a/server/src/services/auth.service.ts
+++ b/server/src/services/auth.service.ts
@@ -338,7 +338,9 @@ export class AuthService extends BaseService {
return {
user: session.user,
- session,
+ session: {
+ id: session.id,
+ },
};
}
diff --git a/server/src/services/duplicate.service.spec.ts b/server/src/services/duplicate.service.spec.ts
index 8be943eaf0..adb884c24f 100644
--- a/server/src/services/duplicate.service.spec.ts
+++ b/server/src/services/duplicate.service.spec.ts
@@ -1,4 +1,4 @@
-import { JobName, JobStatus } from 'src/enum';
+import { AssetFileType, AssetType, JobName, JobStatus } from 'src/enum';
import { WithoutProperty } from 'src/repositories/asset.repository';
import { DuplicateService } from 'src/services/duplicate.service';
import { SearchService } from 'src/services/search.service';
@@ -9,6 +9,33 @@ import { beforeEach, vitest } from 'vitest';
vitest.useFakeTimers();
+const hasEmbedding = {
+ id: 'asset-1',
+ ownerId: 'user-id',
+ files: [
+ {
+ assetId: 'asset-1',
+ createdAt: new Date(),
+ id: 'file-1',
+ path: 'preview.jpg',
+ type: AssetFileType.PREVIEW,
+ updatedAt: new Date(),
+ updateId: 'update-1',
+ },
+ ],
+ isVisible: true,
+ stackId: null,
+ type: AssetType.IMAGE,
+ duplicateId: null,
+ embedding: '[1, 2, 3, 4]',
+};
+
+const hasDupe = {
+ ...hasEmbedding,
+ id: 'asset-2',
+ duplicateId: 'duplicate-id',
+};
+
describe(SearchService.name, () => {
let sut: DuplicateService;
let mocks: ServiceMocks;
@@ -25,16 +52,16 @@ describe(SearchService.name, () => {
it('should get duplicates', async () => {
mocks.asset.getDuplicates.mockResolvedValue([
{
- duplicateId: assetStub.hasDupe.duplicateId!,
- assets: [assetStub.hasDupe, assetStub.hasDupe],
+ duplicateId: 'duplicate-id',
+ assets: [assetStub.image, assetStub.image],
},
]);
await expect(sut.getDuplicates(authStub.admin)).resolves.toEqual([
{
- duplicateId: assetStub.hasDupe.duplicateId,
+ duplicateId: 'duplicate-id',
assets: [
- expect.objectContaining({ id: assetStub.hasDupe.id }),
- expect.objectContaining({ id: assetStub.hasDupe.id }),
+ expect.objectContaining({ id: assetStub.image.id }),
+ expect.objectContaining({ id: assetStub.image.id }),
],
},
]);
@@ -175,7 +202,7 @@ describe(SearchService.name, () => {
it('should skip if asset is part of stack', async () => {
const id = assetStub.primaryImage.id;
- mocks.asset.getById.mockResolvedValue(assetStub.primaryImage);
+ mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue({ ...hasEmbedding, stackId: 'stack-id' });
const result = await sut.handleSearchDuplicates({ id });
@@ -185,7 +212,7 @@ describe(SearchService.name, () => {
it('should skip if asset is not visible', async () => {
const id = assetStub.livePhotoMotionAsset.id;
- mocks.asset.getById.mockResolvedValue(assetStub.livePhotoMotionAsset);
+ mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue({ ...hasEmbedding, isVisible: false });
const result = await sut.handleSearchDuplicates({ id });
@@ -194,7 +221,7 @@ describe(SearchService.name, () => {
});
it('should fail if asset is missing preview image', async () => {
- mocks.asset.getById.mockResolvedValue(assetStub.noResizePath);
+ mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue({ ...hasEmbedding, files: [] });
const result = await sut.handleSearchDuplicates({ id: assetStub.noResizePath.id });
@@ -203,7 +230,7 @@ describe(SearchService.name, () => {
});
it('should fail if asset is missing embedding', async () => {
- mocks.asset.getById.mockResolvedValue(assetStub.image);
+ mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue({ ...hasEmbedding, embedding: null });
const result = await sut.handleSearchDuplicates({ id: assetStub.image.id });
@@ -212,21 +239,21 @@ describe(SearchService.name, () => {
});
it('should search for duplicates and update asset with duplicateId', async () => {
- mocks.asset.getById.mockResolvedValue(assetStub.hasEmbedding);
+ mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue(hasEmbedding);
mocks.search.searchDuplicates.mockResolvedValue([
{ assetId: assetStub.image.id, distance: 0.01, duplicateId: null },
]);
- const expectedAssetIds = [assetStub.image.id, assetStub.hasEmbedding.id];
+ const expectedAssetIds = [assetStub.image.id, hasEmbedding.id];
- const result = await sut.handleSearchDuplicates({ id: assetStub.hasEmbedding.id });
+ const result = await sut.handleSearchDuplicates({ id: hasEmbedding.id });
expect(result).toBe(JobStatus.SUCCESS);
expect(mocks.search.searchDuplicates).toHaveBeenCalledWith({
- assetId: assetStub.hasEmbedding.id,
- embedding: assetStub.hasEmbedding.smartSearch!.embedding,
+ assetId: hasEmbedding.id,
+ embedding: hasEmbedding.embedding,
maxDistance: 0.01,
- type: assetStub.hasEmbedding.type,
- userIds: [assetStub.hasEmbedding.ownerId],
+ type: hasEmbedding.type,
+ userIds: [hasEmbedding.ownerId],
});
expect(mocks.asset.updateDuplicates).toHaveBeenCalledWith({
assetIds: expectedAssetIds,
@@ -239,24 +266,24 @@ describe(SearchService.name, () => {
});
it('should use existing duplicate ID among matched duplicates', async () => {
- const duplicateId = assetStub.hasDupe.duplicateId;
- mocks.asset.getById.mockResolvedValue(assetStub.hasEmbedding);
- mocks.search.searchDuplicates.mockResolvedValue([{ assetId: assetStub.hasDupe.id, distance: 0.01, duplicateId }]);
- const expectedAssetIds = [assetStub.hasEmbedding.id];
+ const duplicateId = hasDupe.duplicateId;
+ mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue(hasEmbedding);
+ mocks.search.searchDuplicates.mockResolvedValue([{ assetId: hasDupe.id, distance: 0.01, duplicateId }]);
+ const expectedAssetIds = [hasEmbedding.id];
- const result = await sut.handleSearchDuplicates({ id: assetStub.hasEmbedding.id });
+ const result = await sut.handleSearchDuplicates({ id: hasEmbedding.id });
expect(result).toBe(JobStatus.SUCCESS);
expect(mocks.search.searchDuplicates).toHaveBeenCalledWith({
- assetId: assetStub.hasEmbedding.id,
- embedding: assetStub.hasEmbedding.smartSearch!.embedding,
+ assetId: hasEmbedding.id,
+ embedding: hasEmbedding.embedding,
maxDistance: 0.01,
- type: assetStub.hasEmbedding.type,
- userIds: [assetStub.hasEmbedding.ownerId],
+ type: hasEmbedding.type,
+ userIds: [hasEmbedding.ownerId],
});
expect(mocks.asset.updateDuplicates).toHaveBeenCalledWith({
assetIds: expectedAssetIds,
- targetDuplicateId: assetStub.hasDupe.duplicateId,
+ targetDuplicateId: duplicateId,
duplicateIds: [],
});
expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith(
@@ -265,15 +292,15 @@ describe(SearchService.name, () => {
});
it('should remove duplicateId if no duplicates found and asset has duplicateId', async () => {
- mocks.asset.getById.mockResolvedValue(assetStub.hasDupe);
+ mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue(hasDupe);
mocks.search.searchDuplicates.mockResolvedValue([]);
- const result = await sut.handleSearchDuplicates({ id: assetStub.hasDupe.id });
+ const result = await sut.handleSearchDuplicates({ id: hasDupe.id });
expect(result).toBe(JobStatus.SUCCESS);
- expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.hasDupe.id, duplicateId: null });
+ expect(mocks.asset.update).toHaveBeenCalledWith({ id: hasDupe.id, duplicateId: null });
expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith({
- assetId: assetStub.hasDupe.id,
+ assetId: hasDupe.id,
duplicatesDetectedAt: expect.any(Date),
});
});
diff --git a/server/src/services/duplicate.service.ts b/server/src/services/duplicate.service.ts
index da6c6794fb..10adb645d3 100644
--- a/server/src/services/duplicate.service.ts
+++ b/server/src/services/duplicate.service.ts
@@ -4,7 +4,6 @@ import { OnJob } from 'src/decorators';
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 { AssetFileType, JobName, JobStatus, QueueName } from 'src/enum';
import { WithoutProperty } from 'src/repositories/asset.repository';
import { AssetDuplicateResult } from 'src/repositories/search.repository';
@@ -53,7 +52,7 @@ export class DuplicateService extends BaseService {
return JobStatus.SKIPPED;
}
- const asset = await this.assetRepository.getById(id, { files: true, smartSearch: true });
+ const asset = await this.assetRepository.getAssetForSearchDuplicatesJob(id);
if (!asset) {
this.logger.error(`Asset ${id} not found`);
return JobStatus.FAILED;
@@ -75,14 +74,14 @@ export class DuplicateService extends BaseService {
return JobStatus.FAILED;
}
- if (!asset.smartSearch?.embedding) {
+ if (!asset.embedding) {
this.logger.debug(`Asset ${id} is missing embedding`);
return JobStatus.FAILED;
}
const duplicateAssets = await this.searchRepository.searchDuplicates({
assetId: asset.id,
- embedding: asset.smartSearch.embedding,
+ embedding: asset.embedding,
maxDistance: machineLearning.duplicateDetection.maxDistance,
type: asset.type,
userIds: [asset.ownerId],
@@ -105,7 +104,10 @@ export class DuplicateService extends BaseService {
return JobStatus.SUCCESS;
}
- private async updateDuplicates(asset: AssetEntity, duplicateAssets: AssetDuplicateResult[]): Promise {
+ private async updateDuplicates(
+ asset: { id: string; duplicateId: string | null },
+ duplicateAssets: AssetDuplicateResult[],
+ ): Promise {
const duplicateIds = [
...new Set(
duplicateAssets
diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts
index a0d1cdb4b4..9947d803a7 100644
--- a/server/src/services/metadata.service.spec.ts
+++ b/server/src/services/metadata.service.spec.ts
@@ -15,6 +15,7 @@ import { probeStub } from 'test/fixtures/media.stub';
import { metadataStub } from 'test/fixtures/metadata.stub';
import { personStub } from 'test/fixtures/person.stub';
import { tagStub } from 'test/fixtures/tag.stub';
+import { factory } from 'test/small.factory';
import { newTestService, ServiceMocks } from 'test/utils';
describe(MetadataService.name, () => {
@@ -545,7 +546,7 @@ describe(MetadataService.name, () => {
id: assetStub.livePhotoWithOriginalFileName.id,
livePhotoVideoId: fileStub.livePhotoMotion.uuid,
});
- expect(mocks.asset.update).toHaveBeenCalledTimes(2);
+ expect(mocks.asset.update).toHaveBeenCalledTimes(3);
});
it('should extract the EmbeddedVideo tag from Samsung JPEG motion photos', async () => {
@@ -597,7 +598,7 @@ describe(MetadataService.name, () => {
id: assetStub.livePhotoWithOriginalFileName.id,
livePhotoVideoId: fileStub.livePhotoMotion.uuid,
});
- expect(mocks.asset.update).toHaveBeenCalledTimes(2);
+ expect(mocks.asset.update).toHaveBeenCalledTimes(3);
});
it('should extract the motion photo video from the XMP directory entry ', async () => {
@@ -649,7 +650,7 @@ describe(MetadataService.name, () => {
id: assetStub.livePhotoWithOriginalFileName.id,
livePhotoVideoId: fileStub.livePhotoMotion.uuid,
});
- expect(mocks.asset.update).toHaveBeenCalledTimes(2);
+ expect(mocks.asset.update).toHaveBeenCalledTimes(3);
});
it('should delete old motion photo video assets if they do not match what is extracted', async () => {
@@ -672,10 +673,6 @@ describe(MetadataService.name, () => {
name: JobName.ASSET_DELETION,
data: { id: assetStub.livePhotoWithOriginalFileName.livePhotoVideoId, deleteOnDisk: true },
});
- expect(mocks.job.queue).toHaveBeenNthCalledWith(2, {
- name: JobName.METADATA_EXTRACTION,
- data: { id: 'random-uuid' },
- });
});
it('should not create a new motion photo video asset if the hash of the extracted video matches an existing asset', async () => {
@@ -722,7 +719,7 @@ describe(MetadataService.name, () => {
id: assetStub.livePhotoStillAsset.id,
livePhotoVideoId: assetStub.livePhotoMotionAsset.id,
});
- expect(mocks.asset.update).toHaveBeenCalledTimes(3);
+ expect(mocks.asset.update).toHaveBeenCalledTimes(4);
});
it('should not update storage usage if motion photo is external', async () => {
@@ -1405,33 +1402,35 @@ describe(MetadataService.name, () => {
describe('handleSidecarWrite', () => {
it('should skip assets that do not exist anymore', async () => {
- mocks.asset.getByIds.mockResolvedValue([]);
+ mocks.asset.getAssetForSidecarWriteJob.mockResolvedValue(void 0);
await expect(sut.handleSidecarWrite({ id: 'asset-123' })).resolves.toBe(JobStatus.FAILED);
expect(mocks.metadata.writeTags).not.toHaveBeenCalled();
});
- it('should skip jobs with not metadata', async () => {
- mocks.asset.getByIds.mockResolvedValue([assetStub.sidecar]);
- await expect(sut.handleSidecarWrite({ id: assetStub.sidecar.id })).resolves.toBe(JobStatus.SKIPPED);
+ it('should skip jobs with no metadata', async () => {
+ const asset = factory.jobAssets.sidecarWrite();
+ mocks.asset.getAssetForSidecarWriteJob.mockResolvedValue(asset);
+ await expect(sut.handleSidecarWrite({ id: asset.id })).resolves.toBe(JobStatus.SKIPPED);
expect(mocks.metadata.writeTags).not.toHaveBeenCalled();
});
it('should write tags', async () => {
+ const asset = factory.jobAssets.sidecarWrite();
const description = 'this is a description';
const gps = 12;
const date = '2023-11-22T04:56:12.196Z';
- mocks.asset.getByIds.mockResolvedValue([assetStub.sidecar]);
+ mocks.asset.getAssetForSidecarWriteJob.mockResolvedValue(asset);
await expect(
sut.handleSidecarWrite({
- id: assetStub.sidecar.id,
+ id: asset.id,
description,
latitude: gps,
longitude: gps,
dateTimeOriginal: date,
}),
).resolves.toBe(JobStatus.SUCCESS);
- expect(mocks.metadata.writeTags).toHaveBeenCalledWith(assetStub.sidecar.sidecarPath, {
+ expect(mocks.metadata.writeTags).toHaveBeenCalledWith(asset.sidecarPath, {
Description: description,
ImageDescription: description,
DateTimeOriginal: date,
diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts
index 402ccbbac7..72f7270844 100644
--- a/server/src/services/metadata.service.ts
+++ b/server/src/services/metadata.service.ts
@@ -316,7 +316,7 @@ export class MetadataService extends BaseService {
@OnJob({ name: JobName.SIDECAR_WRITE, queue: QueueName.SIDECAR })
async handleSidecarWrite(job: JobOf): Promise {
const { id, description, dateTimeOriginal, latitude, longitude, rating, tags } = job;
- const [asset] = await this.assetRepository.getByIds([id], { tags: true });
+ const asset = await this.assetRepository.getAssetForSidecarWriteJob(id);
if (!asset) {
return JobStatus.FAILED;
}
@@ -550,7 +550,8 @@ export class MetadataService extends BaseService {
this.storageCore.ensureFolders(motionAsset.originalPath);
await this.storageRepository.createFile(motionAsset.originalPath, video);
this.logger.log(`Wrote motion photo video to ${motionAsset.originalPath}`);
- await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: motionAsset.id } });
+
+ await this.handleMetadataExtraction({ id: motionAsset.id });
}
this.logger.debug(`Finished motion photo video extraction for asset ${asset.id}: ${asset.originalPath}`);
diff --git a/server/src/services/notification.service.spec.ts b/server/src/services/notification.service.spec.ts
index 89f211b297..823f1614ea 100644
--- a/server/src/services/notification.service.spec.ts
+++ b/server/src/services/notification.service.spec.ts
@@ -357,8 +357,6 @@ describe(NotificationService.name, () => {
{
key: UserMetadataKey.PREFERENCES,
value: { emailNotifications: { enabled: false, albumInvite: true } },
- userId: userStub.user1.id,
- user: userStub.user1,
},
],
});
@@ -374,8 +372,6 @@ describe(NotificationService.name, () => {
{
key: UserMetadataKey.PREFERENCES,
value: { emailNotifications: { enabled: true, albumInvite: false } },
- userId: userStub.user1.id,
- user: userStub.user1,
},
],
});
@@ -391,8 +387,6 @@ describe(NotificationService.name, () => {
{
key: UserMetadataKey.PREFERENCES,
value: { emailNotifications: { enabled: true, albumInvite: true } },
- userId: userStub.user1.id,
- user: userStub.user1,
},
],
});
@@ -414,8 +408,6 @@ describe(NotificationService.name, () => {
{
key: UserMetadataKey.PREFERENCES,
value: { emailNotifications: { enabled: true, albumInvite: true } },
- userId: userStub.user1.id,
- user: userStub.user1,
},
],
});
@@ -443,8 +435,6 @@ describe(NotificationService.name, () => {
{
key: UserMetadataKey.PREFERENCES,
value: { emailNotifications: { enabled: true, albumInvite: true } },
- userId: userStub.user1.id,
- user: userStub.user1,
},
],
});
@@ -476,8 +466,6 @@ describe(NotificationService.name, () => {
{
key: UserMetadataKey.PREFERENCES,
value: { emailNotifications: { enabled: true, albumInvite: true } },
- userId: userStub.user1.id,
- user: userStub.user1,
},
],
});
@@ -536,8 +524,6 @@ describe(NotificationService.name, () => {
{
key: UserMetadataKey.PREFERENCES,
value: { emailNotifications: { enabled: false, albumUpdate: true } },
- user: userStub.user1,
- userId: userStub.user1.id,
},
],
});
@@ -559,8 +545,6 @@ describe(NotificationService.name, () => {
{
key: UserMetadataKey.PREFERENCES,
value: { emailNotifications: { enabled: true, albumUpdate: false } },
- user: userStub.user1,
- userId: userStub.user1.id,
},
],
});
diff --git a/server/src/services/session.service.spec.ts b/server/src/services/session.service.spec.ts
index 3d1b09a39d..c3ab5619be 100644
--- a/server/src/services/session.service.spec.ts
+++ b/server/src/services/session.service.spec.ts
@@ -1,7 +1,7 @@
import { JobStatus } from 'src/enum';
import { SessionService } from 'src/services/session.service';
import { authStub } from 'test/fixtures/auth.stub';
-import { sessionStub } from 'test/fixtures/session.stub';
+import { factory } from 'test/small.factory';
import { newTestService, ServiceMocks } from 'test/utils';
describe('SessionService', () => {
@@ -45,40 +45,35 @@ describe('SessionService', () => {
describe('getAll', () => {
it('should get the devices', async () => {
- mocks.session.getByUserId.mockResolvedValue([sessionStub.valid as any, sessionStub.inactive]);
- await expect(sut.getAll(authStub.user1)).resolves.toEqual([
- {
- createdAt: '2021-01-01T00:00:00.000Z',
- current: true,
- deviceOS: '',
- deviceType: '',
- id: 'token-id',
- updatedAt: expect.any(String),
- },
- {
- createdAt: '2021-01-01T00:00:00.000Z',
- current: false,
- deviceOS: 'Android',
- deviceType: 'Mobile',
- id: 'not_active',
- updatedAt: expect.any(String),
- },
+ const currentSession = factory.session();
+ const otherSession = factory.session();
+ const auth = factory.auth({ session: currentSession });
+
+ mocks.session.getByUserId.mockResolvedValue([currentSession, otherSession]);
+
+ await expect(sut.getAll(auth)).resolves.toEqual([
+ expect.objectContaining({ current: true, id: currentSession.id }),
+ expect.objectContaining({ current: false, id: otherSession.id }),
]);
- expect(mocks.session.getByUserId).toHaveBeenCalledWith(authStub.user1.user.id);
+ expect(mocks.session.getByUserId).toHaveBeenCalledWith(auth.user.id);
});
});
describe('logoutDevices', () => {
it('should logout all devices', async () => {
- mocks.session.getByUserId.mockResolvedValue([sessionStub.inactive, sessionStub.valid] as any[]);
+ const currentSession = factory.session();
+ const otherSession = factory.session();
+ const auth = factory.auth({ session: currentSession });
+
+ mocks.session.getByUserId.mockResolvedValue([currentSession, otherSession]);
mocks.session.delete.mockResolvedValue();
- await sut.deleteAll(authStub.user1);
+ await sut.deleteAll(auth);
- expect(mocks.session.getByUserId).toHaveBeenCalledWith(authStub.user1.user.id);
- expect(mocks.session.delete).toHaveBeenCalledWith('not_active');
- expect(mocks.session.delete).not.toHaveBeenCalledWith('token-id');
+ expect(mocks.session.getByUserId).toHaveBeenCalledWith(auth.user.id);
+ expect(mocks.session.delete).toHaveBeenCalledWith(otherSession.id);
+ expect(mocks.session.delete).not.toHaveBeenCalledWith(currentSession.id);
});
});
diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts
index ef06b6f4b1..d1859ed419 100644
--- a/server/src/services/user.service.ts
+++ b/server/src/services/user.service.ts
@@ -8,12 +8,11 @@ import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto';
import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto';
import { CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto';
import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto, mapUser, mapUserAdmin } from 'src/dtos/user.dto';
-import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
import { UserEntity } from 'src/entities/user.entity';
import { CacheControl, JobName, JobStatus, QueueName, StorageFolder, UserMetadataKey } from 'src/enum';
import { UserFindOptions } from 'src/repositories/user.repository';
import { BaseService } from 'src/services/base.service';
-import { JobOf } from 'src/types';
+import { JobOf, UserMetadataItem } from 'src/types';
import { ImmichFileResponse } from 'src/utils/file';
import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences';
@@ -135,7 +134,7 @@ export class UserService extends BaseService {
const metadata = await this.userRepository.getMetadata(auth.user.id);
const license = metadata.find(
- (item): item is UserMetadataEntity => item.key === UserMetadataKey.LICENSE,
+ (item): item is UserMetadataItem => item.key === UserMetadataKey.LICENSE,
);
if (!license) {
throw new NotFoundException();
diff --git a/server/src/types.ts b/server/src/types.ts
index 6620621da8..b79bc3546b 100644
--- a/server/src/types.ts
+++ b/server/src/types.ts
@@ -11,6 +11,8 @@ import {
SyncEntityType,
SystemMetadataKey,
TranscodeTarget,
+ UserAvatarColor,
+ UserMetadataKey,
VideoCodec,
} from 'src/enum';
import { ActivityRepository } from 'src/repositories/activity.repository';
@@ -492,3 +494,54 @@ export interface SystemMetadata extends Record = {
+ key: T;
+ value: UserMetadata[T];
+};
+
+export interface UserPreferences {
+ folders: {
+ enabled: boolean;
+ sidebarWeb: boolean;
+ };
+ memories: {
+ enabled: boolean;
+ };
+ people: {
+ enabled: boolean;
+ sidebarWeb: boolean;
+ };
+ ratings: {
+ enabled: boolean;
+ };
+ sharedLinks: {
+ enabled: boolean;
+ sidebarWeb: boolean;
+ };
+ tags: {
+ enabled: boolean;
+ sidebarWeb: boolean;
+ };
+ avatar: {
+ color: UserAvatarColor;
+ };
+ emailNotifications: {
+ enabled: boolean;
+ albumInvite: boolean;
+ albumUpdate: boolean;
+ };
+ download: {
+ archiveSize: number;
+ includeEmbeddedVideos: boolean;
+ };
+ purchase: {
+ showSupportBadge: boolean;
+ hideBuyButtonUntil: string;
+ };
+}
+
+export interface UserMetadata extends Record> {
+ [UserMetadataKey.PREFERENCES]: DeepPartial;
+ [UserMetadataKey.LICENSE]: { licenseKey: string; activationKey: string; activatedAt: string };
+}
diff --git a/server/src/utils/asset.util.ts b/server/src/utils/asset.util.ts
index 96ef90bfce..575cbb4a21 100644
--- a/server/src/utils/asset.util.ts
+++ b/server/src/utils/asset.util.ts
@@ -13,7 +13,10 @@ import { PartnerRepository } from 'src/repositories/partner.repository';
import { IBulkAsset, ImmichFile, UploadFile } from 'src/types';
import { checkAccess } from 'src/utils/access';
-export const getAssetFile = (files: AssetFileEntity[], type: AssetFileType | GeneratedImageType) => {
+export const getAssetFile = (
+ files: T[],
+ type: AssetFileType | GeneratedImageType,
+) => {
return (files || []).find((file) => file.type === type);
};
diff --git a/server/src/utils/preferences.ts b/server/src/utils/preferences.ts
index 14e61f1919..584c5300cd 100644
--- a/server/src/utils/preferences.ts
+++ b/server/src/utils/preferences.ts
@@ -1,10 +1,58 @@
import _ from 'lodash';
import { UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto';
-import { UserMetadataItem, UserPreferences, getDefaultPreferences } from 'src/entities/user-metadata.entity';
-import { UserMetadataKey } from 'src/enum';
-import { DeepPartial } from 'src/types';
+import { UserAvatarColor, UserMetadataKey } from 'src/enum';
+import { DeepPartial, UserMetadataItem, UserPreferences } from 'src/types';
+import { HumanReadableSize } from 'src/utils/bytes';
import { getKeysDeep } from 'src/utils/misc';
+const getDefaultPreferences = (user: { email: string }): UserPreferences => {
+ const values = Object.values(UserAvatarColor);
+ const randomIndex = Math.floor(
+ [...user.email].map((letter) => letter.codePointAt(0) ?? 0).reduce((a, b) => a + b, 0) % values.length,
+ );
+
+ return {
+ folders: {
+ enabled: false,
+ sidebarWeb: false,
+ },
+ memories: {
+ enabled: true,
+ },
+ people: {
+ enabled: true,
+ sidebarWeb: false,
+ },
+ sharedLinks: {
+ enabled: true,
+ sidebarWeb: false,
+ },
+ ratings: {
+ enabled: false,
+ },
+ tags: {
+ enabled: false,
+ sidebarWeb: false,
+ },
+ avatar: {
+ color: values[randomIndex],
+ },
+ emailNotifications: {
+ enabled: true,
+ albumInvite: true,
+ albumUpdate: true,
+ },
+ download: {
+ archiveSize: HumanReadableSize.GiB * 4,
+ includeEmbeddedVideos: false,
+ },
+ purchase: {
+ showSupportBadge: true,
+ hideBuyButtonUntil: new Date(2022, 1, 12).toISOString(),
+ },
+ };
+};
+
export const getPreferences = (email: string, metadata: UserMetadataItem[]): UserPreferences => {
const preferences = getDefaultPreferences({ email });
const item = metadata.find(({ key }) => key === UserMetadataKey.PREFERENCES);
diff --git a/server/src/utils/tag.ts b/server/src/utils/tag.ts
index b095fcfd85..4e8a86a7f6 100644
--- a/server/src/utils/tag.ts
+++ b/server/src/utils/tag.ts
@@ -1,15 +1,15 @@
+import { Tag } from 'src/database';
import { TagRepository } from 'src/repositories/tag.repository';
-import { TagItem } from 'src/types';
type UpsertRequest = { userId: string; tags: string[] };
export const upsertTags = async (repository: TagRepository, { userId, tags }: UpsertRequest) => {
tags = [...new Set(tags)];
- const results: TagItem[] = [];
+ const results: Tag[] = [];
for (const tag of tags) {
const parts = tag.split('/').filter(Boolean);
- let parent: TagItem | undefined;
+ let parent: Tag | undefined;
for (const part of parts) {
const value = parent ? `${parent.value}/${part}` : part;
diff --git a/server/test/fixtures/asset.stub.ts b/server/test/fixtures/asset.stub.ts
index d56c5f6efd..72016e9862 100644
--- a/server/test/fixtures/asset.stub.ts
+++ b/server/test/fixtures/asset.stub.ts
@@ -89,7 +89,6 @@ export const assetStub = {
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
faces: [],
sidecarPath: null,
@@ -123,7 +122,6 @@ export const assetStub = {
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
originalFileName: 'IMG_456.jpg',
faces: [],
@@ -162,7 +160,6 @@ export const assetStub = {
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.ext',
faces: [],
@@ -197,7 +194,6 @@ export const assetStub = {
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.jpg',
faces: [],
@@ -243,7 +239,6 @@ export const assetStub = {
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.jpg',
faces: [],
@@ -283,7 +278,6 @@ export const assetStub = {
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.jpg',
faces: [],
@@ -325,7 +319,6 @@ export const assetStub = {
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.jpg',
faces: [],
@@ -363,7 +356,6 @@ export const assetStub = {
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.jpg',
faces: [],
@@ -404,7 +396,6 @@ export const assetStub = {
livePhotoVideo: null,
livePhotoVideoId: null,
libraryId: 'library-id',
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.jpg',
faces: [],
@@ -443,7 +434,6 @@ export const assetStub = {
livePhotoVideo: null,
livePhotoVideoId: null,
isExternal: false,
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.ext',
faces: [],
@@ -480,7 +470,6 @@ export const assetStub = {
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.ext',
faces: [],
@@ -519,7 +508,6 @@ export const assetStub = {
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
faces: [],
sidecarPath: null,
@@ -608,7 +596,6 @@ export const assetStub = {
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.ext',
faces: [],
@@ -650,7 +637,6 @@ export const assetStub = {
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.ext',
faces: [],
@@ -685,7 +671,6 @@ export const assetStub = {
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.ext',
faces: [],
@@ -721,7 +706,6 @@ export const assetStub = {
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
faces: [],
sidecarPath: null,
@@ -759,7 +743,6 @@ export const assetStub = {
livePhotoVideo: null,
livePhotoVideoId: null,
libraryId: 'library-id',
- tags: [],
sharedLinks: [],
originalFileName: 'photo.jpg',
faces: [],
@@ -797,7 +780,6 @@ export const assetStub = {
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.dng',
faces: [],
@@ -837,7 +819,6 @@ export const assetStub = {
isExternal: false,
livePhotoVideo: null,
livePhotoVideoId: null,
- tags: [],
sharedLinks: [],
originalFileName: 'asset-id.hif',
faces: [],
@@ -851,88 +832,4 @@ export const assetStub = {
duplicateId: null,
isOffline: false,
}),
-
- hasEmbedding: Object.freeze({
- id: 'asset-id-embedding',
- status: AssetStatus.ACTIVE,
- deviceAssetId: 'device-asset-id',
- fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
- fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
- owner: userStub.user1,
- ownerId: 'user-id',
- deviceId: 'device-id',
- originalPath: '/original/path.jpg',
- checksum: Buffer.from('file hash', 'utf8'),
- type: AssetType.IMAGE,
- files,
- thumbhash: Buffer.from('blablabla', 'base64'),
- encodedVideoPath: null,
- createdAt: new Date('2023-02-23T05:06:29.716Z'),
- updatedAt: new Date('2023-02-23T05:06:29.716Z'),
- localDateTime: new Date('2023-02-23T05:06:29.716Z'),
- isFavorite: true,
- isArchived: false,
- duration: null,
- isVisible: true,
- isExternal: false,
- livePhotoVideo: null,
- livePhotoVideoId: null,
- tags: [],
- sharedLinks: [],
- originalFileName: 'asset-id.jpg',
- faces: [],
- deletedAt: null,
- sidecarPath: null,
- exifInfo: {
- fileSizeInByte: 5000,
- } as ExifEntity,
- duplicateId: null,
- smartSearch: {
- assetId: 'asset-id',
- embedding: '[1, 2, 3, 4]',
- },
- isOffline: false,
- }),
-
- hasDupe: Object.freeze({
- id: 'asset-id-dupe',
- status: AssetStatus.ACTIVE,
- deviceAssetId: 'device-asset-id',
- fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
- fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
- owner: userStub.user1,
- ownerId: 'user-id',
- deviceId: 'device-id',
- originalPath: '/original/path.jpg',
- checksum: Buffer.from('file hash', 'utf8'),
- type: AssetType.IMAGE,
- files,
- thumbhash: Buffer.from('blablabla', 'base64'),
- encodedVideoPath: null,
- createdAt: new Date('2023-02-23T05:06:29.716Z'),
- updatedAt: new Date('2023-02-23T05:06:29.716Z'),
- localDateTime: new Date('2023-02-23T05:06:29.716Z'),
- isFavorite: true,
- isArchived: false,
- duration: null,
- isVisible: true,
- isExternal: false,
- livePhotoVideo: null,
- livePhotoVideoId: null,
- tags: [],
- sharedLinks: [],
- originalFileName: 'asset-id.jpg',
- faces: [],
- deletedAt: null,
- sidecarPath: null,
- exifInfo: {
- fileSizeInByte: 5000,
- } as ExifEntity,
- duplicateId: 'duplicate-id',
- smartSearch: {
- assetId: 'asset-id',
- embedding: '[1, 2, 3, 4]',
- },
- isOffline: false,
- }),
};
diff --git a/server/test/fixtures/auth.stub.ts b/server/test/fixtures/auth.stub.ts
index f894314258..f5fbe07b53 100644
--- a/server/test/fixtures/auth.stub.ts
+++ b/server/test/fixtures/auth.stub.ts
@@ -1,5 +1,5 @@
+import { Session } from 'src/database';
import { AuthDto } from 'src/dtos/auth.dto';
-import { SessionEntity } from 'src/entities/session.entity';
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
const authUser = {
@@ -27,7 +27,7 @@ export const authStub = {
user: authUser.user1,
session: {
id: 'token-id',
- } as SessionEntity,
+ } as Session,
}),
user2: Object.freeze({
user: {
@@ -40,7 +40,7 @@ export const authStub = {
},
session: {
id: 'token-id',
- } as SessionEntity,
+ } as Session,
}),
adminSharedLink: Object.freeze({
user: authUser.admin,
diff --git a/server/test/fixtures/session.stub.ts b/server/test/fixtures/session.stub.ts
deleted file mode 100644
index af06237473..0000000000
--- a/server/test/fixtures/session.stub.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { SessionEntity } from 'src/entities/session.entity';
-import { userStub } from 'test/fixtures/user.stub';
-
-export const sessionStub = {
- valid: Object.freeze({
- id: 'token-id',
- token: 'auth_token',
- userId: userStub.user1.id,
- user: userStub.user1,
- createdAt: new Date('2021-01-01'),
- updatedAt: new Date(),
- deviceType: '',
- deviceOS: '',
- updateId: 'uuid-v7',
- }),
- inactive: Object.freeze({
- id: 'not_active',
- token: 'auth_token',
- userId: userStub.user1.id,
- user: userStub.user1,
- createdAt: new Date('2021-01-01'),
- updatedAt: new Date('2021-01-01'),
- deviceType: 'Mobile',
- deviceOS: 'Android',
- updateId: 'uuid-v7',
- }),
-};
diff --git a/server/test/fixtures/shared-link.stub.ts b/server/test/fixtures/shared-link.stub.ts
index 6ee31c0dea..739d6c5b93 100644
--- a/server/test/fixtures/shared-link.stub.ts
+++ b/server/test/fixtures/shared-link.stub.ts
@@ -241,7 +241,6 @@ export const sharedLinkStub = {
autoStackId: null,
rating: 3,
},
- tags: [],
sharedLinks: [],
faces: [],
sidecarPath: null,
diff --git a/server/test/fixtures/tag.stub.ts b/server/test/fixtures/tag.stub.ts
index 1a19c2a002..7a2cacf126 100644
--- a/server/test/fixtures/tag.stub.ts
+++ b/server/test/fixtures/tag.stub.ts
@@ -1,7 +1,7 @@
+import { Tag } from 'src/database';
import { TagResponseDto } from 'src/dtos/tag.dto';
-import { TagItem } from 'src/types';
-const parent = Object.freeze({
+const parent = Object.freeze({
id: 'tag-parent',
createdAt: new Date('2021-01-01T00:00:00Z'),
updatedAt: new Date('2021-01-01T00:00:00Z'),
@@ -10,7 +10,7 @@ const parent = Object.freeze({
parentId: null,
});
-const child = Object.freeze({
+const child = Object.freeze({
id: 'tag-child',
createdAt: new Date('2021-01-01T00:00:00Z'),
updatedAt: new Date('2021-01-01T00:00:00Z'),
diff --git a/server/test/fixtures/user.stub.ts b/server/test/fixtures/user.stub.ts
index 0ed1502fb9..844b8c61b9 100644
--- a/server/test/fixtures/user.stub.ts
+++ b/server/test/fixtures/user.stub.ts
@@ -38,7 +38,6 @@ export const userStub = {
assets: [],
metadata: [
{
- userId: authStub.user1.user.id,
key: UserMetadataKey.PREFERENCES,
value: { avatar: { color: UserAvatarColor.PRIMARY } },
},
diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts
index a17ca03e85..36fb298f7f 100644
--- a/server/test/repositories/asset.repository.mock.ts
+++ b/server/test/repositories/asset.repository.mock.ts
@@ -11,6 +11,8 @@ export const newAssetRepositoryMock = (): Mocked randomUUID() as string;
export const newUuids = () =>
@@ -19,7 +32,11 @@ export const newEmbedding = () => {
return '[' + embedding + ']';
};
-const authFactory = ({ apiKey, ...user }: Partial & { apiKey?: Partial } = {}) => {
+const authFactory = ({
+ apiKey,
+ session,
+ ...user
+}: Partial & { apiKey?: Partial; session?: { id: string } } = {}) => {
const auth: AuthDto = {
user: authUserFactory(user),
};
@@ -28,6 +45,10 @@ const authFactory = ({ apiKey, ...user }: Partial & { apiKey?: Partial
auth.apiKey = authApiKeyFactory(apiKey);
}
+ if (session) {
+ auth.session = { id: session.id };
+ }
+
return auth;
};
@@ -64,7 +85,7 @@ const partnerFactory = (partner: Partial = {}) => {
};
};
-const sessionFactory = () => ({
+const sessionFactory = (session: Partial = {}) => ({
id: newUuid(),
createdAt: newDate(),
updatedAt: newDate(),
@@ -73,6 +94,7 @@ const sessionFactory = () => ({
deviceType: 'mobile',
token: 'abc123',
userId: newUuid(),
+ ...session,
});
const stackFactory = () => ({
@@ -143,7 +165,7 @@ const assetFactory = (asset: Partial = {}) => ({
...asset,
});
-const activityFactory = (activity: Partial = {}) => {
+const activityFactory = (activity: Partial = {}) => {
const userId = activity.userId || newUuid();
return {
id: newUuid(),
@@ -186,7 +208,7 @@ const libraryFactory = (library: Partial = {}) => ({
...library,
});
-const memoryFactory = (memory: Partial = {}) => ({
+const memoryFactory = (memory: Partial = {}) => ({
id: newUuid(),
createdAt: newDate(),
updatedAt: newDate(),
@@ -210,6 +232,14 @@ const versionHistoryFactory = () => ({
version: '1.123.45',
});
+const assetSidecarWriteFactory = (asset: Partial = {}) => ({
+ id: newUuid(),
+ sidecarPath: '/path/to/original-path.jpg.xmp',
+ originalPath: '/path/to/original-path.jpg.xmp',
+ tags: [],
+ ...asset,
+});
+
export const factory = {
activity: activityFactory,
apiKey: apiKeyFactory,
@@ -225,4 +255,7 @@ export const factory = {
user: userFactory,
userAdmin: userAdminFactory,
versionHistory: versionHistoryFactory,
+ jobAssets: {
+ sidecarWrite: assetSidecarWriteFactory,
+ },
};
diff --git a/web/package-lock.json b/web/package-lock.json
index 501496cfaf..029d6aede5 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -87,7 +87,7 @@
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
- "@types/node": "^22.13.14",
+ "@types/node": "^22.14.0",
"typescript": "^5.3.3"
}
},
@@ -2127,9 +2127,9 @@
}
},
"node_modules/@sveltejs/kit": {
- "version": "2.20.2",
- "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.2.tgz",
- "integrity": "sha512-Dv8TOAZC9vyfcAB9TMsvUEJsRbklRTeNfcYBPaeH6KnABJ99i3CvCB2eNx8fiiliIqe+9GIchBg4RodRH5p1BQ==",
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.3.tgz",
+ "integrity": "sha512-z1SQ8qra/kGY3DzarG7xc6XsbKm8UY3SnI82XLI3PqMYWbYj/LpjPWuAz9WA5EyLjFNLD7sOAOEW8Gt4yjr5Vg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2541,9 +2541,9 @@
}
},
"node_modules/@types/luxon": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.0.tgz",
- "integrity": "sha512-RtEj20xRyG7cRp142MkQpV3GRF8Wo2MtDkKLz65MQs7rM1Lh8bz+HtfPXCCJEYpnDFu6VwAq/Iv2Ikyp9Jw/hw==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz",
+ "integrity": "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==",
"dev": true,
"license": "MIT"
},
@@ -4248,22 +4248,6 @@
}
}
},
- "node_modules/eslint-compat-utils": {
- "version": "0.6.4",
- "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.6.4.tgz",
- "integrity": "sha512-/u+GQt8NMfXO8w17QendT4gvO5acfxQsAKirAt0LVxDnr2N8YLCVbregaNc/Yhp7NM128DwCaRvr8PLDfeNkQw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.5.4"
- },
- "engines": {
- "node": ">=12"
- },
- "peerDependencies": {
- "eslint": ">=6.0.0"
- }
- },
"node_modules/eslint-config-prettier": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz",
@@ -4278,15 +4262,14 @@
}
},
"node_modules/eslint-plugin-svelte": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.4.1.tgz",
- "integrity": "sha512-wgbRwN/6FampBBiIuuLSmp4QRqmuHuexbuRJwx+kqzsxKOhakU8o8sVgGhsf/bQiZkOmWF/5Mrj2CHmVMwY+YQ==",
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.5.1.tgz",
+ "integrity": "sha512-Qn1slddZHfqYiDO6IN8/iN3YL+VuHlgYjm30FT+hh0Jf/TX0jeZMTJXQMajFm5f6f6hURi+XO8P+NPYD+T4jkg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.1",
"@jridgewell/sourcemap-codec": "^1.5.0",
- "eslint-compat-utils": "^0.6.4",
"esutils": "^2.0.3",
"known-css-properties": "^0.35.0",
"postcss": "^8.4.49",
@@ -4611,9 +4594,9 @@
}
},
"node_modules/esrap": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.3.tgz",
- "integrity": "sha512-Xddc1RsoFJ4z9nR7W7BFaEPIp4UXoeQ0+077UdWLxbafMQFyU79sQJMk7kxNgRwQ9/aVgaKacCHC2pUACGwmYw==",
+ "version": "1.4.6",
+ "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.6.tgz",
+ "integrity": "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
@@ -4687,9 +4670,9 @@
}
},
"node_modules/fabric": {
- "version": "6.6.1",
- "resolved": "https://registry.npmjs.org/fabric/-/fabric-6.6.1.tgz",
- "integrity": "sha512-QrQkx6I7daFL/WdkrE8VOEiAr/ffLK36NQ0t/vNZt8P7QIXPpjT4HegjOatUW1G6vYlulX4pI1P/5NeqIgsDig==",
+ "version": "6.6.2",
+ "resolved": "https://registry.npmjs.org/fabric/-/fabric-6.6.2.tgz",
+ "integrity": "sha512-Mu8ETBfCl829NctOcroAkJT/t/1UWA29bmBPvqVbDtX0uiWFQD63Hk156fW9tn35PZe/kJYeap+bvVq33jEQJw==",
"license": "MIT",
"engines": {
"node": ">=16.20.0"
@@ -8272,9 +8255,9 @@
}
},
"node_modules/svelte": {
- "version": "5.25.5",
- "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.25.5.tgz",
- "integrity": "sha512-ULi9rkVWQJyJYZSpy6SIgSTchWadyWG1QYAUx3JAXL2gXrnhdXtoB20KmXGSNdtNyquq3eYd/gkwAkLcL5PGWw==",
+ "version": "5.25.6",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.25.6.tgz",
+ "integrity": "sha512-RGkaeAXDuJdvhA1fdSM5GgD++vYfJYijZL0uN6kM2s/TRJ663jktBhZlF0qjzAJGR/34PtaeT3G8MKJY1EKeqg==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
@@ -8286,7 +8269,7 @@
"axobject-query": "^4.1.0",
"clsx": "^2.1.1",
"esm-env": "^1.2.1",
- "esrap": "^1.4.3",
+ "esrap": "^1.4.6",
"is-reference": "^3.0.3",
"locate-character": "^3.0.0",
"magic-string": "^0.30.11",
diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte
index 6befc9e623..584f2596ad 100644
--- a/web/src/lib/components/photos-page/asset-grid.svelte
+++ b/web/src/lib/components/photos-page/asset-grid.svelte
@@ -115,10 +115,7 @@
};
beforeNavigate(() => (assetStore.suspendTransitions = true));
afterNavigate((nav) => {
- const { complete, type } = nav;
- if (type === 'enter') {
- return;
- }
+ const { complete } = nav;
complete.then(completeNav, completeNav);
});