diff --git a/deployment/mise.toml b/deployment/mise.toml index 00c6826343..61a50bb666 100644 --- a/deployment/mise.toml +++ b/deployment/mise.toml @@ -1,5 +1,5 @@ [tools] -terragrunt = "1.0.1" +terragrunt = "1.0.2" opentofu = "1.11.6" [tasks."tg:fmt"] diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 979d7fc0ee..25751879f3 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -85,7 +85,7 @@ services: container_name: immich_prometheus ports: - 9090:9090 - image: prom/prometheus@sha256:5550dc63da361dc30f6fe02ac0e4dfc736ededfef3c8d12a634db04a67824d78 + image: prom/prometheus@sha256:e4254400b85610324913f0dc4acf92603d9984e7519414c5a12811aa6146acc3 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus diff --git a/docs/docs/administration/postgres-standalone.md b/docs/docs/administration/postgres-standalone.md index 84681fdfa6..aa19e28cc1 100644 --- a/docs/docs/administration/postgres-standalone.md +++ b/docs/docs/administration/postgres-standalone.md @@ -81,7 +81,7 @@ VectorChord is the successor extension to pgvecto.rs, allowing for higher perfor ### Migrating from pgvecto.rs -Support for pgvecto.rs will be dropped in a later release, hence we recommend all users currently using pgvecto.rs to migrate to VectorChord at their convenience. There are two primary approaches to do so. +Support for pgvecto.rs has been dropped as of 3.0, hence all users currently using pgvecto.rs should migrate to VectorChord. There are two primary approaches to do so. The easiest option is to have both extensions installed during the migration: diff --git a/docs/docs/guides/remote-access.md b/docs/docs/guides/remote-access.md index 518b003c3a..09e34c5107 100644 --- a/docs/docs/guides/remote-access.md +++ b/docs/docs/guides/remote-access.md @@ -39,7 +39,7 @@ You can learn how to set up Tailscale together with Immich with the [tutorial vi ### Cons - The Tailscale client usually needs to run as root on your devices and it increases the attack surface slightly compared to a minimal Wireguard server. e.g., an [RCE vulnerability](https://github.com/tailscale/tailscale/security/advisories/GHSA-vqp6-rc3h-83cp) was discovered in the Windows Tailscale client in November 2022. -- Tailscale is a paid service. However, there is a generous [free tier](https://tailscale.com/pricing/) that permits up to 3 users and up to 100 devices. +- Tailscale is a paid service. However, there is a generous [free tier](https://tailscale.com/pricing/) suitable for personal use. - Tailscale needs to be installed and running on both server-side and client-side. ## Option 3: Reverse Proxy diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index b29c233153..1b67637ac0 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -81,7 +81,7 @@ Information on the current workers can be found [here](/administration/jobs-work | `DB_PASSWORD` | Database password | `postgres` | server, database\*1 | | `DB_DATABASE_NAME` | Database name | `immich` | server, database\*1 | | `DB_SSL_MODE` | Database SSL mode | | server | -| `DB_VECTOR_EXTENSION`\*2 | Database vector extension (one of [`vectorchord`, `pgvector`, `pgvecto.rs`]) | | server | +| `DB_VECTOR_EXTENSION`\*2 | Database vector extension (one of [`vectorchord`, `pgvector`]) | | server | | `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server | | `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])\*3 | `SSD` | database | diff --git a/docs/docs/install/upgrading.md b/docs/docs/install/upgrading.md index 12e5c9c342..38fc056f80 100644 --- a/docs/docs/install/upgrading.md +++ b/docs/docs/install/upgrading.md @@ -130,7 +130,3 @@ These storage mediums have different performance characteristics. As a result, t #### Can I use the new database image as a general PostgreSQL image outside of Immich? It’s a standard PostgreSQL container image that additionally contains the VectorChord, pgvector, and (optionally) pgvecto.rs extensions. If you were using the previous pgvecto.rs image for other purposes, you can similarly do so with this image. - -#### If pgvecto.rs and pgvector still work, why should I switch to VectorChord? - -VectorChord is faster, more stable, uses less RAM, and (with the settings Immich uses) offers higher-quality results than pgvector and pgvecto.rs. This translates to better search and facial recognition experiences. In addition, pgvecto.rs support will be dropped in the future, so changing it sooner will avoid disruption. diff --git a/e2e/src/responses.ts b/e2e/src/responses.ts index 3d7971d6f0..2ec7aecb0e 100644 --- a/e2e/src/responses.ts +++ b/e2e/src/responses.ts @@ -2,82 +2,43 @@ import { expect } from 'vitest'; export const errorDto = { unauthorized: { - error: 'Unauthorized', - statusCode: 401, message: 'Authentication required', - correlationId: expect.any(String), }, unauthorizedWithMessage: (message: string) => ({ - error: 'Unauthorized', - statusCode: 401, message, - correlationId: expect.any(String), }), forbidden: { - error: 'Forbidden', - statusCode: 403, message: expect.any(String), - correlationId: expect.any(String), }, missingPermission: (permission: string) => ({ - error: 'Forbidden', - statusCode: 403, message: `Missing required permission: ${permission}`, - correlationId: expect.any(String), }), wrongPassword: { - error: 'Bad Request', - statusCode: 400, message: 'Wrong password', - correlationId: expect.any(String), }, invalidToken: { - error: 'Unauthorized', - statusCode: 401, message: 'Invalid user token', - correlationId: expect.any(String), }, invalidShareKey: { - error: 'Unauthorized', - statusCode: 401, message: 'Invalid share key', - correlationId: expect.any(String), }, passwordRequired: { - error: 'Unauthorized', - statusCode: 401, message: 'Password required', - correlationId: expect.any(String), }, badRequest: (message: any = null) => ({ - error: 'Bad Request', - statusCode: 400, message: message ?? expect.anything(), - correlationId: expect.any(String), }), noPermission: { - error: 'Bad Request', - statusCode: 400, message: expect.stringContaining('Not found or no'), - correlationId: expect.any(String), }, incorrectLogin: { - error: 'Unauthorized', - statusCode: 401, message: 'Incorrect email or password', - correlationId: expect.any(String), }, alreadyHasAdmin: { - error: 'Bad Request', - statusCode: 400, message: 'The server already has an admin', - correlationId: expect.any(String), }, invalidEmail: { - error: 'Bad Request', - statusCode: 400, message: ['email must be an email'], - correlationId: expect.any(String), }, }; diff --git a/e2e/src/specs/server/api/oauth.e2e-spec.ts b/e2e/src/specs/server/api/oauth.e2e-spec.ts index 9dcb431a4b..157fdfc84c 100644 --- a/e2e/src/specs/server/api/oauth.e2e-spec.ts +++ b/e2e/src/specs/server/api/oauth.e2e-spec.ts @@ -332,9 +332,7 @@ describe(`/oauth`, () => { const { status, body } = await request(app).post('/oauth/callback').send(callbackParams); expect(status).toBe(500); expect(body).toMatchObject({ - error: 'Internal Server Error', message: 'Failed to finish oauth', - statusCode: 500, }); }); @@ -495,11 +493,10 @@ describe(`/oauth`, () => { }); it('should reject OAuth discovery over HTTP', async () => { - const { status, body } = await request(app) + const { status } = await request(app) .post('/oauth/authorize') .send({ redirectUri: 'http://127.0.0.1:2285/auth/login' }); expect(status).toBe(500); - expect(body).toMatchObject({ statusCode: 500 }); }); }); }); diff --git a/e2e/src/ui/generators/timeline/model-objects.ts b/e2e/src/ui/generators/timeline/model-objects.ts index e300de1161..f5654afd5e 100644 --- a/e2e/src/ui/generators/timeline/model-objects.ts +++ b/e2e/src/ui/generators/timeline/model-objects.ts @@ -32,8 +32,12 @@ export function generateThumbhash(rng: SeededRandom): string { return Array.from({ length: 10 }, () => rng.nextInt(0, 256).toString(16).padStart(2, '0')).join(''); } -export function generateDuration(rng: SeededRandom): string { - return `${rng.nextInt(GENERATION_CONSTANTS.MIN_VIDEO_DURATION_SECONDS, GENERATION_CONSTANTS.MAX_VIDEO_DURATION_SECONDS)}.${rng.nextInt(0, 1000).toString().padStart(3, '0')}`; +export function generateDuration(rng: SeededRandom): number { + return ( + rng.nextInt(GENERATION_CONSTANTS.MIN_VIDEO_DURATION_SECONDS, GENERATION_CONSTANTS.MAX_VIDEO_DURATION_SECONDS) * + 1000 + + rng.nextInt(0, 1000) + ); } export function generateUUID(): string { diff --git a/e2e/src/ui/generators/timeline/timeline-config.ts b/e2e/src/ui/generators/timeline/timeline-config.ts index 992480eef9..4dea2f4f78 100644 --- a/e2e/src/ui/generators/timeline/timeline-config.ts +++ b/e2e/src/ui/generators/timeline/timeline-config.ts @@ -43,7 +43,7 @@ export type MockTimelineAsset = { isTrashed: boolean; isVideo: boolean; isImage: boolean; - duration: string | null; + duration: number | null; projectionType: string | null; livePhotoVideoId: string | null; city: string | null; diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile index 8126ff0859..46c32f3d6a 100644 --- a/machine-learning/Dockerfile +++ b/machine-learning/Dockerfile @@ -48,14 +48,14 @@ FROM python:3.13-slim-trixie@sha256:d168b8d9eb761f4d3fe305ebd04aeb7e7f2de0297cec RUN apt-get update && \ apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \ - wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-core-2_2.28.4+20760_amd64.deb && \ - wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-opencl-2_2.28.4+20760_amd64.deb && \ - wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/intel-opencl-icd_26.05.37020.3-0_amd64.deb && \ + wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.32.7/intel-igc-core-2_2.32.7+21184_amd64.deb && \ + wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.32.7/intel-igc-opencl-2_2.32.7+21184_amd64.deb && \ + wget -nv https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/intel-opencl-icd_26.14.37833.4-0_amd64.deb && \ wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \ wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \ wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \ # TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file - wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/libigdgmm12_22.9.0_amd64.deb && \ + wget -nv https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/libigdgmm12_22.9.0_amd64.deb && \ dpkg -i *.deb && \ rm *.deb && \ apt-get remove wget -yqq && \ diff --git a/machine-learning/immich_ml/main.py b/machine-learning/immich_ml/main.py index e7e3a719bb..4fca7a2e2b 100644 --- a/machine-learning/immich_ml/main.py +++ b/machine-learning/immich_ml/main.py @@ -183,7 +183,10 @@ async def predict( text: str | None = Form(default=None), ) -> Any: if image is not None: - inputs: Image | str = await run(lambda: decode_pil(image)) + decoded = await run(lambda: decode_pil(image)) + if decoded.width == 0 or decoded.height == 0: + raise HTTPException(400, "Image has zero width or height") + inputs: Image | str = decoded elif text is not None: inputs = text else: diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index 640996f54a..d61df51e38 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -9,12 +9,12 @@ dependencies = [ "aiocache>=0.12.1,<1.0", "fastapi>=0.95.2,<1.0", "gunicorn>=21.1.0", - "huggingface-hub>=0.20.1,<1.0", + "huggingface-hub>=1.0,<2.0", "insightface>=0.7.3,<1.0", "numpy<2.4.0", "opencv-python-headless>=4.7.0.72,<5.0", "orjson>=3.9.5", - "pillow>=12.2,<12.3", + "pillow>=12.2,<13", "pydantic>=2.0.0,<3", "pydantic-settings>=2.5.2,<3", "python-multipart>=0.0.6,<1.0", diff --git a/machine-learning/test_main.py b/machine-learning/test_main.py index 0182c57c67..cce334e40e 100644 --- a/machine-learning/test_main.py +++ b/machine-learning/test_main.py @@ -1198,6 +1198,19 @@ class TestLoad: mock_model.model_format = ModelFormat.ONNX +@pytest.mark.parametrize("size", [(0, 100), (100, 0), (0, 0)]) +def test_predict_rejects_empty_image(size: tuple[int, int], deployed_app: TestClient) -> None: + with mock.patch("immich_ml.main.decode_pil", return_value=Image.new("RGB", size)): + response = deployed_app.post( + "http://localhost:3003/predict", + data={"entries": json.dumps({"clip": {"visual": {"modelName": "ViT-B-32__openai"}}})}, + files={"image": b"fake image bytes"}, + ) + + assert response.status_code == 400 + assert "zero" in response.json()["detail"].lower() + + def test_root_endpoint(deployed_app: TestClient) -> None: response = deployed_app.get("http://localhost:3003") diff --git a/mise.toml b/mise.toml index 367eb75da9..b7398a0e8d 100644 --- a/mise.toml +++ b/mise.toml @@ -15,9 +15,9 @@ config_roots = [ [tools] node = "24.15.0" -flutter = "3.41.6" -pnpm = "10.33.0" -terragrunt = "1.0.1" +flutter = "3.41.7" +pnpm = "10.33.1" +terragrunt = "1.0.2" opentofu = "1.11.6" java = "21.0.2" diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index b9cd68cb7b..fbffa69ba5 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -751,7 +751,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; CUSTOM_GROUP_ID = group.app.immich.share; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -760,7 +760,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.121.0; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile; + PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.profile; PRODUCT_NAME = "Immich-Profile"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -895,7 +895,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; CUSTOM_GROUP_ID = group.app.immich.share; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -904,7 +904,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.121.0; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug; + PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.debug; PRODUCT_NAME = "Immich-Debug"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -925,7 +925,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; CUSTOM_GROUP_ID = group.app.immich.share; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -958,7 +958,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -975,7 +975,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.Widget; + PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.debug.Widget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; @@ -1001,7 +1001,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1041,7 +1041,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1057,7 +1057,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.Widget; + PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.profile.Widget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1081,7 +1081,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; CUSTOM_GROUP_ID = group.app.immich.share; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1098,7 +1098,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.ShareExtension; + PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.debug.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -1125,7 +1125,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; CUSTOM_GROUP_ID = group.app.immich.share; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1166,7 +1166,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; CUSTOM_GROUP_ID = group.app.immich.share; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1182,7 +1182,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.ShareExtension; + PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.profile.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; diff --git a/mobile/ios/ShareExtension/Info.plist b/mobile/ios/ShareExtension/Info.plist index 0f52fbffdf..dbed75e380 100644 --- a/mobile/ios/ShareExtension/Info.plist +++ b/mobile/ios/ShareExtension/Info.plist @@ -1,35 +1,35 @@ - - AppGroupId - $(CUSTOM_GROUP_ID) - NSExtension - - NSExtensionAttributes - - IntentsSupported - - INSendMessageIntent - - NSExtensionActivationRule - SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments, + + AppGroupId + $(CUSTOM_GROUP_ID) + NSExtension + + NSExtensionAttributes + + IntentsSupported + + INSendMessageIntent + + NSExtensionActivationRule + SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments, $attachment, ( ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ) ).@count > 0 ).@count > 0 - PHSupportedMediaTypes - - Video - Image - - - NSExtensionMainStoryboard - MainInterface - NSExtensionPointIdentifier - com.apple.share-services - - - \ No newline at end of file + PHSupportedMediaTypes + + Video + Image + + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + + + diff --git a/mobile/ios/ShareExtension/ShareExtension.entitlements b/mobile/ios/ShareExtension/ShareExtension.entitlements index 4ad1a257d8..d16dcca065 100644 --- a/mobile/ios/ShareExtension/ShareExtension.entitlements +++ b/mobile/ios/ShareExtension/ShareExtension.entitlements @@ -1,10 +1,10 @@ - - com.apple.security.application-groups - - group.app.immich.share - - - + + com.apple.security.application-groups + + group.app.immich.share + + + \ No newline at end of file diff --git a/mobile/ios/WidgetExtension/WidgetExtension.entitlements b/mobile/ios/WidgetExtension/WidgetExtension.entitlements index 4ad1a257d8..d16dcca065 100644 --- a/mobile/ios/WidgetExtension/WidgetExtension.entitlements +++ b/mobile/ios/WidgetExtension/WidgetExtension.entitlements @@ -1,10 +1,10 @@ - - com.apple.security.application-groups - - group.app.immich.share - - - + + com.apple.security.application-groups + + group.app.immich.share + + + \ No newline at end of file diff --git a/mobile/ios/fastlane/Appfile b/mobile/ios/fastlane/Appfile index e233ba2dcc..77318e3603 100644 --- a/mobile/ios/fastlane/Appfile +++ b/mobile/ios/fastlane/Appfile @@ -1,5 +1,5 @@ app_identifier "app.alextran.immich" # The bundle identifier of your app -apple_id "alex.tran1502@gmail.com" # Your Apple email address +apple_id "altran@futo.org" # Your Apple email address # For more information about the Appfile, see: diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile index 9c31ced00d..ff9fc4580f 100644 --- a/mobile/ios/fastlane/Fastfile +++ b/mobile/ios/fastlane/Fastfile @@ -17,10 +17,11 @@ default_platform(:ios) platform :ios do # Constants - TEAM_ID = "2F67MQ8R79" - CODE_SIGN_IDENTITY = "Apple Distribution: Hau Tran (#{TEAM_ID})" + TEAM_ID = "2W7AC6T8T5" + CODE_SIGN_IDENTITY = "Apple Distribution: FUTO Holdings, Inc. (#{TEAM_ID})" BASE_BUNDLE_ID = "app.alextran.immich" - + DEV_BUNDLE_ID = "tech.futo.immich.testflight" + # Helper method to get App Store Connect API key def get_api_key app_store_connect_api_key( @@ -44,47 +45,45 @@ def get_version_from_pubspec end # Helper method to configure code signing for all targets - def configure_code_signing(bundle_id_suffix: "", profile_name_main:, profile_name_share:, profile_name_widget:) - bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}" - + def configure_code_signing(base_bundle_id:, profile_name_main:, profile_name_share:, profile_name_widget:) # Runner (main app) update_code_signing_settings( use_automatic_signing: false, path: "./Runner.xcodeproj", team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, code_sign_identity: CODE_SIGN_IDENTITY, - bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}", + bundle_identifier: base_bundle_id, profile_name: profile_name_main, targets: ["Runner"] ) - + # ShareExtension update_code_signing_settings( use_automatic_signing: false, path: "./Runner.xcodeproj", team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, code_sign_identity: CODE_SIGN_IDENTITY, - bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.ShareExtension", + bundle_identifier: "#{base_bundle_id}.ShareExtension", profile_name: profile_name_share, targets: ["ShareExtension"] ) - + # WidgetExtension update_code_signing_settings( use_automatic_signing: false, path: "./Runner.xcodeproj", team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, code_sign_identity: CODE_SIGN_IDENTITY, - bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.Widget", + bundle_identifier: "#{base_bundle_id}.Widget", profile_name: profile_name_widget, targets: ["WidgetExtension"] ) end - + # Helper method to build and upload to TestFlight def build_and_upload( api_key:, - bundle_id_suffix: "", + base_bundle_id:, configuration: "Release", distribute_external: true, version_number: nil, @@ -92,9 +91,8 @@ end profile_name_share:, profile_name_widget: ) - bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}" - app_identifier = "#{BASE_BUNDLE_ID}#{bundle_suffix}" - + app_identifier = base_bundle_id + # Set version number if provided if version_number increment_version_number(version_number: version_number) @@ -138,31 +136,31 @@ end desc "iOS Development Build to TestFlight (requires separate bundle ID)" lane :gha_testflight_dev do api_key = get_api_key - + # Download and install provisioning profiles from App Store Connect # Certificate is imported by GHA workflow into build.keychain # Capture profile names after each sigh call - sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development", force: true) + sigh(api_key: api_key, app_identifier: DEV_BUNDLE_ID, force: true) main_profile_name = lane_context[SharedValues::SIGH_NAME] - - sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.ShareExtension", force: true) + + sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.ShareExtension", force: true) share_profile_name = lane_context[SharedValues::SIGH_NAME] - - sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.Widget", force: true) + + sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.Widget", force: true) widget_profile_name = lane_context[SharedValues::SIGH_NAME] - + # Configure code signing for dev bundle IDs using the downloaded profile names configure_code_signing( - bundle_id_suffix: "development", + base_bundle_id: DEV_BUNDLE_ID, profile_name_main: main_profile_name, profile_name_share: share_profile_name, profile_name_widget: widget_profile_name ) - + # Build and upload build_and_upload( api_key: api_key, - bundle_id_suffix: "development", + base_bundle_id: DEV_BUNDLE_ID, configuration: "Profile", distribute_external: false, profile_name_main: main_profile_name, @@ -189,6 +187,7 @@ end # Configure code signing for production bundle IDs configure_code_signing( + base_bundle_id: BASE_BUNDLE_ID, profile_name_main: main_profile_name, profile_name_share: share_profile_name, profile_name_widget: widget_profile_name @@ -197,6 +196,7 @@ end # Build and upload with version number build_and_upload( api_key: api_key, + base_bundle_id: BASE_BUNDLE_ID, version_number: get_version_from_pubspec, distribute_external: false, profile_name_main: main_profile_name, @@ -243,30 +243,30 @@ end desc "iOS Build Only (no TestFlight upload)" lane :gha_build_only do - # Use the same build process as production, just skip the upload - # This ensures PR builds validate the same way as production builds - + # Use the same build process as the dev TestFlight lane, just skip the upload + # This ensures PR builds validate the same way as dev TestFlight builds + api_key = get_api_key - + # Download and install provisioning profiles from App Store Connect # Certificate is imported by GHA workflow into build.keychain - sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development", force: true) + sigh(api_key: api_key, app_identifier: DEV_BUNDLE_ID, force: true) main_profile_name = lane_context[SharedValues::SIGH_NAME] - - sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.ShareExtension", force: true) + + sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.ShareExtension", force: true) share_profile_name = lane_context[SharedValues::SIGH_NAME] - - sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.Widget", force: true) + + sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.Widget", force: true) widget_profile_name = lane_context[SharedValues::SIGH_NAME] - + # Configure code signing for dev bundle IDs configure_code_signing( - bundle_id_suffix: "development", + base_bundle_id: DEV_BUNDLE_ID, profile_name_main: main_profile_name, profile_name_share: share_profile_name, profile_name_widget: widget_profile_name ) - + # Build the app (same as gha_testflight_dev but without upload) build_app( scheme: "Runner", @@ -277,9 +277,9 @@ end xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual", export_options: { provisioningProfiles: { - "#{BASE_BUNDLE_ID}.development" => main_profile_name, - "#{BASE_BUNDLE_ID}.development.ShareExtension" => share_profile_name, - "#{BASE_BUNDLE_ID}.development.Widget" => widget_profile_name + DEV_BUNDLE_ID => main_profile_name, + "#{DEV_BUNDLE_ID}.ShareExtension" => share_profile_name, + "#{DEV_BUNDLE_ID}.Widget" => widget_profile_name }, signingStyle: "manual", signingCertificate: CODE_SIGN_IDENTITY diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index 5cdca97314..51999a69d5 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:convert'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; @@ -192,17 +193,22 @@ class SyncStreamService { final remoteSyncAssets = data.cast(); await _syncStreamRepository.updateAssetsV1(remoteSyncAssets); if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { - final hasPermission = await _localFilesManager.hasManageMediaPermission(); - if (hasPermission) { - await _handleRemoteTrashed(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.checksum)); - await _applyRemoteRestoreToLocal(); - } else { - _logger.warning("sync Trashed Assets cannot proceed because MANAGE_MEDIA permission is missing"); - } + await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList()); + } + return; + case SyncEntityType.assetV2: + final remoteSyncAssets = data.cast(); + await _syncStreamRepository.updateAssetsV2(remoteSyncAssets); + if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { + await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList()); } return; case SyncEntityType.assetDeleteV1: - return _syncStreamRepository.deleteAssetsV1(data.cast()); + final remoteSyncAssets = data.cast(); + if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { + await _syncAssetDeletion(remoteSyncAssets.map((e) => e.assetId).toList()); + } + return _syncStreamRepository.deleteAssetsV1(remoteSyncAssets); case SyncEntityType.assetExifV1: return _syncStreamRepository.updateAssetsExifV1(data.cast()); case SyncEntityType.assetEditV1: @@ -215,8 +221,12 @@ class SyncStreamService { return _syncStreamRepository.deleteAssetsMetadataV1(data.cast()); case SyncEntityType.partnerAssetV1: return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner'); + case SyncEntityType.partnerAssetV2: + return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'partner'); case SyncEntityType.partnerAssetBackfillV1: return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner backfill'); + case SyncEntityType.partnerAssetBackfillV2: + return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'partner backfill'); case SyncEntityType.partnerAssetDeleteV1: return _syncStreamRepository.deleteAssetsV1(data.cast(), debugLabel: "partner"); case SyncEntityType.partnerAssetExifV1: @@ -237,10 +247,16 @@ class SyncStreamService { return _syncStreamRepository.deleteAlbumUsersV1(data.cast()); case SyncEntityType.albumAssetCreateV1: return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset create'); + case SyncEntityType.albumAssetCreateV2: + return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset create'); case SyncEntityType.albumAssetUpdateV1: return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset update'); + case SyncEntityType.albumAssetUpdateV2: + return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset update'); case SyncEntityType.albumAssetBackfillV1: return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset backfill'); + case SyncEntityType.albumAssetBackfillV2: + return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset backfill'); case SyncEntityType.albumAssetExifCreateV1: return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album asset exif create'); case SyncEntityType.albumAssetExifUpdateV1: @@ -346,6 +362,47 @@ class SyncStreamService { } } + Future handleWsAssetUploadReadyV2Batch(List batchData) async { + if (batchData.isEmpty) return; + + _logger.info('Processing batch of ${batchData.length} AssetUploadReadyV2 events'); + + final List assets = []; + final List exifs = []; + + try { + for (final data in batchData) { + if (data is! Map) { + continue; + } + + final payload = data; + final assetData = payload['asset']; + final exifData = payload['exif']; + + if (assetData == null || exifData == null) { + continue; + } + + final asset = SyncAssetV2.fromJson(assetData); + final exif = SyncAssetExifV1.fromJson(exifData); + + if (asset != null && exif != null) { + assets.add(asset); + exifs.add(exif); + } + } + + if (assets.isNotEmpty && exifs.isNotEmpty) { + await _syncStreamRepository.updateAssetsV2(assets, debugLabel: 'websocket-batch'); + await _syncStreamRepository.updateAssetsExifV1(exifs, debugLabel: 'websocket-batch'); + _logger.info('Successfully processed ${assets.length} assets in batch'); + } + } catch (error, stackTrace) { + _logger.severe("Error processing AssetUploadReadyV2 websocket batch events", error, stackTrace); + } + } + Future handleWsAssetEditReadyV1(dynamic data) async { _logger.info('Processing AssetEditReadyV1 event'); @@ -386,28 +443,67 @@ class SyncStreamService { } } - Future _handleRemoteTrashed(Iterable checksums) async { - if (checksums.isEmpty) { + Future handleWsAssetEditReadyV2(dynamic data) async { + _logger.info('Processing AssetEditReadyV2 event'); + + try { + if (data is! Map) { + throw ArgumentError("Invalid data format for AssetEditReadyV2 event"); + } + + final payload = data; + + if (payload['asset'] == null) { + throw ArgumentError("Missing 'asset' field in AssetEditReadyV2 event data"); + } + + final asset = SyncAssetV2.fromJson(payload['asset']); + if (asset == null) { + throw ArgumentError("Failed to parse 'asset' field in AssetEditReadyV2 event data"); + } + + final assetEdits = (payload['edit'] as List) + .map((e) => SyncAssetEditV1.fromJson(e)) + .whereType() + .toList(); + + await _syncStreamRepository.updateAssetsV2([asset], debugLabel: 'websocket-edit'); + await _syncStreamRepository.replaceAssetEditsV1(asset.id, assetEdits, debugLabel: 'websocket-edit'); + + _logger.info( + 'Successfully processed AssetEditReadyV2 event for asset ${asset.id} with ${assetEdits.length} edits', + ); + } catch (error, stackTrace) { + _logger.severe("Error processing AssetEditReadyV2 websocket event", error, stackTrace); + } + } + + Future _handleRemoteDeleted(Iterable remoteIds) async { + if (remoteIds.isEmpty) { return Future.value(); } else { - final localAssetsToTrash = await _localAssetRepository.getAssetsFromBackupAlbums(checksums); + final localAssetsToTrash = await _localAssetRepository.getAssetsFromBackupAlbums(remoteIds); if (localAssetsToTrash.isNotEmpty) { - final mediaUrls = await Future.wait( - localAssetsToTrash.values - .expand((e) => e) - .map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())), - ); - _logger.info("Moving to trash ${mediaUrls.join(", ")} assets"); - final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList()); - if (result) { - await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash); - } + await _trashLocalAssets(localAssetsToTrash); } else { - _logger.info("No assets found in backup-enabled albums for assets: $checksums"); + _logger.info("No assets found in backup-enabled albums for remote assets: $remoteIds"); } } } + Future _trashLocalAssets(Map> localAssetsToTrash) async { + final mediaUrls = await Future.wait( + localAssetsToTrash.values + .expand((e) => e) + .map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())), + ); + _logger.info("Moving to trash ${mediaUrls.join(", ")} assets"); + final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList()); + if (result) { + await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash); + } + } + Future _applyRemoteRestoreToLocal() async { final assetsToRestore = await _trashedLocalAssetRepository.getToRestore(); if (assetsToRestore.isNotEmpty) { @@ -417,4 +513,23 @@ class SyncStreamService { _logger.info("No remote assets found for restoration"); } } + + Future _syncAssetTrashStatus(List remoteIds) async { + if (!(await _localFilesManager.hasManageMediaPermission())) { + _logger.warning("Syncing asset trash status cannot proceed because MANAGE_MEDIA permission is missing"); + return; + } + + await _handleRemoteDeleted(remoteIds); + await _applyRemoteRestoreToLocal(); + } + + Future _syncAssetDeletion(List remoteIds) async { + if (!(await _localFilesManager.hasManageMediaPermission())) { + _logger.warning("Syncing asset deletion cannot proceed because MANAGE_MEDIA permission is missing"); + return; + } + + await _handleRemoteDeleted(remoteIds); + } } diff --git a/mobile/lib/domain/utils/background_sync.dart b/mobile/lib/domain/utils/background_sync.dart index 7c9b6ae061..030e77cd54 100644 --- a/mobile/lib/domain/utils/background_sync.dart +++ b/mobile/lib/domain/utils/background_sync.dart @@ -186,7 +186,7 @@ class BackgroundSyncManager { }); } - Future syncWebsocketBatch(List batchData) { + Future syncWebsocketBatchV1(List batchData) { if (_syncWebsocketTask != null) { return _syncWebsocketTask!.future; } @@ -196,7 +196,17 @@ class BackgroundSyncManager { }); } - Future syncWebsocketEdit(dynamic data) { + Future syncWebsocketBatchV2(List batchData) { + if (_syncWebsocketTask != null) { + return _syncWebsocketTask!.future; + } + _syncWebsocketTask = _handleWsAssetUploadReadyV2Batch(batchData); + return _syncWebsocketTask!.whenComplete(() { + _syncWebsocketTask = null; + }); + } + + Future syncWebsocketEditV1(dynamic data) { if (_syncWebsocketTask != null) { return _syncWebsocketTask!.future; } @@ -206,6 +216,16 @@ class BackgroundSyncManager { }); } + Future syncWebsocketEditV2(dynamic data) { + if (_syncWebsocketTask != null) { + return _syncWebsocketTask!.future; + } + _syncWebsocketTask = _handleWsAssetEditReadyV2(data); + return _syncWebsocketTask!.whenComplete(() { + _syncWebsocketTask = null; + }); + } + Future syncLinkedAlbum() { if (_linkedAlbumSyncTask != null) { return _linkedAlbumSyncTask!.future; @@ -242,7 +262,17 @@ Cancelable _handleWsAssetUploadReadyV1Batch(List batchData) => ru debugLabel: 'websocket-batch', ); +Cancelable _handleWsAssetUploadReadyV2Batch(List batchData) => runInIsolateGentle( + computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetUploadReadyV2Batch(batchData), + debugLabel: 'websocket-batch', +); + Cancelable _handleWsAssetEditReadyV1(dynamic data) => runInIsolateGentle( computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV1(data), debugLabel: 'websocket-edit', ); + +Cancelable _handleWsAssetEditReadyV2(dynamic data) => runInIsolateGentle( + computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV2(data), + debugLabel: 'websocket-edit', +); diff --git a/mobile/lib/extensions/asset_extensions.dart b/mobile/lib/extensions/asset_extensions.dart index 6bcc11f18d..3a994f9cb8 100644 --- a/mobile/lib/extensions/asset_extensions.dart +++ b/mobile/lib/extensions/asset_extensions.dart @@ -1,6 +1,5 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/exif.model.dart'; -import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; import 'package:openapi/api.dart' as api; @@ -14,7 +13,7 @@ extension DTOToAsset on api.AssetResponseDto { updatedAt: updatedAt, ownerId: ownerId, visibility: visibility.toAssetVisibility(), - durationMs: duration?.toDuration()?.inMilliseconds ?? 0, + durationMs: duration, height: height?.toInt(), width: width?.toInt(), isFavorite: isFavorite, @@ -36,7 +35,7 @@ extension DTOToAsset on api.AssetResponseDto { updatedAt: updatedAt, ownerId: ownerId, visibility: visibility.toAssetVisibility(), - durationMs: duration?.toDuration()?.inMilliseconds ?? 0, + durationMs: duration, height: height?.toInt(), width: width?.toInt(), isFavorite: isFavorite, diff --git a/mobile/lib/infrastructure/repositories/local_asset.repository.dart b/mobile/lib/infrastructure/repositories/local_asset.repository.dart index 6f6ef20aeb..c34d2c4697 100644 --- a/mobile/lib/infrastructure/repositories/local_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_asset.repository.dart @@ -109,31 +109,40 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository { return query.map((localAlbum) => localAlbum.toDto()).get(); } - Future>> getAssetsFromBackupAlbums(Iterable checksums) async { - if (checksums.isEmpty) { + Future>> getAssetsFromBackupAlbums(Iterable remoteIds) async { + if (remoteIds.isEmpty) { return {}; } final result = >{}; - for (final slice in checksums.toSet().slices(kDriftMaxChunk)) { + for (final slice in remoteIds.toSet().slices(kDriftMaxChunk)) { final rows = await (_db.select(_db.localAlbumAssetEntity).join([ - innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id)), + innerJoin( + _db.localAlbumEntity, + _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id), + useColumns: false, + ), innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)), + innerJoin( + _db.remoteAssetEntity, + _db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum), + useColumns: false, + ), ])..where( _db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) & - _db.localAssetEntity.checksum.isIn(slice), + _db.remoteAssetEntity.id.isIn(slice), )) .get(); for (final row in rows) { final albumId = row.readTable(_db.localAlbumAssetEntity).albumId; - final assetData = row.readTable(_db.localAssetEntity); - final asset = assetData.toDto(); + final asset = row.readTable(_db.localAssetEntity).toDto(); (result[albumId] ??= []).add(asset); } } + return result; } diff --git a/mobile/lib/infrastructure/repositories/sync_api.repository.dart b/mobile/lib/infrastructure/repositories/sync_api.repository.dart index f1e25834cf..fc87c90f90 100644 --- a/mobile/lib/infrastructure/repositories/sync_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_api.repository.dart @@ -46,19 +46,25 @@ class SyncApiRepository { types: [ SyncRequestType.authUsersV1, SyncRequestType.usersV1, - SyncRequestType.assetsV1, + serverVersion >= const SemVer(major: 3, minor: 0, patch: 0) + ? SyncRequestType.assetsV2 + : SyncRequestType.assetsV1, SyncRequestType.assetExifsV1, if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetEditsV1, SyncRequestType.assetMetadataV1, SyncRequestType.partnersV1, - SyncRequestType.partnerAssetsV1, + serverVersion >= const SemVer(major: 3, minor: 0, patch: 0) + ? SyncRequestType.partnerAssetsV2 + : SyncRequestType.partnerAssetsV1, SyncRequestType.partnerAssetExifsV1, if (serverVersion < const SemVer(major: 3, minor: 0, patch: 0)) SyncRequestType.albumsV1 else SyncRequestType.albumsV2, SyncRequestType.albumUsersV1, - SyncRequestType.albumAssetsV1, + serverVersion >= const SemVer(major: 3, minor: 0, patch: 0) + ? SyncRequestType.albumAssetsV2 + : SyncRequestType.albumAssetsV1, SyncRequestType.albumAssetExifsV1, SyncRequestType.albumToAssetsV1, SyncRequestType.memoriesV1, @@ -67,8 +73,9 @@ class SyncApiRepository { SyncRequestType.partnerStacksV1, SyncRequestType.userMetadataV1, SyncRequestType.peopleV1, - if (serverVersion < const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV1, - if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV2, + serverVersion >= const SemVer(major: 2, minor: 6, patch: 0) + ? SyncRequestType.assetFacesV2 + : SyncRequestType.assetFacesV1, if (serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)) SyncRequestType.assetOcrV1, ], reset: shouldReset, @@ -154,6 +161,7 @@ const _kResponseMap = { SyncEntityType.partnerV1: SyncPartnerV1.fromJson, SyncEntityType.partnerDeleteV1: SyncPartnerDeleteV1.fromJson, SyncEntityType.assetV1: SyncAssetV1.fromJson, + SyncEntityType.assetV2: SyncAssetV2.fromJson, SyncEntityType.assetDeleteV1: SyncAssetDeleteV1.fromJson, SyncEntityType.assetExifV1: SyncAssetExifV1.fromJson, SyncEntityType.assetEditV1: SyncAssetEditV1.fromJson, @@ -161,7 +169,9 @@ const _kResponseMap = { SyncEntityType.assetMetadataV1: SyncAssetMetadataV1.fromJson, SyncEntityType.assetMetadataDeleteV1: SyncAssetMetadataDeleteV1.fromJson, SyncEntityType.partnerAssetV1: SyncAssetV1.fromJson, + SyncEntityType.partnerAssetV2: SyncAssetV2.fromJson, SyncEntityType.partnerAssetBackfillV1: SyncAssetV1.fromJson, + SyncEntityType.partnerAssetBackfillV2: SyncAssetV2.fromJson, SyncEntityType.partnerAssetDeleteV1: SyncAssetDeleteV1.fromJson, SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson, SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson, @@ -172,8 +182,11 @@ const _kResponseMap = { SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson, SyncEntityType.albumUserDeleteV1: SyncAlbumUserDeleteV1.fromJson, SyncEntityType.albumAssetCreateV1: SyncAssetV1.fromJson, + SyncEntityType.albumAssetCreateV2: SyncAssetV2.fromJson, SyncEntityType.albumAssetUpdateV1: SyncAssetV1.fromJson, + SyncEntityType.albumAssetUpdateV2: SyncAssetV2.fromJson, SyncEntityType.albumAssetBackfillV1: SyncAssetV1.fromJson, + SyncEntityType.albumAssetBackfillV2: SyncAssetV2.fromJson, SyncEntityType.albumAssetExifCreateV1: SyncAssetExifV1.fromJson, SyncEntityType.albumAssetExifUpdateV1: SyncAssetExifV1.fromJson, SyncEntityType.albumAssetExifBackfillV1: SyncAssetExifV1.fromJson, diff --git a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart index 34e447bd6c..1f30c7fce1 100644 --- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart @@ -222,6 +222,44 @@ class SyncStreamRepository extends DriftDatabaseRepository { } } + Future updateAssetsV2(Iterable data, {String debugLabel = 'user'}) async { + try { + await _db.batch((batch) { + for (final asset in data) { + final companion = RemoteAssetEntityCompanion( + name: Value(asset.originalFileName), + type: Value(asset.type.toAssetType()), + createdAt: Value.absentIfNull(asset.fileCreatedAt), + updatedAt: Value.absentIfNull(asset.fileModifiedAt), + durationMs: Value(asset.duration), + checksum: Value(asset.checksum), + isFavorite: Value(asset.isFavorite), + ownerId: Value(asset.ownerId), + localDateTime: Value(asset.localDateTime), + thumbHash: Value(asset.thumbhash), + deletedAt: Value(asset.deletedAt), + visibility: Value(asset.visibility.toAssetVisibility()), + livePhotoVideoId: Value(asset.livePhotoVideoId), + stackId: Value(asset.stackId), + libraryId: Value(asset.libraryId), + width: Value(asset.width), + height: Value(asset.height), + isEdited: Value(asset.isEdited), + ); + + batch.insert( + _db.remoteAssetEntity, + companion.copyWith(id: Value(asset.id)), + onConflict: DoUpdate((_) => companion), + ); + } + }); + } catch (error, stack) { + _logger.severe('Error: updateAssetsV2 - $debugLabel', error, stack); + rethrow; + } + } + Future updateAssetsExifV1(Iterable data, {String debugLabel = 'user'}) async { try { await _db.batch((batch) { diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart index 21755f3a35..cf8be58219 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/scroll_extensions.dart'; @@ -365,7 +366,8 @@ class _AssetPageState extends ConsumerState { } BaseAsset displayAsset = asset; - final stackChildren = ref.watch(stackChildrenNotifier(asset)).valueOrNull; + final showAssetStack = ref.watch(timelineServiceProvider.select((s) => s.origin != TimelineOrigin.trash)); + final stackChildren = showAssetStack ? ref.watch(stackChildrenNotifier(asset)).valueOrNull : null; if (stackChildren != null && stackChildren.isNotEmpty) { displayAsset = stackChildren.elementAt(stackIndex); } diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_stack.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_stack.widget.dart index 213dc92ef3..f5d75a6a86 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_stack.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_stack.widget.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; class AssetStackRow extends ConsumerWidget { final List stack; @@ -15,6 +17,11 @@ class AssetStackRow extends ConsumerWidget { return const SizedBox.shrink(); } + final hideAssetStack = ref.read(timelineServiceProvider).origin == TimelineOrigin.trash; + if (hideAssetStack) { + return const SizedBox.shrink(); + } + final showingControls = ref.watch(assetViewerProvider.select((s) => s.showingControls)); double opacity = ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0); diff --git a/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart index d7ef604718..3770c5d32d 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart @@ -2,17 +2,21 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/map/map.state.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; class MapBottomSheet extends StatelessWidget { - const MapBottomSheet({super.key}); + final Key? sheetKey; + + const MapBottomSheet({super.key, this.sheetKey}); @override Widget build(BuildContext context) { return BaseBottomSheet( + key: sheetKey, initialChildSize: 0.25, maxChildSize: 0.75, shouldCloseOnMinExtent: false, @@ -49,7 +53,7 @@ class _ScopedMapTimeline extends StatelessWidget { return timelineService; }), ], - child: const Timeline(appBar: null, bottomSheet: null, withScrubber: false), + child: const Timeline(appBar: null, bottomSheet: GeneralBottomSheet(minChildSize: 0.23), withScrubber: false), ); } } diff --git a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart index 5746414361..406ca30820 100644 --- a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart +++ b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart @@ -21,6 +21,7 @@ class ThumbnailTile extends ConsumerStatefulWidget { this.showStorageIndicator = false, this.lockSelection = false, this.heroOffset, + this.showStackIndicator = false, super.key, }); @@ -30,6 +31,7 @@ class ThumbnailTile extends ConsumerStatefulWidget { final bool showStorageIndicator; final bool lockSelection; final int? heroOffset; + final bool showStackIndicator; @override ConsumerState createState() => _ThumbnailTileState(); @@ -139,7 +141,14 @@ class _ThumbnailTileState extends ConsumerState { duration: Durations.short4, child: Align( alignment: Alignment.topRight, - child: _AssetTypeIcons(asset: asset), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + _AssetTypeIcons(asset: asset), + if (widget.showStackIndicator) _StackIndicator(asset: asset), + ], + ), ), ), if (storageIndicator && asset != null) @@ -286,8 +295,8 @@ class _AssetTypeIcons extends StatelessWidget { @override Widget build(BuildContext context) { - final hasStack = asset is RemoteAsset && (asset as RemoteAsset).stackId != null; - final isLivePhoto = asset is RemoteAsset && asset.livePhotoVideoId != null; + final remoteAsset = asset is RemoteAsset ? asset as RemoteAsset : null; + final isLivePhoto = remoteAsset?.livePhotoVideoId != null; return Column( mainAxisSize: MainAxisSize.min, @@ -295,11 +304,6 @@ class _AssetTypeIcons extends StatelessWidget { children: [ if (asset.isVideo) Padding(padding: const EdgeInsets.only(right: 10.0, top: 6.0), child: _VideoIndicator(asset.duration)), - if (hasStack) - const Padding( - padding: EdgeInsets.only(right: 10.0, top: 6.0), - child: _TileOverlayIcon(Icons.burst_mode_rounded), - ), if (isLivePhoto) const Padding( padding: EdgeInsets.only(right: 10.0, top: 6.0), @@ -312,6 +316,24 @@ class _AssetTypeIcons extends StatelessWidget { } } +class _StackIndicator extends StatelessWidget { + final BaseAsset asset; + + const _StackIndicator({required this.asset}); + + @override + Widget build(BuildContext context) { + if (asset is! RemoteAsset || (asset as RemoteAsset).stackId == null) { + return const SizedBox.shrink(); + } + + return const Padding( + padding: EdgeInsets.only(right: 10.0, top: 6.0), + child: _TileOverlayIcon(Icons.burst_mode_rounded), + ); + } +} + class _UploadProgressOverlay extends StatelessWidget { final double progress; diff --git a/mobile/lib/presentation/widgets/map/map.widget.dart b/mobile/lib/presentation/widgets/map/map.widget.dart index 3f406dd551..f6c4f7d468 100644 --- a/mobile/lib/presentation/widgets/map/map.widget.dart +++ b/mobile/lib/presentation/widgets/map/map.widget.dart @@ -11,6 +11,7 @@ import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/map/map.state.dart'; import 'package:immich_mobile/presentation/widgets/map/map_utils.dart'; @@ -53,6 +54,7 @@ class _DriftMapState extends ConsumerState { final _reloadMutex = AsyncMutex(); final _debouncer = Debouncer(interval: const Duration(milliseconds: 500), maxWaitTime: const Duration(seconds: 2)); final ValueNotifier bottomSheetOffset = ValueNotifier(0.25); + final GlobalKey _bottomSheetKey = GlobalKey(); StreamSubscription? _eventSubscription; @override @@ -184,7 +186,7 @@ class _DriftMapState extends ConsumerState { return Stack( children: [ _Map(initialLocation: widget.initialLocation, onMapCreated: onMapCreated, onMapReady: onMapReady), - _DynamicBottomSheet(bottomSheetOffset: bottomSheetOffset), + _DynamicBottomSheet(bottomSheetOffset: bottomSheetOffset, sheetKey: _bottomSheetKey), _DynamicMyLocationButton(onZoomToLocation: onZoomToLocation, bottomSheetOffset: bottomSheetOffset), ], ); @@ -224,8 +226,9 @@ class _Map extends StatelessWidget { class _DynamicBottomSheet extends StatefulWidget { final ValueNotifier bottomSheetOffset; + final GlobalKey sheetKey; - const _DynamicBottomSheet({required this.bottomSheetOffset}); + const _DynamicBottomSheet({required this.bottomSheetOffset, required this.sheetKey}); @override State<_DynamicBottomSheet> createState() => _DynamicBottomSheetState(); @@ -236,10 +239,13 @@ class _DynamicBottomSheetState extends State<_DynamicBottomSheet> { Widget build(BuildContext context) { return NotificationListener( onNotification: (notification) { - widget.bottomSheetOffset.value = notification.extent; - return true; + final sheet = notification.context.findAncestorWidgetOfExactType(); + if (sheet?.key == widget.sheetKey) { + widget.bottomSheetOffset.value = notification.extent; + } + return false; }, - child: const MapBottomSheet(), + child: MapBottomSheet(sheetKey: widget.sheetKey), ); } } diff --git a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart index aa2112b8dd..c62a4946c7 100644 --- a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart +++ b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart @@ -244,6 +244,7 @@ class _AssetTileWidget extends ConsumerWidget { final lockSelection = _getLockSelectionStatus(ref); final showStorageIndicator = ref.watch(timelineArgsProvider.select((args) => args.showStorageIndicator)); final isReadonlyModeEnabled = ref.watch(readonlyModeProvider); + final showStackIndicator = ref.read(timelineServiceProvider).origin != TimelineOrigin.trash; return RepaintBoundary( child: GestureDetector( @@ -253,6 +254,7 @@ class _AssetTileWidget extends ConsumerWidget { asset, lockSelection: lockSelection, showStorageIndicator: showStorageIndicator, + showStackIndicator: showStackIndicator, heroOffset: heroOffset, ), ), diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart index 8d494a8452..578bd37a23 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -469,6 +469,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { ref.read(timelineStateProvider.notifier).setScrolling(true); }, child: Stack( + clipBehavior: Clip.none, children: [ timeline, if (isBottomWidgetVisible) diff --git a/mobile/lib/providers/websocket.provider.dart b/mobile/lib/providers/websocket.provider.dart index c79f40a25d..60afcec2d2 100644 --- a/mobile/lib/providers/websocket.provider.dart +++ b/mobile/lib/providers/websocket.provider.dart @@ -94,8 +94,10 @@ class WebsocketNotifier extends StateNotifier { state = const WebsocketState(isConnected: false, socket: null); }); - socket.on('AssetUploadReadyV1', _handleSyncAssetUploadReady); - socket.on('AssetEditReadyV1', _handleSyncAssetEditReady); + socket.on('AssetUploadReadyV1', _handleSyncAssetUploadReadyV1); + socket.on('AssetUploadReadyV2', _handleSyncAssetUploadReadyV2); + socket.on('AssetEditReadyV1', _handleSyncAssetEditReadyV1); + socket.on('AssetEditReadyV2', _handleSyncAssetEditReadyV2); socket.on('on_config_update', _handleOnConfigUpdate); socket.on('on_new_release', _handleReleaseUpdates); } catch (e) { @@ -163,16 +165,25 @@ class WebsocketNotifier extends StateNotifier { _ref.read(serverInfoProvider.notifier).handleReleaseInfo(serverVersion, releaseVersion); } - void _handleSyncAssetUploadReady(dynamic data) { + void _handleSyncAssetUploadReadyV1(dynamic data) { _batchedAssetUploadReady.add(data); - _batchDebouncer.run(_processBatchedAssetUploadReady); + _batchDebouncer.run(_processBatchedAssetUploadReadyV1); } - void _handleSyncAssetEditReady(dynamic data) { - unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEdit(data)); + void _handleSyncAssetUploadReadyV2(dynamic data) { + _batchedAssetUploadReady.add(data); + _batchDebouncer.run(_processBatchedAssetUploadReadyV2); } - void _processBatchedAssetUploadReady() { + void _handleSyncAssetEditReadyV1(dynamic data) { + unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV1(data)); + } + + void _handleSyncAssetEditReadyV2(dynamic data) { + unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV2(data)); + } + + void _processBatchedAssetUploadReadyV1() { if (_batchedAssetUploadReady.isEmpty) { return; } @@ -180,7 +191,7 @@ class WebsocketNotifier extends StateNotifier { final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false); try { unawaited( - _ref.read(backgroundSyncProvider).syncWebsocketBatch(_batchedAssetUploadReady.toList()).then((_) { + _ref.read(backgroundSyncProvider).syncWebsocketBatchV1(_batchedAssetUploadReady.toList()).then((_) { if (isSyncAlbumEnabled) { _ref.read(backgroundSyncProvider).syncLinkedAlbum(); } @@ -192,6 +203,27 @@ class WebsocketNotifier extends StateNotifier { _batchedAssetUploadReady.clear(); } + + void _processBatchedAssetUploadReadyV2() { + if (_batchedAssetUploadReady.isEmpty) { + return; + } + + final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false); + try { + unawaited( + _ref.read(backgroundSyncProvider).syncWebsocketBatchV2(_batchedAssetUploadReady.toList()).then((_) { + if (isSyncAlbumEnabled) { + _ref.read(backgroundSyncProvider).syncLinkedAlbum(); + } + }), + ); + } catch (error) { + _log.severe("Error processing batched AssetUploadReadyV2 events: $error"); + } + + _batchedAssetUploadReady.clear(); + } } final websocketProvider = StateNotifierProvider((ref) { diff --git a/mobile/lib/utils/action_button.utils.dart b/mobile/lib/utils/action_button.utils.dart index 3edc50c847..4f7ad83093 100644 --- a/mobile/lib/utils/action_button.utils.dart +++ b/mobile/lib/utils/action_button.utils.dart @@ -148,6 +148,7 @@ enum ActionButtonType { context.selectedCount == 1, ActionButtonType.unstack => context.isOwner && // + context.timelineOrigin != TimelineOrigin.trash && !context.isInLockedView && // context.isStacked, ActionButtonType.openInBrowser => context.asset.hasRemote && !context.isInLockedView, diff --git a/mobile/openapi/lib/api/assets_api.dart b/mobile/openapi/lib/api/assets_api.dart index 5046376168..691c57cd3e 100644 --- a/mobile/openapi/lib/api/assets_api.dart +++ b/mobile/openapi/lib/api/assets_api.dart @@ -1234,8 +1234,8 @@ class AssetsApi { /// * [String] xImmichChecksum: /// sha1 checksum that can be used for duplicate detection before the file is uploaded /// - /// * [String] duration: - /// Duration (for videos) + /// * [int] duration: + /// Duration in milliseconds (for videos) /// /// * [String] filename: /// Filename @@ -1253,7 +1253,7 @@ class AssetsApi { /// Sidecar file data /// /// * [AssetVisibility] visibility: - Future uploadAssetWithHttpInfo(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async { + Future uploadAssetWithHttpInfo(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, int? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets'; @@ -1358,8 +1358,8 @@ class AssetsApi { /// * [String] xImmichChecksum: /// sha1 checksum that can be used for duplicate detection before the file is uploaded /// - /// * [String] duration: - /// Duration (for videos) + /// * [int] duration: + /// Duration in milliseconds (for videos) /// /// * [String] filename: /// Filename @@ -1377,7 +1377,7 @@ class AssetsApi { /// Sidecar file data /// /// * [AssetVisibility] visibility: - Future uploadAsset(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async { + Future uploadAsset(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, int? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async { final response = await uploadAssetWithHttpInfo(assetData, fileCreatedAt, fileModifiedAt, key: key, slug: slug, xImmichChecksum: xImmichChecksum, duration: duration, filename: filename, isFavorite: isFavorite, livePhotoVideoId: livePhotoVideoId, metadata: metadata, sidecarData: sidecarData, visibility: visibility, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); diff --git a/mobile/openapi/lib/api/people_api.dart b/mobile/openapi/lib/api/people_api.dart index c8c1821423..99821f31aa 100644 --- a/mobile/openapi/lib/api/people_api.dart +++ b/mobile/openapi/lib/api/people_api.dart @@ -183,15 +183,15 @@ class PeopleApi { /// * [String] closestPersonId: /// Closest person ID for similarity search /// - /// * [num] page: + /// * [int] page: /// Page number for pagination /// - /// * [num] size: + /// * [int] size: /// Number of items per page /// /// * [bool] withHidden: /// Include hidden people - Future getAllPeopleWithHttpInfo({ String? closestAssetId, String? closestPersonId, num? page, num? size, bool? withHidden, }) async { + Future getAllPeopleWithHttpInfo({ String? closestAssetId, String? closestPersonId, int? page, int? size, bool? withHidden, }) async { // ignore: prefer_const_declarations final apiPath = r'/people'; @@ -244,15 +244,15 @@ class PeopleApi { /// * [String] closestPersonId: /// Closest person ID for similarity search /// - /// * [num] page: + /// * [int] page: /// Page number for pagination /// - /// * [num] size: + /// * [int] size: /// Number of items per page /// /// * [bool] withHidden: /// Include hidden people - Future getAllPeople({ String? closestAssetId, String? closestPersonId, num? page, num? size, bool? withHidden, }) async { + Future getAllPeople({ String? closestAssetId, String? closestPersonId, int? page, int? size, bool? withHidden, }) async { final response = await getAllPeopleWithHttpInfo( closestAssetId: closestAssetId, closestPersonId: closestPersonId, page: page, size: size, withHidden: withHidden, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); diff --git a/mobile/openapi/lib/api/search_api.dart b/mobile/openapi/lib/api/search_api.dart index 730627d4a1..6f8a4df902 100644 --- a/mobile/openapi/lib/api/search_api.dart +++ b/mobile/openapi/lib/api/search_api.dart @@ -404,10 +404,10 @@ class SearchApi { /// * [List] personIds: /// Filter by person IDs /// - /// * [num] rating: + /// * [int] rating: /// Filter by rating [1-5], or null for unrated /// - /// * [num] size: + /// * [int] size: /// Number of results to return /// /// * [String] state: @@ -443,7 +443,7 @@ class SearchApi { /// /// * [bool] withExif: /// Include EXIF data in response - Future searchLargeAssetsWithHttpInfo({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, num? rating, num? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { + Future searchLargeAssetsWithHttpInfo({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, int? rating, int? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/large-assets'; @@ -619,10 +619,10 @@ class SearchApi { /// * [List] personIds: /// Filter by person IDs /// - /// * [num] rating: + /// * [int] rating: /// Filter by rating [1-5], or null for unrated /// - /// * [num] size: + /// * [int] size: /// Number of results to return /// /// * [String] state: @@ -658,7 +658,7 @@ class SearchApi { /// /// * [bool] withExif: /// Include EXIF data in response - Future?> searchLargeAssets({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, num? rating, num? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { + Future?> searchLargeAssets({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, int? rating, int? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { final response = await searchLargeAssetsWithHttpInfo( albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, ocr: ocr, personIds: personIds, rating: rating, size: size, state: state, tagIds: tagIds, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, visibility: visibility, withDeleted: withDeleted, withExif: withExif, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); diff --git a/mobile/openapi/lib/model/asset_bulk_update_dto.dart b/mobile/openapi/lib/model/asset_bulk_update_dto.dart index f97300b19f..f85026f054 100644 --- a/mobile/openapi/lib/model/asset_bulk_update_dto.dart +++ b/mobile/openapi/lib/model/asset_bulk_update_dto.dart @@ -37,12 +37,15 @@ class AssetBulkUpdateDto { /// Relative time offset in seconds /// + /// Minimum value: -9007199254740991 + /// Maximum value: 9007199254740991 + /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - num? dateTimeRelative; + int? dateTimeRelative; /// Asset description /// @@ -213,7 +216,7 @@ class AssetBulkUpdateDto { return AssetBulkUpdateDto( dateTimeOriginal: mapValueOfType(json, r'dateTimeOriginal'), - dateTimeRelative: num.parse('${json[r'dateTimeRelative']}'), + dateTimeRelative: mapValueOfType(json, r'dateTimeRelative'), description: mapValueOfType(json, r'description'), duplicateId: mapValueOfType(json, r'duplicateId'), ids: json[r'ids'] is Iterable diff --git a/mobile/openapi/lib/model/asset_edit_action_item_dto_parameters.dart b/mobile/openapi/lib/model/asset_edit_action_item_dto_parameters.dart index 2086f72929..6f2811e89d 100644 --- a/mobile/openapi/lib/model/asset_edit_action_item_dto_parameters.dart +++ b/mobile/openapi/lib/model/asset_edit_action_item_dto_parameters.dart @@ -24,22 +24,26 @@ class AssetEditActionItemDtoParameters { /// Height of the crop /// /// Minimum value: 1 - num height; + /// Maximum value: 9007199254740991 + int height; /// Width of the crop /// /// Minimum value: 1 - num width; + /// Maximum value: 9007199254740991 + int width; /// Top-Left X coordinate of crop /// /// Minimum value: 0 - num x; + /// Maximum value: 9007199254740991 + int x; /// Top-Left Y coordinate of crop /// /// Minimum value: 0 - num y; + /// Maximum value: 9007199254740991 + int y; /// Rotation angle in degrees num angle; @@ -88,10 +92,10 @@ class AssetEditActionItemDtoParameters { final json = value.cast(); return AssetEditActionItemDtoParameters( - height: num.parse('${json[r'height']}'), - width: num.parse('${json[r'width']}'), - x: num.parse('${json[r'x']}'), - y: num.parse('${json[r'y']}'), + height: mapValueOfType(json, r'height')!, + width: mapValueOfType(json, r'width')!, + x: mapValueOfType(json, r'x')!, + y: mapValueOfType(json, r'y')!, angle: num.parse('${json[r'angle']}'), axis: MirrorAxis.fromJson(json[r'axis'])!, ); diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index 324d12fcbf..7284c44580 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -57,8 +57,11 @@ class AssetResponseDto { /// Duplicate group ID String? duplicateId; - /// Video/gif duration in hh:mm:ss.SSS format (null for static images) - String? duration; + /// Video/gif duration in milliseconds (null for static images) + /// + /// Minimum value: 0 + /// Maximum value: 2147483647 + int? duration; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -80,7 +83,8 @@ class AssetResponseDto { /// Asset height /// /// Minimum value: 0 - num? height; + /// Maximum value: 9007199254740991 + int? height; /// Asset ID String id; @@ -165,7 +169,8 @@ class AssetResponseDto { /// Asset width /// /// Minimum value: 0 - num? width; + /// Maximum value: 9007199254740991 + int? width; @override bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto && @@ -341,14 +346,12 @@ class AssetResponseDto { checksum: mapValueOfType(json, r'checksum')!, createdAt: mapDateTime(json, r'createdAt', r'')!, duplicateId: mapValueOfType(json, r'duplicateId'), - duration: mapValueOfType(json, r'duration'), + duration: mapValueOfType(json, r'duration'), exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']), fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!, fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!, hasMetadata: mapValueOfType(json, r'hasMetadata')!, - height: json[r'height'] == null - ? null - : num.parse('${json[r'height']}'), + height: mapValueOfType(json, r'height'), id: mapValueOfType(json, r'id')!, isArchived: mapValueOfType(json, r'isArchived')!, isEdited: mapValueOfType(json, r'isEdited')!, @@ -372,9 +375,7 @@ class AssetResponseDto { unassignedFaces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'unassignedFaces']), updatedAt: mapDateTime(json, r'updatedAt', r'')!, visibility: AssetVisibility.fromJson(json[r'visibility'])!, - width: json[r'width'] == null - ? null - : num.parse('${json[r'width']}'), + width: mapValueOfType(json, r'width'), ); } return null; diff --git a/mobile/openapi/lib/model/crop_parameters.dart b/mobile/openapi/lib/model/crop_parameters.dart index 8c5b884596..d19c23562b 100644 --- a/mobile/openapi/lib/model/crop_parameters.dart +++ b/mobile/openapi/lib/model/crop_parameters.dart @@ -22,22 +22,26 @@ class CropParameters { /// Height of the crop /// /// Minimum value: 1 - num height; + /// Maximum value: 9007199254740991 + int height; /// Width of the crop /// /// Minimum value: 1 - num width; + /// Maximum value: 9007199254740991 + int width; /// Top-Left X coordinate of crop /// /// Minimum value: 0 - num x; + /// Maximum value: 9007199254740991 + int x; /// Top-Left Y coordinate of crop /// /// Minimum value: 0 - num y; + /// Maximum value: 9007199254740991 + int y; @override bool operator ==(Object other) => identical(this, other) || other is CropParameters && @@ -75,10 +79,10 @@ class CropParameters { final json = value.cast(); return CropParameters( - height: num.parse('${json[r'height']}'), - width: num.parse('${json[r'width']}'), - x: num.parse('${json[r'x']}'), - y: num.parse('${json[r'y']}'), + height: mapValueOfType(json, r'height')!, + width: mapValueOfType(json, r'width')!, + x: mapValueOfType(json, r'x')!, + y: mapValueOfType(json, r'y')!, ); } return null; diff --git a/mobile/openapi/lib/model/database_backup_config.dart b/mobile/openapi/lib/model/database_backup_config.dart index 419968c3f3..4beb32849e 100644 --- a/mobile/openapi/lib/model/database_backup_config.dart +++ b/mobile/openapi/lib/model/database_backup_config.dart @@ -27,7 +27,8 @@ class DatabaseBackupConfig { /// Keep last amount /// /// Minimum value: 1 - num keepLastAmount; + /// Maximum value: 9007199254740991 + int keepLastAmount; @override bool operator ==(Object other) => identical(this, other) || other is DatabaseBackupConfig && @@ -64,7 +65,7 @@ class DatabaseBackupConfig { return DatabaseBackupConfig( cronExpression: mapValueOfType(json, r'cronExpression')!, enabled: mapValueOfType(json, r'enabled')!, - keepLastAmount: num.parse('${json[r'keepLastAmount']}'), + keepLastAmount: mapValueOfType(json, r'keepLastAmount')!, ); } return null; diff --git a/mobile/openapi/lib/model/database_backup_dto.dart b/mobile/openapi/lib/model/database_backup_dto.dart index abfa637157..5a2590da40 100644 --- a/mobile/openapi/lib/model/database_backup_dto.dart +++ b/mobile/openapi/lib/model/database_backup_dto.dart @@ -22,7 +22,10 @@ class DatabaseBackupDto { String filename; /// Backup file size - num filesize; + /// + /// Minimum value: -9007199254740991 + /// Maximum value: 9007199254740991 + int filesize; /// Backup timezone String timezone; @@ -61,7 +64,7 @@ class DatabaseBackupDto { return DatabaseBackupDto( filename: mapValueOfType(json, r'filename')!, - filesize: num.parse('${json[r'filesize']}'), + filesize: mapValueOfType(json, r'filesize')!, timezone: mapValueOfType(json, r'timezone')!, ); } diff --git a/mobile/openapi/lib/model/exif_response_dto.dart b/mobile/openapi/lib/model/exif_response_dto.dart index 64a5a73bed..ed5ffd2958 100644 --- a/mobile/openapi/lib/model/exif_response_dto.dart +++ b/mobile/openapi/lib/model/exif_response_dto.dart @@ -52,12 +52,14 @@ class ExifResponseDto { /// Image height in pixels /// /// Minimum value: 0 - num? exifImageHeight; + /// Maximum value: 9007199254740991 + int? exifImageHeight; /// Image width in pixels /// /// Minimum value: 0 - num? exifImageWidth; + /// Maximum value: 9007199254740991 + int? exifImageWidth; /// Exposure time String? exposureTime; @@ -75,7 +77,10 @@ class ExifResponseDto { num? focalLength; /// ISO sensitivity - num? iso; + /// + /// Minimum value: -9007199254740991 + /// Maximum value: 9007199254740991 + int? iso; /// GPS latitude num? latitude; @@ -102,7 +107,10 @@ class ExifResponseDto { String? projectionType; /// Rating - num? rating; + /// + /// Minimum value: -9007199254740991 + /// Maximum value: 9007199254740991 + int? rating; /// State/province name String? state; @@ -292,12 +300,8 @@ class ExifResponseDto { country: mapValueOfType(json, r'country'), dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', r''), description: mapValueOfType(json, r'description'), - exifImageHeight: json[r'exifImageHeight'] == null - ? null - : num.parse('${json[r'exifImageHeight']}'), - exifImageWidth: json[r'exifImageWidth'] == null - ? null - : num.parse('${json[r'exifImageWidth']}'), + exifImageHeight: mapValueOfType(json, r'exifImageHeight'), + exifImageWidth: mapValueOfType(json, r'exifImageWidth'), exposureTime: mapValueOfType(json, r'exposureTime'), fNumber: json[r'fNumber'] == null ? null @@ -306,9 +310,7 @@ class ExifResponseDto { focalLength: json[r'focalLength'] == null ? null : num.parse('${json[r'focalLength']}'), - iso: json[r'iso'] == null - ? null - : num.parse('${json[r'iso']}'), + iso: mapValueOfType(json, r'iso'), latitude: json[r'latitude'] == null ? null : num.parse('${json[r'latitude']}'), @@ -321,9 +323,7 @@ class ExifResponseDto { modifyDate: mapDateTime(json, r'modifyDate', r''), orientation: mapValueOfType(json, r'orientation'), projectionType: mapValueOfType(json, r'projectionType'), - rating: json[r'rating'] == null - ? null - : num.parse('${json[r'rating']}'), + rating: mapValueOfType(json, r'rating'), state: mapValueOfType(json, r'state'), timeZone: mapValueOfType(json, r'timeZone'), ); diff --git a/mobile/openapi/lib/model/machine_learning_availability_checks_dto.dart b/mobile/openapi/lib/model/machine_learning_availability_checks_dto.dart index dc0cf5fac0..a9b8608ac1 100644 --- a/mobile/openapi/lib/model/machine_learning_availability_checks_dto.dart +++ b/mobile/openapi/lib/model/machine_learning_availability_checks_dto.dart @@ -21,9 +21,13 @@ class MachineLearningAvailabilityChecksDto { /// Enabled bool enabled; - num interval; + /// Minimum value: -9007199254740991 + /// Maximum value: 9007199254740991 + int interval; - num timeout; + /// Minimum value: -9007199254740991 + /// Maximum value: 9007199254740991 + int timeout; @override bool operator ==(Object other) => identical(this, other) || other is MachineLearningAvailabilityChecksDto && @@ -59,8 +63,8 @@ class MachineLearningAvailabilityChecksDto { return MachineLearningAvailabilityChecksDto( enabled: mapValueOfType(json, r'enabled')!, - interval: num.parse('${json[r'interval']}'), - timeout: num.parse('${json[r'timeout']}'), + interval: mapValueOfType(json, r'interval')!, + timeout: mapValueOfType(json, r'timeout')!, ); } return null; diff --git a/mobile/openapi/lib/model/maintenance_detect_install_storage_folder_dto.dart b/mobile/openapi/lib/model/maintenance_detect_install_storage_folder_dto.dart index e3f8c0acbe..83182f53d7 100644 --- a/mobile/openapi/lib/model/maintenance_detect_install_storage_folder_dto.dart +++ b/mobile/openapi/lib/model/maintenance_detect_install_storage_folder_dto.dart @@ -20,7 +20,10 @@ class MaintenanceDetectInstallStorageFolderDto { }); /// Number of files in the folder - num files; + /// + /// Minimum value: -9007199254740991 + /// Maximum value: 9007199254740991 + int files; StorageFolder folder; @@ -66,7 +69,7 @@ class MaintenanceDetectInstallStorageFolderDto { final json = value.cast(); return MaintenanceDetectInstallStorageFolderDto( - files: num.parse('${json[r'files']}'), + files: mapValueOfType(json, r'files')!, folder: StorageFolder.fromJson(json[r'folder'])!, readable: mapValueOfType(json, r'readable')!, writable: mapValueOfType(json, r'writable')!, diff --git a/mobile/openapi/lib/model/maintenance_status_response_dto.dart b/mobile/openapi/lib/model/maintenance_status_response_dto.dart index 124fa674fd..c1c94acd91 100644 --- a/mobile/openapi/lib/model/maintenance_status_response_dto.dart +++ b/mobile/openapi/lib/model/maintenance_status_response_dto.dart @@ -32,13 +32,15 @@ class MaintenanceStatusResponseDto { /// String? error; + /// Minimum value: -9007199254740991 + /// Maximum value: 9007199254740991 /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - num? progress; + int? progress; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -102,7 +104,7 @@ class MaintenanceStatusResponseDto { action: MaintenanceAction.fromJson(json[r'action'])!, active: mapValueOfType(json, r'active')!, error: mapValueOfType(json, r'error'), - progress: num.parse('${json[r'progress']}'), + progress: mapValueOfType(json, r'progress'), task: mapValueOfType(json, r'task'), ); } diff --git a/mobile/openapi/lib/model/metadata_search_dto.dart b/mobile/openapi/lib/model/metadata_search_dto.dart index d49ea7a4e5..29b1d5b68d 100644 --- a/mobile/openapi/lib/model/metadata_search_dto.dart +++ b/mobile/openapi/lib/model/metadata_search_dto.dart @@ -215,13 +215,14 @@ class MetadataSearchDto { /// Page number /// /// Minimum value: 1 + /// Maximum value: 9007199254740991 /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - num? page; + int? page; /// Filter by person IDs List personIds; @@ -239,7 +240,7 @@ class MetadataSearchDto { /// /// Minimum value: -1 /// Maximum value: 5 - num? rating; + int? rating; /// Number of results to return /// @@ -251,7 +252,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - num? size; + int? size; /// Filter by state/province name String? state; @@ -724,15 +725,13 @@ class MetadataSearchDto { order: AssetOrder.fromJson(json[r'order']), originalFileName: mapValueOfType(json, r'originalFileName'), originalPath: mapValueOfType(json, r'originalPath'), - page: num.parse('${json[r'page']}'), + page: mapValueOfType(json, r'page'), personIds: json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) : const [], previewPath: mapValueOfType(json, r'previewPath'), - rating: json[r'rating'] == null - ? null - : num.parse('${json[r'rating']}'), - size: num.parse('${json[r'size']}'), + rating: mapValueOfType(json, r'rating'), + size: mapValueOfType(json, r'size'), state: mapValueOfType(json, r'state'), tagIds: json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) diff --git a/mobile/openapi/lib/model/random_search_dto.dart b/mobile/openapi/lib/model/random_search_dto.dart index 3f33d8f850..728072639c 100644 --- a/mobile/openapi/lib/model/random_search_dto.dart +++ b/mobile/openapi/lib/model/random_search_dto.dart @@ -147,7 +147,7 @@ class RandomSearchDto { /// /// Minimum value: -1 /// Maximum value: 5 - num? rating; + int? rating; /// Number of results to return /// @@ -159,7 +159,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - num? size; + int? size; /// Filter by state/province name String? state; @@ -549,10 +549,8 @@ class RandomSearchDto { personIds: json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) : const [], - rating: json[r'rating'] == null - ? null - : num.parse('${json[r'rating']}'), - size: num.parse('${json[r'size']}'), + rating: mapValueOfType(json, r'rating'), + size: mapValueOfType(json, r'size'), state: mapValueOfType(json, r'state'), tagIds: json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) diff --git a/mobile/openapi/lib/model/session_create_dto.dart b/mobile/openapi/lib/model/session_create_dto.dart index 3874bc3303..37c07955cd 100644 --- a/mobile/openapi/lib/model/session_create_dto.dart +++ b/mobile/openapi/lib/model/session_create_dto.dart @@ -39,13 +39,14 @@ class SessionCreateDto { /// Session duration in seconds /// /// Minimum value: 1 + /// Maximum value: 9007199254740991 /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - num? duration; + int? duration; @override bool operator ==(Object other) => identical(this, other) || other is SessionCreateDto && @@ -94,7 +95,7 @@ class SessionCreateDto { return SessionCreateDto( deviceOS: mapValueOfType(json, r'deviceOS'), deviceType: mapValueOfType(json, r'deviceType'), - duration: num.parse('${json[r'duration']}'), + duration: mapValueOfType(json, r'duration'), ); } return null; diff --git a/mobile/openapi/lib/model/smart_search_dto.dart b/mobile/openapi/lib/model/smart_search_dto.dart index bf1465223e..9bbb4a25f0 100644 --- a/mobile/openapi/lib/model/smart_search_dto.dart +++ b/mobile/openapi/lib/model/smart_search_dto.dart @@ -154,13 +154,14 @@ class SmartSearchDto { /// Page number /// /// Minimum value: 1 + /// Maximum value: 9007199254740991 /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - num? page; + int? page; /// Filter by person IDs List personIds; @@ -187,7 +188,7 @@ class SmartSearchDto { /// /// Minimum value: -1 /// Maximum value: 5 - num? rating; + int? rating; /// Number of results to return /// @@ -199,7 +200,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - num? size; + int? size; /// Filter by state/province name String? state; @@ -583,16 +584,14 @@ class SmartSearchDto { make: mapValueOfType(json, r'make'), model: mapValueOfType(json, r'model'), ocr: mapValueOfType(json, r'ocr'), - page: num.parse('${json[r'page']}'), + page: mapValueOfType(json, r'page'), personIds: json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) : const [], query: mapValueOfType(json, r'query'), queryAssetId: mapValueOfType(json, r'queryAssetId'), - rating: json[r'rating'] == null - ? null - : num.parse('${json[r'rating']}'), - size: num.parse('${json[r'size']}'), + rating: mapValueOfType(json, r'rating'), + size: mapValueOfType(json, r'size'), state: mapValueOfType(json, r'state'), tagIds: json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) diff --git a/mobile/openapi/lib/model/statistics_search_dto.dart b/mobile/openapi/lib/model/statistics_search_dto.dart index d0070e8e12..f276e3717b 100644 --- a/mobile/openapi/lib/model/statistics_search_dto.dart +++ b/mobile/openapi/lib/model/statistics_search_dto.dart @@ -152,7 +152,7 @@ class StatisticsSearchDto { /// /// Minimum value: -1 /// Maximum value: 5 - num? rating; + int? rating; /// Filter by state/province name String? state; @@ -479,9 +479,7 @@ class StatisticsSearchDto { personIds: json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) : const [], - rating: json[r'rating'] == null - ? null - : num.parse('${json[r'rating']}'), + rating: mapValueOfType(json, r'rating'), state: mapValueOfType(json, r'state'), tagIds: json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) diff --git a/mobile/openapi/lib/model/sync_asset_v2.dart b/mobile/openapi/lib/model/sync_asset_v2.dart new file mode 100644 index 0000000000..ebe75c59b2 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_v2.dart @@ -0,0 +1,321 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetV2 { + /// Returns a new [SyncAssetV2] instance. + SyncAssetV2({ + required this.checksum, + required this.deletedAt, + required this.duration, + required this.fileCreatedAt, + required this.fileModifiedAt, + required this.height, + required this.id, + required this.isEdited, + required this.isFavorite, + required this.libraryId, + required this.livePhotoVideoId, + required this.localDateTime, + required this.originalFileName, + required this.ownerId, + required this.stackId, + required this.thumbhash, + required this.type, + required this.visibility, + required this.width, + }); + + /// Checksum + String checksum; + + /// Deleted at + DateTime? deletedAt; + + /// Duration + /// + /// Minimum value: 0 + /// Maximum value: 2147483647 + int? duration; + + /// File created at + DateTime? fileCreatedAt; + + /// File modified at + DateTime? fileModifiedAt; + + /// Asset height + /// + /// Minimum value: -9007199254740991 + /// Maximum value: 9007199254740991 + int? height; + + /// Asset ID + String id; + + /// Is edited + bool isEdited; + + /// Is favorite + bool isFavorite; + + /// Library ID + String? libraryId; + + /// Live photo video ID + String? livePhotoVideoId; + + /// Local date time + DateTime? localDateTime; + + /// Original file name + String originalFileName; + + /// Owner ID + String ownerId; + + /// Stack ID + String? stackId; + + /// Thumbhash + String? thumbhash; + + AssetTypeEnum type; + + AssetVisibility visibility; + + /// Asset width + /// + /// Minimum value: -9007199254740991 + /// Maximum value: 9007199254740991 + int? width; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetV2 && + other.checksum == checksum && + other.deletedAt == deletedAt && + other.duration == duration && + other.fileCreatedAt == fileCreatedAt && + other.fileModifiedAt == fileModifiedAt && + other.height == height && + other.id == id && + other.isEdited == isEdited && + other.isFavorite == isFavorite && + other.libraryId == libraryId && + other.livePhotoVideoId == livePhotoVideoId && + other.localDateTime == localDateTime && + other.originalFileName == originalFileName && + other.ownerId == ownerId && + other.stackId == stackId && + other.thumbhash == thumbhash && + other.type == type && + other.visibility == visibility && + other.width == width; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (checksum.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (duration == null ? 0 : duration!.hashCode) + + (fileCreatedAt == null ? 0 : fileCreatedAt!.hashCode) + + (fileModifiedAt == null ? 0 : fileModifiedAt!.hashCode) + + (height == null ? 0 : height!.hashCode) + + (id.hashCode) + + (isEdited.hashCode) + + (isFavorite.hashCode) + + (libraryId == null ? 0 : libraryId!.hashCode) + + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + + (localDateTime == null ? 0 : localDateTime!.hashCode) + + (originalFileName.hashCode) + + (ownerId.hashCode) + + (stackId == null ? 0 : stackId!.hashCode) + + (thumbhash == null ? 0 : thumbhash!.hashCode) + + (type.hashCode) + + (visibility.hashCode) + + (width == null ? 0 : width!.hashCode); + + @override + String toString() => 'SyncAssetV2[checksum=$checksum, deletedAt=$deletedAt, duration=$duration, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, height=$height, id=$id, isEdited=$isEdited, isFavorite=$isFavorite, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, ownerId=$ownerId, stackId=$stackId, thumbhash=$thumbhash, type=$type, visibility=$visibility, width=$width]'; + + Map toJson() { + final json = {}; + json[r'checksum'] = this.checksum; + if (this.deletedAt != null) { + json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? this.deletedAt!.millisecondsSinceEpoch + : this.deletedAt!.toUtc().toIso8601String(); + } else { + // json[r'deletedAt'] = null; + } + if (this.duration != null) { + json[r'duration'] = this.duration; + } else { + // json[r'duration'] = null; + } + if (this.fileCreatedAt != null) { + json[r'fileCreatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? this.fileCreatedAt!.millisecondsSinceEpoch + : this.fileCreatedAt!.toUtc().toIso8601String(); + } else { + // json[r'fileCreatedAt'] = null; + } + if (this.fileModifiedAt != null) { + json[r'fileModifiedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? this.fileModifiedAt!.millisecondsSinceEpoch + : this.fileModifiedAt!.toUtc().toIso8601String(); + } else { + // json[r'fileModifiedAt'] = null; + } + if (this.height != null) { + json[r'height'] = this.height; + } else { + // json[r'height'] = null; + } + json[r'id'] = this.id; + json[r'isEdited'] = this.isEdited; + json[r'isFavorite'] = this.isFavorite; + if (this.libraryId != null) { + json[r'libraryId'] = this.libraryId; + } else { + // json[r'libraryId'] = null; + } + if (this.livePhotoVideoId != null) { + json[r'livePhotoVideoId'] = this.livePhotoVideoId; + } else { + // json[r'livePhotoVideoId'] = null; + } + if (this.localDateTime != null) { + json[r'localDateTime'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? this.localDateTime!.millisecondsSinceEpoch + : this.localDateTime!.toUtc().toIso8601String(); + } else { + // json[r'localDateTime'] = null; + } + json[r'originalFileName'] = this.originalFileName; + json[r'ownerId'] = this.ownerId; + if (this.stackId != null) { + json[r'stackId'] = this.stackId; + } else { + // json[r'stackId'] = null; + } + if (this.thumbhash != null) { + json[r'thumbhash'] = this.thumbhash; + } else { + // json[r'thumbhash'] = null; + } + json[r'type'] = this.type; + json[r'visibility'] = this.visibility; + if (this.width != null) { + json[r'width'] = this.width; + } else { + // json[r'width'] = null; + } + return json; + } + + /// Returns a new [SyncAssetV2] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetV2? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetV2"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetV2( + checksum: mapValueOfType(json, r'checksum')!, + deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + duration: mapValueOfType(json, r'duration'), + fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + height: mapValueOfType(json, r'height'), + id: mapValueOfType(json, r'id')!, + isEdited: mapValueOfType(json, r'isEdited')!, + isFavorite: mapValueOfType(json, r'isFavorite')!, + libraryId: mapValueOfType(json, r'libraryId'), + livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), + localDateTime: mapDateTime(json, r'localDateTime', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + originalFileName: mapValueOfType(json, r'originalFileName')!, + ownerId: mapValueOfType(json, r'ownerId')!, + stackId: mapValueOfType(json, r'stackId'), + thumbhash: mapValueOfType(json, r'thumbhash'), + type: AssetTypeEnum.fromJson(json[r'type'])!, + visibility: AssetVisibility.fromJson(json[r'visibility'])!, + width: mapValueOfType(json, r'width'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetV2.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetV2.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetV2-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetV2.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'checksum', + 'deletedAt', + 'duration', + 'fileCreatedAt', + 'fileModifiedAt', + 'height', + 'id', + 'isEdited', + 'isFavorite', + 'libraryId', + 'livePhotoVideoId', + 'localDateTime', + 'originalFileName', + 'ownerId', + 'stackId', + 'thumbhash', + 'type', + 'visibility', + 'width', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_o_auth_dto.dart b/mobile/openapi/lib/model/system_config_o_auth_dto.dart index 3fd22978ff..c65de03391 100644 --- a/mobile/openapi/lib/model/system_config_o_auth_dto.dart +++ b/mobile/openapi/lib/model/system_config_o_auth_dto.dart @@ -57,7 +57,8 @@ class SystemConfigOAuthDto { /// Default storage quota /// /// Minimum value: 0 - num? defaultStorageQuota; + /// Maximum value: 9007199254740991 + int? defaultStorageQuota; /// Enabled bool enabled; @@ -200,9 +201,7 @@ class SystemConfigOAuthDto { buttonText: mapValueOfType(json, r'buttonText')!, clientId: mapValueOfType(json, r'clientId')!, clientSecret: mapValueOfType(json, r'clientSecret')!, - defaultStorageQuota: json[r'defaultStorageQuota'] == null - ? null - : num.parse('${json[r'defaultStorageQuota']}'), + defaultStorageQuota: mapValueOfType(json, r'defaultStorageQuota'), enabled: mapValueOfType(json, r'enabled')!, endSessionEndpoint: mapValueOfType(json, r'endSessionEndpoint')!, issuerUrl: mapValueOfType(json, r'issuerUrl')!, diff --git a/mobile/openapi/lib/model/system_config_smtp_transport_dto.dart b/mobile/openapi/lib/model/system_config_smtp_transport_dto.dart index 9e16e5badf..266e3f3c86 100644 --- a/mobile/openapi/lib/model/system_config_smtp_transport_dto.dart +++ b/mobile/openapi/lib/model/system_config_smtp_transport_dto.dart @@ -34,7 +34,7 @@ class SystemConfigSmtpTransportDto { /// /// Minimum value: 0 /// Maximum value: 65535 - num port; + int port; /// Whether to use secure connection (TLS/SSL) bool secure; @@ -87,7 +87,7 @@ class SystemConfigSmtpTransportDto { host: mapValueOfType(json, r'host')!, ignoreCert: mapValueOfType(json, r'ignoreCert')!, password: mapValueOfType(json, r'password')!, - port: num.parse('${json[r'port']}'), + port: mapValueOfType(json, r'port')!, secure: mapValueOfType(json, r'secure')!, username: mapValueOfType(json, r'username')!, ); diff --git a/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart b/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart index e2f9bec1ec..08e690de71 100644 --- a/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart +++ b/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart @@ -39,8 +39,8 @@ class TimeBucketAssetResponseDto { /// Array of country names extracted from EXIF GPS data List country; - /// Array of video/gif durations in hh:mm:ss.SSS format (null for static images) - List duration; + /// Array of video/gif durations in milliseconds (null for static images) + List duration; /// Array of file creation timestamps in UTC List fileCreatedAt; @@ -172,7 +172,7 @@ class TimeBucketAssetResponseDto { ? (json[r'country'] as Iterable).cast().toList(growable: false) : const [], duration: json[r'duration'] is Iterable - ? (json[r'duration'] as Iterable).cast().toList(growable: false) + ? (json[r'duration'] as Iterable).cast().toList(growable: false) : const [], fileCreatedAt: json[r'fileCreatedAt'] is Iterable ? (json[r'fileCreatedAt'] as Iterable).cast().toList(growable: false) diff --git a/mobile/openapi/lib/model/workflow_action_response_dto.dart b/mobile/openapi/lib/model/workflow_action_response_dto.dart index dcbb5ee8ef..999d9d86cb 100644 --- a/mobile/openapi/lib/model/workflow_action_response_dto.dart +++ b/mobile/openapi/lib/model/workflow_action_response_dto.dart @@ -26,7 +26,10 @@ class WorkflowActionResponseDto { String id; /// Action order - num order; + /// + /// Minimum value: -9007199254740991 + /// Maximum value: 9007199254740991 + int order; /// Plugin action ID String pluginActionId; @@ -79,7 +82,7 @@ class WorkflowActionResponseDto { return WorkflowActionResponseDto( actionConfig: mapCastOfType(json, r'actionConfig'), id: mapValueOfType(json, r'id')!, - order: num.parse('${json[r'order']}'), + order: mapValueOfType(json, r'order')!, pluginActionId: mapValueOfType(json, r'pluginActionId')!, workflowId: mapValueOfType(json, r'workflowId')!, ); diff --git a/mobile/openapi/lib/model/workflow_filter_response_dto.dart b/mobile/openapi/lib/model/workflow_filter_response_dto.dart index 932722f5a5..b9a841a68b 100644 --- a/mobile/openapi/lib/model/workflow_filter_response_dto.dart +++ b/mobile/openapi/lib/model/workflow_filter_response_dto.dart @@ -26,7 +26,10 @@ class WorkflowFilterResponseDto { String id; /// Filter order - num order; + /// + /// Minimum value: -9007199254740991 + /// Maximum value: 9007199254740991 + int order; /// Plugin filter ID String pluginFilterId; @@ -79,7 +82,7 @@ class WorkflowFilterResponseDto { return WorkflowFilterResponseDto( filterConfig: mapCastOfType(json, r'filterConfig'), id: mapValueOfType(json, r'id')!, - order: num.parse('${json[r'order']}'), + order: mapValueOfType(json, r'order')!, pluginFilterId: mapValueOfType(json, r'pluginFilterId')!, workflowId: mapValueOfType(json, r'workflowId')!, ); diff --git a/mobile/test/domain/services/sync_stream_service_test.dart b/mobile/test/domain/services/sync_stream_service_test.dart index a182c6cdca..1bee1dccde 100644 --- a/mobile/test/domain/services/sync_stream_service_test.dart +++ b/mobile/test/domain/services/sync_stream_service_test.dart @@ -419,8 +419,8 @@ void main() { 'album-b': [mergedAsset], }; when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer((invocation) async { - final Iterable requestedChecksums = invocation.positionalArguments.first as Iterable; - expect(requestedChecksums.toSet(), equals({'checksum-local', 'checksum-merged', 'checksum-remote-only'})); + final Iterable requestedRemoteIds = invocation.positionalArguments.first as Iterable; + expect(requestedRemoteIds.toSet(), equals({'remote-1', 'remote-2', 'remote-3'})); return assetsByAlbum; }); @@ -482,12 +482,18 @@ void main() { verifyNever(() => mockTrashedLocalAssetRepo.trashLocalAsset(any())); }); - test("does not request local deletions for permanent remote delete events", () async { + test("requests local deletions lookup by remote ids for permanent remote delete events", () async { + when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer((invocation) async { + final Iterable requestedRemoteIds = invocation.positionalArguments.first as Iterable; + expect(requestedRemoteIds.toSet(), equals({'remote-asset'})); + return {}; + }); + final events = [SyncStreamStub.assetDeleteV1]; await simulateEvents(events); - verifyNever(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())); + verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1); verifyNever(() => mockLocalFilesManagerRepo.moveToTrash(any())); verify(() => mockSyncStreamRepo.deleteAssetsV1(any())).called(1); }); diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 05262210d0..1cfdcd5ab2 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -7964,8 +7964,9 @@ "description": "Page number for pagination", "schema": { "minimum": 1, + "maximum": 9007199254740991, "default": 1, - "type": "number" + "type": "integer" } }, { @@ -7977,7 +7978,7 @@ "minimum": 1, "maximum": 1000, "default": 500, - "type": "number" + "type": "integer" } }, { @@ -9372,7 +9373,7 @@ ], "x-immich-state": "Stable", "schema": { - "type": "number", + "type": "integer", "minimum": -1, "maximum": 5, "nullable": true @@ -9386,7 +9387,7 @@ "schema": { "minimum": 1, "maximum": 1000, - "type": "number" + "type": "integer" } }, { @@ -15636,7 +15637,9 @@ }, "dateTimeRelative": { "description": "Relative time offset in seconds", - "type": "number" + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" }, "description": { "description": "Asset description", @@ -16258,8 +16261,10 @@ "type": "string" }, "duration": { - "description": "Duration (for videos)", - "type": "string" + "description": "Duration in milliseconds (for videos)", + "maximum": 2147483647, + "minimum": 0, + "type": "integer" }, "fileCreatedAt": { "description": "File creation date", @@ -16627,9 +16632,11 @@ "type": "string" }, "duration": { - "description": "Video/gif duration in hh:mm:ss.SSS format (null for static images)", + "description": "Video/gif duration in milliseconds (null for static images)", + "maximum": 2147483647, + "minimum": 0, "nullable": true, - "type": "string" + "type": "integer" }, "exifInfo": { "$ref": "#/components/schemas/ExifResponseDto" @@ -16650,9 +16657,10 @@ }, "height": { "description": "Asset height", + "maximum": 9007199254740991, "minimum": 0, "nullable": true, - "type": "number" + "type": "integer" }, "id": { "description": "Asset ID", @@ -16795,9 +16803,10 @@ }, "width": { "description": "Asset width", + "maximum": 9007199254740991, "minimum": 0, "nullable": true, - "type": "number" + "type": "integer" } }, "required": [ @@ -17214,23 +17223,27 @@ "properties": { "height": { "description": "Height of the crop", + "maximum": 9007199254740991, "minimum": 1, - "type": "number" + "type": "integer" }, "width": { "description": "Width of the crop", + "maximum": 9007199254740991, "minimum": 1, - "type": "number" + "type": "integer" }, "x": { "description": "Top-Left X coordinate of crop", + "maximum": 9007199254740991, "minimum": 0, - "type": "number" + "type": "integer" }, "y": { "description": "Top-Left Y coordinate of crop", + "maximum": 9007199254740991, "minimum": 0, - "type": "number" + "type": "integer" } }, "required": [ @@ -17254,8 +17267,9 @@ }, "keepLastAmount": { "description": "Keep last amount", + "maximum": 9007199254740991, "minimum": 1, - "type": "number" + "type": "integer" } }, "required": [ @@ -17288,7 +17302,9 @@ }, "filesize": { "description": "Backup file size", - "type": "number" + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" }, "timezone": { "description": "Backup timezone", @@ -17627,16 +17643,18 @@ "exifImageHeight": { "default": null, "description": "Image height in pixels", + "maximum": 9007199254740991, "minimum": 0, "nullable": true, - "type": "number" + "type": "integer" }, "exifImageWidth": { "default": null, "description": "Image width in pixels", + "maximum": 9007199254740991, "minimum": 0, "nullable": true, - "type": "number" + "type": "integer" }, "exposureTime": { "default": null, @@ -17667,8 +17685,10 @@ "iso": { "default": null, "description": "ISO sensitivity", + "maximum": 9007199254740991, + "minimum": -9007199254740991, "nullable": true, - "type": "number" + "type": "integer" }, "latitude": { "default": null, @@ -17722,8 +17742,10 @@ "rating": { "default": null, "description": "Rating", + "maximum": 9007199254740991, + "minimum": -9007199254740991, "nullable": true, - "type": "number" + "type": "integer" }, "state": { "default": null, @@ -18150,10 +18172,14 @@ "type": "boolean" }, "interval": { - "type": "number" + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" }, "timeout": { - "type": "number" + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" } }, "required": [ @@ -18203,7 +18229,9 @@ "properties": { "files": { "description": "Number of files in the folder", - "type": "number" + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" }, "folder": { "$ref": "#/components/schemas/StorageFolder" @@ -18246,7 +18274,9 @@ "type": "string" }, "progress": { - "type": "number" + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" }, "task": { "type": "string" @@ -18723,8 +18753,9 @@ }, "page": { "description": "Page number", + "maximum": 9007199254740991, "minimum": 1, - "type": "number" + "type": "integer" }, "personIds": { "description": "Filter by person IDs", @@ -18744,7 +18775,7 @@ "maximum": 5, "minimum": -1, "nullable": true, - "type": "number", + "type": "integer", "x-immich-history": [ { "version": "v1", @@ -18766,7 +18797,7 @@ "description": "Number of results to return", "maximum": 1000, "minimum": 1, - "type": "number" + "type": "integer" }, "state": { "description": "Filter by state/province name", @@ -20597,7 +20628,7 @@ "maximum": 5, "minimum": -1, "nullable": true, - "type": "number", + "type": "integer", "x-immich-history": [ { "version": "v1", @@ -20619,7 +20650,7 @@ "description": "Number of results to return", "maximum": 1000, "minimum": 1, - "type": "number" + "type": "integer" }, "state": { "description": "Filter by state/province name", @@ -21437,8 +21468,9 @@ }, "duration": { "description": "Session duration in seconds", + "maximum": 9007199254740991, "minimum": 1, - "type": "number" + "type": "integer" } }, "type": "object" @@ -21952,8 +21984,9 @@ }, "page": { "description": "Page number", + "maximum": 9007199254740991, "minimum": 1, - "type": "number" + "type": "integer" }, "personIds": { "description": "Filter by person IDs", @@ -21979,7 +22012,7 @@ "maximum": 5, "minimum": -1, "nullable": true, - "type": "number", + "type": "integer", "x-immich-history": [ { "version": "v1", @@ -22001,7 +22034,7 @@ "description": "Number of results to return", "maximum": 1000, "minimum": 1, - "type": "number" + "type": "integer" }, "state": { "description": "Filter by state/province name", @@ -22239,7 +22272,7 @@ "maximum": 5, "minimum": -1, "nullable": true, - "type": "number", + "type": "integer", "x-immich-history": [ { "version": "v1", @@ -23258,6 +23291,135 @@ ], "type": "object" }, + "SyncAssetV2": { + "properties": { + "checksum": { + "description": "Checksum", + "type": "string" + }, + "deletedAt": { + "description": "Deleted at", + "example": "2024-01-01T00:00:00.000Z", + "format": "date-time", + "nullable": true, + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "type": "string" + }, + "duration": { + "description": "Duration", + "maximum": 2147483647, + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "fileCreatedAt": { + "description": "File created at", + "example": "2024-01-01T00:00:00.000Z", + "format": "date-time", + "nullable": true, + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "type": "string" + }, + "fileModifiedAt": { + "description": "File modified at", + "example": "2024-01-01T00:00:00.000Z", + "format": "date-time", + "nullable": true, + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "type": "string" + }, + "height": { + "description": "Asset height", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "nullable": true, + "type": "integer" + }, + "id": { + "description": "Asset ID", + "type": "string" + }, + "isEdited": { + "description": "Is edited", + "type": "boolean" + }, + "isFavorite": { + "description": "Is favorite", + "type": "boolean" + }, + "libraryId": { + "description": "Library ID", + "nullable": true, + "type": "string" + }, + "livePhotoVideoId": { + "description": "Live photo video ID", + "nullable": true, + "type": "string" + }, + "localDateTime": { + "description": "Local date time", + "example": "2024-01-01T00:00:00.000Z", + "format": "date-time", + "nullable": true, + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "type": "string" + }, + "originalFileName": { + "description": "Original file name", + "type": "string" + }, + "ownerId": { + "description": "Owner ID", + "type": "string" + }, + "stackId": { + "description": "Stack ID", + "nullable": true, + "type": "string" + }, + "thumbhash": { + "description": "Thumbhash", + "nullable": true, + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/AssetTypeEnum" + }, + "visibility": { + "$ref": "#/components/schemas/AssetVisibility" + }, + "width": { + "description": "Asset width", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "nullable": true, + "type": "integer" + } + }, + "required": [ + "checksum", + "deletedAt", + "duration", + "fileCreatedAt", + "fileModifiedAt", + "height", + "id", + "isEdited", + "isFavorite", + "libraryId", + "livePhotoVideoId", + "localDateTime", + "originalFileName", + "ownerId", + "stackId", + "thumbhash", + "type", + "visibility", + "width" + ], + "type": "object" + }, "SyncAuthUserV1": { "properties": { "avatarColor": { @@ -23358,6 +23520,7 @@ "UserV1", "UserDeleteV1", "AssetV1", + "AssetV2", "AssetDeleteV1", "AssetExifV1", "AssetEditV1", @@ -23369,7 +23532,9 @@ "PartnerV1", "PartnerDeleteV1", "PartnerAssetV1", + "PartnerAssetV2", "PartnerAssetBackfillV1", + "PartnerAssetBackfillV2", "PartnerAssetDeleteV1", "PartnerAssetExifV1", "PartnerAssetExifBackfillV1", @@ -23383,8 +23548,11 @@ "AlbumUserBackfillV1", "AlbumUserDeleteV1", "AlbumAssetCreateV1", + "AlbumAssetCreateV2", "AlbumAssetUpdateV1", + "AlbumAssetUpdateV2", "AlbumAssetBackfillV1", + "AlbumAssetBackfillV2", "AlbumAssetExifCreateV1", "AlbumAssetExifUpdateV1", "AlbumAssetExifBackfillV1", @@ -23676,8 +23844,10 @@ "AlbumUsersV1", "AlbumToAssetsV1", "AlbumAssetsV1", + "AlbumAssetsV2", "AlbumAssetExifsV1", "AssetsV1", + "AssetsV2", "AssetExifsV1", "AssetEditsV1", "AssetMetadataV1", @@ -23687,6 +23857,7 @@ "MemoryToAssetsV1", "PartnersV1", "PartnerAssetsV1", + "PartnerAssetsV2", "PartnerAssetExifsV1", "PartnerStacksV1", "StacksV1", @@ -24486,9 +24657,10 @@ }, "defaultStorageQuota": { "description": "Default storage quota", + "maximum": 9007199254740991, "minimum": 0, "nullable": true, - "type": "number" + "type": "integer" }, "enabled": { "description": "Enabled", @@ -24663,7 +24835,7 @@ "description": "SMTP server port", "maximum": 65535, "minimum": 0, - "type": "number" + "type": "integer" }, "secure": { "description": "Whether to use secure connection (TLS/SSL)", @@ -25079,10 +25251,12 @@ "type": "array" }, "duration": { - "description": "Array of video/gif durations in hh:mm:ss.SSS format (null for static images)", + "description": "Array of video/gif durations in milliseconds (null for static images)", "items": { + "maximum": 2147483647, + "minimum": 0, "nullable": true, - "type": "string" + "type": "integer" }, "type": "array" }, @@ -26081,7 +26255,9 @@ }, "order": { "description": "Action order", - "type": "number" + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" }, "pluginActionId": { "description": "Plugin action ID", @@ -26180,7 +26356,9 @@ }, "order": { "description": "Filter order", - "type": "number" + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" }, "pluginFilterId": { "description": "Plugin filter ID", diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 78a58db20e..27511d2522 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -614,8 +614,8 @@ export type AssetMetadataUpsertItemDto = { export type AssetMediaCreateDto = { /** Asset file data */ assetData: Blob; - /** Duration (for videos) */ - duration?: string; + /** Duration in milliseconds (for videos) */ + duration?: number; /** File creation date */ fileCreatedAt: string; /** File modification date */ @@ -854,8 +854,8 @@ export type AssetResponseDto = { createdAt: string; /** Duplicate group ID */ duplicateId?: string | null; - /** Video/gif duration in hh:mm:ss.SSS format (null for static images) */ - duration: string | null; + /** Video/gif duration in milliseconds (null for static images) */ + duration: number | null; exifInfo?: ExifResponseDto; /** The actual UTC timestamp when the file was created/captured, preserving timezone information. This is the authoritative timestamp for chronological sorting within timeline groups. Combined with timezone data, this can be used to determine the exact moment the photo was taken. */ fileCreatedAt: string; @@ -2673,8 +2673,8 @@ export type TimeBucketAssetResponseDto = { city: (string | null)[]; /** Array of country names extracted from EXIF GPS data */ country: (string | null)[]; - /** Array of video/gif durations in hh:mm:ss.SSS format (null for static images) */ - duration: (string | null)[]; + /** Array of video/gif durations in milliseconds (null for static images) */ + duration: (number | null)[]; /** Array of file creation timestamps in UTC */ fileCreatedAt: string[]; /** Array of asset IDs in the time bucket */ @@ -3113,6 +3113,44 @@ export type SyncAssetV1 = { /** Asset width */ width: number | null; }; +export type SyncAssetV2 = { + /** Checksum */ + checksum: string; + /** Deleted at */ + deletedAt: string | null; + /** Duration */ + duration: number | null; + /** File created at */ + fileCreatedAt: string | null; + /** File modified at */ + fileModifiedAt: string | null; + /** Asset height */ + height: number | null; + /** Asset ID */ + id: string; + /** Is edited */ + isEdited: boolean; + /** Is favorite */ + isFavorite: boolean; + /** Library ID */ + libraryId: string | null; + /** Live photo video ID */ + livePhotoVideoId: string | null; + /** Local date time */ + localDateTime: string | null; + /** Original file name */ + originalFileName: string; + /** Owner ID */ + ownerId: string; + /** Stack ID */ + stackId: string | null; + /** Thumbhash */ + thumbhash: string | null; + "type": AssetTypeEnum; + visibility: AssetVisibility; + /** Asset width */ + width: number | null; +}; export type SyncAuthUserV1 = { avatarColor?: (UserAvatarColor) | null; /** User deleted at */ @@ -7147,6 +7185,7 @@ export enum SyncEntityType { UserV1 = "UserV1", UserDeleteV1 = "UserDeleteV1", AssetV1 = "AssetV1", + AssetV2 = "AssetV2", AssetDeleteV1 = "AssetDeleteV1", AssetExifV1 = "AssetExifV1", AssetEditV1 = "AssetEditV1", @@ -7158,7 +7197,9 @@ export enum SyncEntityType { PartnerV1 = "PartnerV1", PartnerDeleteV1 = "PartnerDeleteV1", PartnerAssetV1 = "PartnerAssetV1", + PartnerAssetV2 = "PartnerAssetV2", PartnerAssetBackfillV1 = "PartnerAssetBackfillV1", + PartnerAssetBackfillV2 = "PartnerAssetBackfillV2", PartnerAssetDeleteV1 = "PartnerAssetDeleteV1", PartnerAssetExifV1 = "PartnerAssetExifV1", PartnerAssetExifBackfillV1 = "PartnerAssetExifBackfillV1", @@ -7172,8 +7213,11 @@ export enum SyncEntityType { AlbumUserBackfillV1 = "AlbumUserBackfillV1", AlbumUserDeleteV1 = "AlbumUserDeleteV1", AlbumAssetCreateV1 = "AlbumAssetCreateV1", + AlbumAssetCreateV2 = "AlbumAssetCreateV2", AlbumAssetUpdateV1 = "AlbumAssetUpdateV1", + AlbumAssetUpdateV2 = "AlbumAssetUpdateV2", AlbumAssetBackfillV1 = "AlbumAssetBackfillV1", + AlbumAssetBackfillV2 = "AlbumAssetBackfillV2", AlbumAssetExifCreateV1 = "AlbumAssetExifCreateV1", AlbumAssetExifUpdateV1 = "AlbumAssetExifUpdateV1", AlbumAssetExifBackfillV1 = "AlbumAssetExifBackfillV1", @@ -7203,8 +7247,10 @@ export enum SyncRequestType { AlbumUsersV1 = "AlbumUsersV1", AlbumToAssetsV1 = "AlbumToAssetsV1", AlbumAssetsV1 = "AlbumAssetsV1", + AlbumAssetsV2 = "AlbumAssetsV2", AlbumAssetExifsV1 = "AlbumAssetExifsV1", AssetsV1 = "AssetsV1", + AssetsV2 = "AssetsV2", AssetExifsV1 = "AssetExifsV1", AssetEditsV1 = "AssetEditsV1", AssetMetadataV1 = "AssetMetadataV1", @@ -7214,6 +7260,7 @@ export enum SyncRequestType { MemoryToAssetsV1 = "MemoryToAssetsV1", PartnersV1 = "PartnersV1", PartnerAssetsV1 = "PartnerAssetsV1", + PartnerAssetsV2 = "PartnerAssetsV2", PartnerAssetExifsV1 = "PartnerAssetExifsV1", PartnerStacksV1 = "PartnerStacksV1", StacksV1 = "StacksV1", diff --git a/package.json b/package.json index 133c61ba5a..abe9e6b44f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "2.7.5", "description": "Monorepo for Immich", "private": true, - "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319", + "packageManager": "pnpm@10.33.1+sha512.05ba3c1d5d1c18f68df06470d74055e62d41fc110a0c660db1b2dfb2785327f04cf0f68345d4609bc52089e7fa0343c31593b2f9594e2c5d5da426230acc9820", "engines": { "pnpm": ">=10.0.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7fa8af285b..6de45bf746 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,7 +68,7 @@ importers: version: 24.12.2 '@vitest/coverage-v8': specifier: ^4.0.0 - version: 4.1.4(vitest@4.1.4) + version: 4.1.5(vitest@4.1.5) byte-size: specifier: ^9.0.0 version: 9.0.1 @@ -107,16 +107,16 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.58.0 - version: 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) vite: specifier: ^8.0.0 - version: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: ^4.0.0 - version: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest-fetch-mock: specifier: ^0.4.0 - version: 0.4.5(vitest@4.1.4) + version: 0.4.5(vitest@4.1.5) yaml: specifier: ^2.3.1 version: 2.8.3 @@ -146,7 +146,7 @@ importers: version: 3.1.1(@types/react@19.2.14)(react@19.2.5) autoprefixer: specifier: ^10.4.17 - version: 10.5.0(postcss@8.5.10) + version: 10.5.0(postcss@8.5.12) docusaurus-lunr-search: specifier: ^3.3.2 version: 3.6.0(@docusaurus/core@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -155,7 +155,7 @@ importers: version: 2.3.9 postcss: specifier: ^8.4.25 - version: 8.5.10 + version: 8.5.12 prism-react-renderer: specifier: ^2.3.1 version: 2.4.1(react@19.2.5) @@ -246,7 +246,7 @@ importers: version: 64.0.0(eslint@10.2.1(jiti@2.6.1)) exiftool-vendored: specifier: ^35.0.0 - version: 35.15.1 + version: 35.18.0 globals: specifier: ^17.0.0 version: 17.5.0 @@ -279,16 +279,16 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.28.0 - version: 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) utimes: specifier: ^5.2.1 version: 5.2.1(encoding@0.1.13) vite-tsconfig-paths: specifier: ^6.1.1 - version: 6.1.1(typescript@6.0.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.1.1(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: ^4.0.0 - version: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) e2e-auth-server: devDependencies: @@ -346,10 +346,10 @@ importers: version: 2.0.0-rc13 '@immich/sql-tools': specifier: ^0.5.1 - version: 0.5.1 + version: 0.5.2 '@nestjs/bullmq': specifier: ^11.0.1 - version: 11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(bullmq@5.74.1) + version: 11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(bullmq@5.76.1) '@nestjs/common': specifier: ^11.0.4 version: 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -366,8 +366,8 @@ importers: specifier: ^6.0.0 version: 6.1.3(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) '@nestjs/swagger': - specifier: 11.2.6 - version: 11.2.6(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2) + specifier: ^11.4.2 + version: 11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2) '@nestjs/websockets': specifier: ^11.0.4 version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -384,14 +384,14 @@ importers: specifier: ^0.215.0 version: 0.215.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-ioredis': - specifier: ^0.62.0 - version: 0.62.0(@opentelemetry/api@1.9.1) + specifier: ^0.63.0 + version: 0.63.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-nestjs-core': - specifier: ^0.60.0 - version: 0.60.0(@opentelemetry/api@1.9.1) + specifier: ^0.61.0 + version: 0.61.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-pg': - specifier: ^0.66.0 - version: 0.66.0(@opentelemetry/api@1.9.1) + specifier: ^0.67.0 + version: 0.67.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': specifier: ^2.0.1 version: 2.7.0(@opentelemetry/api@1.9.1) @@ -427,7 +427,7 @@ importers: version: 2.2.2 bullmq: specifier: ^5.51.0 - version: 5.74.1 + version: 5.76.1 chokidar: specifier: ^4.0.3 version: 4.0.3 @@ -445,7 +445,7 @@ importers: version: 4.4.0 exiftool-vendored: specifier: ^35.0.0 - version: 35.15.1 + version: 35.18.0 express: specifier: ^5.1.0 version: 5.2.1 @@ -511,7 +511,7 @@ importers: version: 7.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) nestjs-zod: specifier: ^5.3.0 - version: 5.3.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.2.6(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6) + version: 5.3.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6) nodemailer: specifier: ^8.0.0 version: 8.0.5 @@ -570,8 +570,8 @@ importers: specifier: ^2.0.0 version: 2.0.9 uuid: - specifier: ^11.1.0 - version: 11.1.0 + specifier: ^14.0.0 + version: 14.0.0 validator: specifier: ^13.12.0 version: 13.15.35 @@ -584,7 +584,7 @@ importers: version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) '@nestjs/cli': specifier: ^11.0.2 - version: 11.0.21(@swc/core@1.15.26(@swc/helpers@0.5.17))(@types/node@24.12.2)(esbuild@0.28.0)(prettier@3.8.3) + version: 11.0.21(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@24.12.2)(esbuild@0.28.0)(prettier@3.8.3) '@nestjs/schematics': specifier: ^11.0.0 version: 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@6.0.3) @@ -593,7 +593,7 @@ importers: version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-express@11.1.19) '@swc/core': specifier: ^1.4.14 - version: 1.15.26(@swc/helpers@0.5.17) + version: 1.15.30(@swc/helpers@0.5.21) '@types/archiver': specifier: ^7.0.0 version: 7.0.0 @@ -710,13 +710,13 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.28.0 - version: 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) unplugin-swc: specifier: ^1.4.5 - version: 1.5.9(@swc/core@1.15.26(@swc/helpers@0.5.17))(rollup@4.55.1) + version: 1.5.9(@swc/core@1.15.30(@swc/helpers@0.5.21))(rollup@4.55.1) vite-tsconfig-paths: specifier: ^6.0.0 - version: 6.1.1(typescript@6.0.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.1.1(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: ^3.0.0 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) @@ -734,7 +734,7 @@ importers: version: link:../open-api/typescript-sdk '@immich/ui': specifier: ^0.76.0 - version: 0.76.0(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) + version: 0.76.2(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) '@mapbox/mapbox-gl-rtl-text': specifier: 0.4.0 version: 0.4.0 @@ -776,7 +776,7 @@ importers: version: 2.6.0 fabric: specifier: ^7.0.0 - version: 7.2.0 + version: 7.3.1 geo-coordinates-parser: specifier: ^1.7.4 version: 1.7.4 @@ -803,7 +803,7 @@ importers: version: 3.7.2 maplibre-gl: specifier: ^5.6.2 - version: 5.23.0 + version: 5.24.0 pmtiles: specifier: ^4.3.0 version: 4.4.1 @@ -812,7 +812,7 @@ importers: version: 1.5.4 simple-icons: specifier: ^16.0.0 - version: 16.16.0 + version: 16.17.0 socket.io-client: specifier: ~4.8.0 version: 4.8.3 @@ -864,25 +864,25 @@ importers: version: 3.1.2 '@sveltejs/adapter-static': specifier: ^3.0.8 - version: 3.0.10(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) + version: 3.0.10(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) '@sveltejs/enhanced-img': specifier: ^0.10.4 - version: 0.10.4(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(rollup@4.55.1)(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 0.10.4(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(rollup@4.55.1)(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@sveltejs/kit': specifier: ^2.56.1 - version: 2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@sveltejs/vite-plugin-svelte': specifier: 7.0.0 - version: 7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@tailwindcss/vite': specifier: ^4.2.2 - version: 4.2.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@testing-library/jest-dom': specifier: ^6.4.2 version: 6.9.1 '@testing-library/svelte': specifier: ^5.2.8 - version: 5.3.1(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) + version: 5.3.1(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) '@testing-library/user-event': specifier: ^14.5.2 version: 14.6.1(@testing-library/dom@10.4.1) @@ -909,7 +909,7 @@ importers: version: 1.5.6 '@vitest/coverage-v8': specifier: ^4.0.0 - version: 4.1.4(vitest@4.1.4) + version: 4.1.5(vitest@4.1.5) dotenv: specifier: ^17.0.0 version: 17.4.2 @@ -924,7 +924,7 @@ importers: version: 7.0.1(eslint@10.2.1(jiti@2.6.1)) eslint-plugin-svelte: specifier: ^3.12.4 - version: 3.17.0(eslint@10.2.1(jiti@2.6.1))(svelte@5.55.2) + version: 3.17.1(eslint@10.2.1(jiti@2.6.1))(svelte@5.55.2) eslint-plugin-unicorn: specifier: ^64.0.0 version: 64.0.0(eslint@10.2.1(jiti@2.6.1)) @@ -945,7 +945,7 @@ importers: version: 3.5.1(prettier@3.8.3)(svelte@5.55.2) rollup-plugin-visualizer: specifier: ^7.0.0 - version: 7.0.1(rolldown@1.0.0-rc.15)(rollup@4.55.1) + version: 7.0.1(rolldown@1.0.0-rc.17)(rollup@4.55.1) svelte: specifier: 5.55.2 version: 5.55.2 @@ -963,13 +963,13 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.45.0 - version: 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) vite: specifier: ^8.0.0 - version: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: ^4.0.0 - version: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) packages: @@ -2249,11 +2249,11 @@ packages: resolution: {integrity: sha512-T3B0WTigsIthe0D4LQa2k+7bJY+c3WS+Wq2JhcznOSpn1lSN64yNtHQXboCj3QnUs1EuAZszQG1SHKu5w5ZrlA==} engines: {node: '>=20.0'} - '@emnapi/core@1.9.2': - resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@emnapi/runtime@1.9.2': - resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} @@ -3015,8 +3015,8 @@ packages: '@immich/justified-layout-wasm@0.4.3': resolution: {integrity: sha512-fpcQ7zPhP3Cp1bEXhONVYSUeIANa2uzaQFGKufUZQo5FO7aFT77szTVChhlCy4XaVy5R4ZvgSkA/1TJmeORz7Q==} - '@immich/sql-tools@0.5.1': - resolution: {integrity: sha512-1yb5w8IS0PIVgTZ75fAsbaH1JowNNB7d6h0h8ZLQt32Y35xBzmZef/IL9LVAWnWBObzwWi12+RLcg0gkMS6dpA==} + '@immich/sql-tools@0.5.2': + resolution: {integrity: sha512-O1SJ+BbeFVsUTF4af1MfagJZM+lPgLjI8lQ3SZNjpo8SGJReSbUl2ii03OKuGni/G0yp2GnRLpOTNSHYGtVrcg==} hasBin: true '@immich/svelte-markdown-preprocess@0.4.1': @@ -3024,8 +3024,8 @@ packages: peerDependencies: svelte: ^5.0.0 - '@immich/ui@0.76.0': - resolution: {integrity: sha512-ghxfbC47UPMwQJ65maOUYdduQ/G/zo87Oc2ZUKe6o8KgoHsWxLVjQUw44T3dZdFOhvyS8SsIlkGLuagVcrM9Bg==} + '@immich/ui@0.76.2': + resolution: {integrity: sha512-D5oqBMyGg8x7YcrmWLgYO1z6d5BU454jejoDJqkW/oJGHMXCSSyY+l/skmVR+fLd1Pttf28gJE9TVG1xXqJ0rA==} peerDependencies: svelte: ^5.0.0 @@ -3172,8 +3172,8 @@ packages: '@types/node': optional: true - '@internationalized/date@3.12.0': - resolution: {integrity: sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ==} + '@internationalized/date@3.12.1': + resolution: {integrity: sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==} '@ioredis/commands@1.5.1': resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} @@ -3428,8 +3428,8 @@ packages: resolution: {integrity: sha512-zxa92qF96ZNojLxeAjnaRpjVCy+swoUNJvDhtpC90k7u5F0TMr4GmvNqMKvYrMoPB8d7gRSXbMG1hBbmgESIsw==} hasBin: true - '@maplibre/mlt@1.1.8': - resolution: {integrity: sha512-8vtfYGidr1rNkv5IwIoU2lfe3Oy+Wa8HluzQYcQi9cveU9K3pweAal/poQj4GJ0K/EW4bTQp2wVAs09g2yDRZg==} + '@maplibre/mlt@1.1.9': + resolution: {integrity: sha512-g/tD8EYJB97udq33ipuJ9a4Q7fcbZnTEnUrgnEc/tLMmEL+zaCbR+X5fkDBO2dgpaAMsLH179qE3UXg2N0Nc/g==} '@maplibre/vt-pbf@4.3.0': resolution: {integrity: sha512-jIvp8F5hQCcreqOOpEt42TJMUlsrEcpf/kI1T2v85YrQRV6PPXUcEXUg5karKtH6oh47XJZ4kHu56pUkOuqA7w==} @@ -3560,12 +3560,12 @@ packages: '@nestjs/websockets': optional: true - '@nestjs/mapped-types@2.1.0': - resolution: {integrity: sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==} + '@nestjs/mapped-types@2.1.1': + resolution: {integrity: sha512-SCCoMEJ6jdeI5h/N+KCVF1+pmg/hmEkNA5nHTS8Gvww7T/LCl4o1gFLinw2iQ60w7slFkszHcGLKGdazVI4F8A==} peerDependencies: '@nestjs/common': ^10.0.0 || ^11.0.0 class-transformer: ^0.4.0 || ^0.5.0 - class-validator: ^0.13.0 || ^0.14.0 + class-validator: ^0.13.0 || ^0.14.0 || ^0.15.0 reflect-metadata: ^0.1.12 || ^0.2.0 peerDependenciesMeta: class-transformer: @@ -3601,8 +3601,8 @@ packages: prettier: optional: true - '@nestjs/swagger@11.2.6': - resolution: {integrity: sha512-oiXOxMQqDFyv1AKAqFzSo6JPvMEs4uA36Eyz/s2aloZLxUjcLfUMELSLSNQunr61xCPTpwEOShfmO7NIufKXdA==} + '@nestjs/swagger@11.4.2': + resolution: {integrity: sha512-aBihEogDMj/bLEcaqhkvyX/ZVWUw/bmnhKzR0zwUoyGJikvZyaq7rOPYl/H7Lxkkr3c90SJxyuv1AX2UT1WKlw==} peerDependencies: '@fastify/static': ^8.0.0 || ^9.0.0 '@nestjs/common': ^11.0.1 @@ -3679,10 +3679,6 @@ packages: '@oazapfts/runtime@1.2.0': resolution: {integrity: sha512-fi7dp7dNayyh/vzqhf0ZdoPfC7tJvYfjaE8MBL1yR+iIsH7cFoqHt+DV70VU49OMCqLc7wQa+yVJcSmIRnV4wA==} - '@opentelemetry/api-logs@0.214.0': - resolution: {integrity: sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==} - engines: {node: '>=8.0.0'} - '@opentelemetry/api-logs@0.215.0': resolution: {integrity: sha512-xrFlqhdhUyO8wSRn6DjE0145/HPWSJ5Nm0C7vWua6TdL/FSEAZvEyvdsa9CRXuxo9ebb7j/NEPhEcO62IJ0qUA==} engines: {node: '>=8.0.0'} @@ -3791,26 +3787,20 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-ioredis@0.62.0': - resolution: {integrity: sha512-ZYt//zcPve8qklaZX+5Z4MkU7UpEkFRrxsf2cnaKYBitqDnsCN69CPAuuMOX6NYdW2rG9sFy7V/QWtBlP5XiNQ==} + '@opentelemetry/instrumentation-ioredis@0.63.0': + resolution: {integrity: sha512-x+h/uq7mstqr7TwU1q0MdmMkyU1SDZcmd/ErXbdNfScmXMcYfo8sCRzMsL9UwukSdaU3ccYYpYweGXghv9xN0Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-nestjs-core@0.60.0': - resolution: {integrity: sha512-BZqFAoD+frnwjpb0/T4kEEQMhl2YykZch4n2MMLKAVTzTehTBBV2hZxvFF629ipS+WOGBKjCjz1dycU9QNIckQ==} + '@opentelemetry/instrumentation-nestjs-core@0.61.0': + resolution: {integrity: sha512-e/zpwFbEyQFK8uINyFqbeQsA6PW5+hKI+eJj8L98lz1FnQSbRsNMz3Z8c0KYWcDqbg857DpB97s9P3lXdtwccg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-pg@0.66.0': - resolution: {integrity: sha512-KxfLGXBb7k2ueaPJfq2GXBDXBly8P+SpR/4Mj410hhNgmQF3sCqwXvUBQxZQkDAmsdBAoenM+yV1LhtsMRamcA==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/instrumentation@0.214.0': - resolution: {integrity: sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w==} + '@opentelemetry/instrumentation-pg@0.67.0': + resolution: {integrity: sha512-1b1o/9nelDwoE3+EucZ9eHZsdUgji799C94lX1ZPy6O0EVjdTj3HczLL6z3GqPGZHmV4OpmJjGz8kuLtuPjCGA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 @@ -3851,8 +3841,8 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/redis-common@0.38.2': - resolution: {integrity: sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==} + '@opentelemetry/redis-common@0.38.3': + resolution: {integrity: sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw==} engines: {node: ^18.19.0 || >=20.6.0} '@opentelemetry/resources@2.7.0': @@ -3901,8 +3891,8 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 - '@oxc-project/types@0.124.0': - resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==} + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} @@ -4279,103 +4269,103 @@ packages: '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 - '@rolldown/binding-android-arm64@1.0.0-rc.15': - resolution: {integrity: sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==} + '@rolldown/binding-android-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.15': - resolution: {integrity: sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.15': - resolution: {integrity: sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==} + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.15': - resolution: {integrity: sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': - resolution: {integrity: sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': - resolution: {integrity: sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': - resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': - resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': - resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==} + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': - resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': - resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': - resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': - resolution: {integrity: sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==} - engines: {node: '>=14.0.0'} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': - resolution: {integrity: sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': - resolution: {integrity: sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-rc.15': - resolution: {integrity: sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==} + '@rolldown/pluginutils@1.0.0-rc.17': + resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} @@ -4692,86 +4682,86 @@ packages: resolution: {integrity: sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==} engines: {node: '>=14'} - '@swc/core-darwin-arm64@1.15.26': - resolution: {integrity: sha512-OmcP96CFsNOwa65tamQayRcfqhNlcQ3YCWOq+0Wb+CAM4uB7kOMrXY41Gj4atthxrGhLQ9pg7Vk26iApb88idA==} + '@swc/core-darwin-arm64@1.15.30': + resolution: {integrity: sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.15.26': - resolution: {integrity: sha512-liTTTpKSv89ivIxcZ+iU1cRige9Y7JkOjVnJ2Ystzl+DsWNHqt7wLTTgm/u7gEqmmAS2JKryODLQn3q1UtFNPQ==} + '@swc/core-darwin-x64@1.15.30': + resolution: {integrity: sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.15.26': - resolution: {integrity: sha512-Y/g+m3I8CeBof5A3kWWOS6QA2HOIUytF5EeTgfwcAK+GKT/tGe7Xqo5svBtaqflU5od2zzbMTWqkinPXgRWGgA==} + '@swc/core-linux-arm-gnueabihf@1.15.30': + resolution: {integrity: sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.15.26': - resolution: {integrity: sha512-19IvwyPfBN/rz9s7qXhOTQmW0922+pjpRUUvIebu+CMM75nX6YuDzHsGx8hSmn5dS89SNaMCh1lgUuXqm++6jg==} + '@swc/core-linux-arm64-gnu@1.15.30': + resolution: {integrity: sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg==} engines: {node: '>=10'} cpu: [arm64] os: [linux] libc: [glibc] - '@swc/core-linux-arm64-musl@1.15.26': - resolution: {integrity: sha512-iNlbvTIo425rkKzDLLWFJGnFXr3myETUdIDHcjuiPNZE8b0ogmcAuilC4yEJX7FSHGbnlsoJcCT2xf4b3VJmmQ==} + '@swc/core-linux-arm64-musl@1.15.30': + resolution: {integrity: sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA==} engines: {node: '>=10'} cpu: [arm64] os: [linux] libc: [musl] - '@swc/core-linux-ppc64-gnu@1.15.26': - resolution: {integrity: sha512-AuuEOtG+YXKIjIUup4RsxYNklx6XVB3WKWfhxG6hnfPrn7vp89RNOLbbyyprgj6Sk7k9ulwGVTJElEvmBNPSCA==} + '@swc/core-linux-ppc64-gnu@1.15.30': + resolution: {integrity: sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g==} engines: {node: '>=10'} cpu: [ppc64] os: [linux] libc: [glibc] - '@swc/core-linux-s390x-gnu@1.15.26': - resolution: {integrity: sha512-JcMDWQvW1BchUyRg8E0jHiTx7CQYpUr5uDEL1dnPDECrEjBEGG2ynmJ3XX70sWXql0JagqR1t3VpANYFWdUnqA==} + '@swc/core-linux-s390x-gnu@1.15.30': + resolution: {integrity: sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g==} engines: {node: '>=10'} cpu: [s390x] os: [linux] libc: [glibc] - '@swc/core-linux-x64-gnu@1.15.26': - resolution: {integrity: sha512-FW7V7Mbpq4+PA7BiAq76LJs8MdNuUSylyuRVfQRkhIyeWadFroZ+KOPgjku8Z/fXzngxBRvsk+PGGB0t8mGcjA==} + '@swc/core-linux-x64-gnu@1.15.30': + resolution: {integrity: sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA==} engines: {node: '>=10'} cpu: [x64] os: [linux] libc: [glibc] - '@swc/core-linux-x64-musl@1.15.26': - resolution: {integrity: sha512-w8erqMHsVcdGwUfJxF6LaiTuPoKnyLOcUbhLcxiXrlLt5MLjtlgcIeUY/NWK/oPoyqkgH+/i8pOJnMTxvl83ZQ==} + '@swc/core-linux-x64-musl@1.15.30': + resolution: {integrity: sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA==} engines: {node: '>=10'} cpu: [x64] os: [linux] libc: [musl] - '@swc/core-win32-arm64-msvc@1.15.26': - resolution: {integrity: sha512-uDCWCNpUiqkbvPmsuPUTn/P7ag9SqNXD2JT/W3dUu7yZ2krzN+nmmoQ2xRX63/J6RYiHI7aT4jo7Z++lsljlPA==} + '@swc/core-win32-arm64-msvc@1.15.30': + resolution: {integrity: sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.15.26': - resolution: {integrity: sha512-2k1ax1QmmqLEnpC0uRCw7OXhBfyvdPqERBXupDasjYbChT6ZSO/uha28Bp38cw0viKIG79L27aTDkbkABsMW3w==} + '@swc/core-win32-ia32-msvc@1.15.30': + resolution: {integrity: sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.15.26': - resolution: {integrity: sha512-aUuYecSEGa4SUSdyCWaI/vk8jdseifYnsF1GZQx2+piL8GIuT/5QrVcFfmes4Iwy7FIVXxtzD063z/FfpZ7K7w==} + '@swc/core-win32-x64-msvc@1.15.30': + resolution: {integrity: sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.15.26': - resolution: {integrity: sha512-tglZGyx8N5PC+x1Nd/JrZxqpqlcZoSuG9gTDKO6AuFToFiVB3uS8HvbKFuO7g3lJzvFf9riAb94xs9HU2UhAHQ==} + '@swc/core@1.15.30': + resolution: {integrity: sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -4782,8 +4772,8 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/helpers@0.5.17': - resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + '@swc/helpers@0.5.21': + resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==} '@swc/types@0.1.26': resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==} @@ -4792,69 +4782,69 @@ packages: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} - '@tailwindcss/node@4.2.2': - resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} + '@tailwindcss/node@4.2.4': + resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} - '@tailwindcss/oxide-android-arm64@4.2.2': - resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} + '@tailwindcss/oxide-android-arm64@4.2.4': + resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.2.2': - resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} + '@tailwindcss/oxide-darwin-arm64@4.2.4': + resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.2.2': - resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} + '@tailwindcss/oxide-darwin-x64@4.2.4': + resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.2.2': - resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} + '@tailwindcss/oxide-freebsd-x64@4.2.4': + resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': - resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': - resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': - resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [musl] - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': - resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-x64-musl@4.2.2': - resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [musl] - '@tailwindcss/oxide-wasm32-wasi@4.2.2': - resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -4865,24 +4855,24 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': - resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': - resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.2.2': - resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} + '@tailwindcss/oxide@4.2.4': + resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} engines: {node: '>= 20'} - '@tailwindcss/vite@4.2.2': - resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} + '@tailwindcss/vite@4.2.4': + resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 @@ -5416,63 +5406,63 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.58.2': - resolution: {integrity: sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==} + '@typescript-eslint/eslint-plugin@8.59.0': + resolution: {integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.58.2 + '@typescript-eslint/parser': ^8.59.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.58.2': - resolution: {integrity: sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==} + '@typescript-eslint/parser@8.59.0': + resolution: {integrity: sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.58.2': - resolution: {integrity: sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==} + '@typescript-eslint/project-service@8.59.0': + resolution: {integrity: sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.58.2': - resolution: {integrity: sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==} + '@typescript-eslint/scope-manager@8.59.0': + resolution: {integrity: sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.58.2': - resolution: {integrity: sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==} + '@typescript-eslint/tsconfig-utils@8.59.0': + resolution: {integrity: sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.58.2': - resolution: {integrity: sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==} + '@typescript-eslint/type-utils@8.59.0': + resolution: {integrity: sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.58.2': - resolution: {integrity: sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==} + '@typescript-eslint/types@8.59.0': + resolution: {integrity: sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.58.2': - resolution: {integrity: sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==} + '@typescript-eslint/typescript-estree@8.59.0': + resolution: {integrity: sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.58.2': - resolution: {integrity: sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==} + '@typescript-eslint/utils@8.59.0': + resolution: {integrity: sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.58.2': - resolution: {integrity: sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==} + '@typescript-eslint/visitor-keys@8.59.0': + resolution: {integrity: sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': @@ -5491,11 +5481,11 @@ packages: '@vitest/browser': optional: true - '@vitest/coverage-v8@4.1.4': - resolution: {integrity: sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==} + '@vitest/coverage-v8@4.1.5': + resolution: {integrity: sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==} peerDependencies: - '@vitest/browser': 4.1.4 - vitest: 4.1.4 + '@vitest/browser': 4.1.5 + vitest: 4.1.5 peerDependenciesMeta: '@vitest/browser': optional: true @@ -5503,8 +5493,8 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/expect@4.1.4': - resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==} + '@vitest/expect@4.1.5': + resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} '@vitest/mocker@3.2.4': resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} @@ -5517,8 +5507,8 @@ packages: vite: optional: true - '@vitest/mocker@4.1.4': - resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==} + '@vitest/mocker@4.1.5': + resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -5531,32 +5521,32 @@ packages: '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/pretty-format@4.1.4': - resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} + '@vitest/pretty-format@4.1.5': + resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} '@vitest/runner@3.2.4': resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/runner@4.1.4': - resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==} + '@vitest/runner@4.1.5': + resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} '@vitest/snapshot@3.2.4': resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/snapshot@4.1.4': - resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==} + '@vitest/snapshot@4.1.5': + resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/spy@4.1.4': - resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==} + '@vitest/spy@4.1.5': + resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - '@vitest/utils@4.1.4': - resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} + '@vitest/utils@4.1.5': + resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -6002,8 +5992,8 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bits-ui@2.16.3: - resolution: {integrity: sha512-5hJ5dEhf5yPzkRFcxzgQHScGodeo0gK0MUUXrdLlRHWaBOBGZiacWLG96j/wwFatKwZvouw7q+sn14i0fx3RIg==} + bits-ui@2.18.0: + resolution: {integrity: sha512-GLOBZRVy3hxNHIQ2MpD/+5aK9KcBFZRhUJtZ1UDABXdlVR4K6zFpgt4T+Rwuhf2sQzlc6yK1q/DprHPjwT4Pjw==} engines: {node: '>=20'} peerDependencies: '@internationalized/date': ^3.8.1 @@ -6077,8 +6067,8 @@ packages: resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} engines: {node: '>=18.20'} - bullmq@5.74.1: - resolution: {integrity: sha512-GfJEos2zoOGM9xqkB7VZouwwFuejKFqm667cBcmbBekJXKqqXWk4QYP3Uy2pzgUwCbg1cR7GgGmGczM7fnhWSA==} + bullmq@5.76.1: + resolution: {integrity: sha512-9Xc5Pj4Ho0clodowuuUSydMOR4gCn+YxYYVQXbGJycO8r4jyxsff1rZl3CKj3k50c/B42gDDNTLJH6uwb3dYmg==} bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} @@ -7220,8 +7210,8 @@ packages: resolution: {integrity: sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==} engines: {node: '>=10.2.0'} - enhanced-resolve@5.20.1: - resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} + enhanced-resolve@5.21.0: + resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==} engines: {node: '>=10.13.0'} entities@2.2.0: @@ -7264,8 +7254,8 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-module-lexer@2.0.0: - resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} @@ -7359,8 +7349,8 @@ packages: eslint-config-prettier: optional: true - eslint-plugin-svelte@3.17.0: - resolution: {integrity: sha512-sF6wgd5FLS2P8CCaOy2HdYYYEcZ6TwL251dLHUkNmtLnWECk1Dwc+j6VeulmmnFxr7Xs0WNtjweOA+bJ0PnaFw==} + eslint-plugin-svelte@3.17.1: + resolution: {integrity: sha512-NyiXHtS3Ni7e532RBwS9OXlMKDIrENg3gY+/+ODjZzQx2xhU3NlJ+nIl1a93iUUQeiJL3lS8KLmY+W8hklzweQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.1 || ^9.0.0 || ^10.0.0 @@ -7520,17 +7510,17 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - exiftool-vendored.exe@13.53.0: - resolution: {integrity: sha512-CX8w1iVDOdt6iitqoOmUCWLYVmfBVmd59htXGpns/+CItu8LBAT9qVHdBP+Jl0abZyCcDrZf0eaLsfXb9mZOcQ==} + exiftool-vendored.exe@13.57.0: + resolution: {integrity: sha512-9ENCWzUiFy6F/O4jSX50ygSGrTOtvoqJFWE0zAOl7VL/EFooLWNF0LkaNSox0ibbIsQz5rWSKi0TPlEbF4qBIw==} os: [win32] - exiftool-vendored.pl@13.53.0: - resolution: {integrity: sha512-D/3yJymCPeMQPtQA9Q8ou/+vvEeQcTjrNt2jT7GS2A9tE0s0NiMNVc62HaKdwm5reQXQRbPrnp56sNxWpNCHKA==} + exiftool-vendored.pl@13.57.0: + resolution: {integrity: sha512-7HYhrIygbfKD+E/sUF9L8YEs7qCEFLFWKoeevJllnD9jxVvZ09tfFsjbBPQ7SAgGwWSHW//SVULFHLgrO8JsBw==} os: ['!win32'] hasBin: true - exiftool-vendored@35.15.1: - resolution: {integrity: sha512-ox+pcW9m52MGeXMMuZjbdaKgeha9WmWPE7HhVw6GNZ607a9Hx2HyiAUDQm+XdAzv6Y34sahLReCeJRmS9F70Ww==} + exiftool-vendored@35.18.0: + resolution: {integrity: sha512-QBtYNz71VAwZWqxFP1iWWS9qwOx3b9MSpk0GAMyIfS8gupUWsOyhn4i2WrB4OlRSQPuQ2YeKSw2fygi6E0LGiw==} engines: {node: '>=20.0.0'} expect-type@1.3.0: @@ -7558,8 +7548,8 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - fabric@7.2.0: - resolution: {integrity: sha512-XSYmSqSMrlbCg+/j7/uU/PFeZuA5hHRDp7sGbDlMvz/T6BHt2MQSOYtz/AIdr+kmReA1s5jTzHJ8AjHwYUcmfQ==} + fabric@7.3.1: + resolution: {integrity: sha512-RoLAQzUX+/3RNMYKliuN0P2HXdSDEGzyjS7FnmEbo3nhb8LFh59T+l3f6ApIu5LT4YB49YfMNrEajeIbutmD7Q==} engines: {node: '>=20.0.0'} factory.ts@1.4.2: @@ -8883,9 +8873,6 @@ packages: lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} - lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} @@ -8972,8 +8959,8 @@ packages: resolution: {integrity: sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==} engines: {node: ^20.17.0 || >=22.9.0} - maplibre-gl@5.23.0: - resolution: {integrity: sha512-aou8YBNFS8uVtDWFWt0W/6oorfl18wt+oIA8fnXk1kivjkbtXi9gGrQvflTpwrR3hG13aWdIdbYWeN0NFMV7ag==} + maplibre-gl@5.24.0: + resolution: {integrity: sha512-ALyFxgtd5R+65UqZ/++lOqwWcC0SNho9c27fYSyLmG7AfnAul2o46F05aDJGPbFU57wos9dgcIySHs0Xe6ia3A==} engines: {node: '>=16.14.0', npm: '>=8.1.0'} mark.js@8.11.1: @@ -8996,8 +8983,8 @@ packages: engines: {node: '>= 20'} hasBin: true - marked@17.0.5: - resolution: {integrity: sha512-6hLvc0/JEbRjRgzI6wnT2P1XuM1/RrrDEX0kPt0N7jGm1133g6X7DlxFasUIx+72aKAr904GTxhSLDrd5DIlZg==} + marked@17.0.6: + resolution: {integrity: sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==} engines: {node: '>= 20'} hasBin: true @@ -9870,9 +9857,6 @@ packages: path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} - path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} @@ -10442,8 +10426,8 @@ packages: peerDependencies: postcss: ^8.4.31 - postcss@8.5.10: - resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} + postcss@8.5.12: + resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -10917,8 +10901,8 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - rolldown@1.0.0-rc.15: - resolution: {integrity: sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==} + rolldown@1.0.0-rc.17: + resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -11157,8 +11141,8 @@ packages: simple-get@3.1.1: resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==} - simple-icons@16.16.0: - resolution: {integrity: sha512-H+Z29a0TrCw6mrG42V2aqHQaKdJCT87x5aojLlPiIXOf1lpMqnKFAR/jP5xkI5hLrVTCBWs33e9sOtyNWqCx1A==} + simple-icons@16.17.0: + resolution: {integrity: sha512-bRrGtzM6NLgxeMWmRcfDdrRksECk101lRrCn6jjj6qzUB6lQ+E5smnr52rqS1kLPmbLpS/g6iF463j50M4BT7A==} engines: {node: '>=0.12.18'} sirv@2.0.4: @@ -11537,8 +11521,8 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - swagger-ui-dist@5.31.0: - resolution: {integrity: sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==} + swagger-ui-dist@5.32.4: + resolution: {integrity: sha512-0AADFFQNJzExEN49SrD/34Nn9cxNxVLiydYl2MBwSZFPVXNkVwC/EFAjoezGGqE8oDegiDC+p47t8lKObCinMQ==} swr@2.3.8: resolution: {integrity: sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==} @@ -11604,9 +11588,6 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - tailwindcss@4.2.2: - resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} - tailwindcss@4.2.4: resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} @@ -11898,8 +11879,8 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typescript-eslint@8.58.2: - resolution: {integrity: sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==} + typescript-eslint@8.59.0: + resolution: {integrity: sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -12110,6 +12091,10 @@ packages: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true + uuid@14.0.0: + resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -12200,8 +12185,8 @@ packages: yaml: optional: true - vite@8.0.8: - resolution: {integrity: sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==} + vite@8.0.10: + resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -12285,20 +12270,20 @@ packages: jsdom: optional: true - vitest@4.1.4: - resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==} + vitest@4.1.5: + resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.4 - '@vitest/browser-preview': 4.1.4 - '@vitest/browser-webdriverio': 4.1.4 - '@vitest/coverage-istanbul': 4.1.4 - '@vitest/coverage-v8': 4.1.4 - '@vitest/ui': 4.1.4 + '@vitest/browser-playwright': 4.1.5 + '@vitest/browser-preview': 4.1.5 + '@vitest/browser-webdriverio': 4.1.5 + '@vitest/coverage-istanbul': 4.1.5 + '@vitest/coverage-v8': 4.1.5 + '@vitest/ui': 4.1.5 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -13763,261 +13748,261 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-alpha-function@1.0.1(postcss@8.5.10)': + '@csstools/postcss-alpha-function@1.0.1(postcss@8.5.12)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - '@csstools/postcss-cascade-layers@5.0.2(postcss@8.5.10)': + '@csstools/postcss-cascade-layers@5.0.2(postcss@8.5.12)': dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 - '@csstools/postcss-color-function-display-p3-linear@1.0.1(postcss@8.5.10)': + '@csstools/postcss-color-function-display-p3-linear@1.0.1(postcss@8.5.12)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - '@csstools/postcss-color-function@4.0.12(postcss@8.5.10)': + '@csstools/postcss-color-function@4.0.12(postcss@8.5.12)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - '@csstools/postcss-color-mix-function@3.0.12(postcss@8.5.10)': + '@csstools/postcss-color-mix-function@3.0.12(postcss@8.5.12)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - '@csstools/postcss-color-mix-variadic-function-arguments@1.0.2(postcss@8.5.10)': + '@csstools/postcss-color-mix-variadic-function-arguments@1.0.2(postcss@8.5.12)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - '@csstools/postcss-content-alt-text@2.0.8(postcss@8.5.10)': + '@csstools/postcss-content-alt-text@2.0.8(postcss@8.5.12)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - '@csstools/postcss-contrast-color-function@2.0.12(postcss@8.5.10)': + '@csstools/postcss-contrast-color-function@2.0.12(postcss@8.5.12)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - '@csstools/postcss-exponential-functions@2.0.9(postcss@8.5.10)': + '@csstools/postcss-exponential-functions@2.0.9(postcss@8.5.12)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.10)': + '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.12)': dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 postcss-value-parser: 4.2.0 - '@csstools/postcss-gamut-mapping@2.0.11(postcss@8.5.10)': + '@csstools/postcss-gamut-mapping@2.0.11(postcss@8.5.12)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-gradients-interpolation-method@5.0.12(postcss@8.5.10)': + '@csstools/postcss-gradients-interpolation-method@5.0.12(postcss@8.5.12)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - '@csstools/postcss-hwb-function@4.0.12(postcss@8.5.10)': + '@csstools/postcss-hwb-function@4.0.12(postcss@8.5.12)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - '@csstools/postcss-ic-unit@4.0.4(postcss@8.5.10)': + '@csstools/postcss-ic-unit@4.0.4(postcss@8.5.12)': dependencies: - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 postcss-value-parser: 4.2.0 - '@csstools/postcss-initial@2.0.1(postcss@8.5.10)': + '@csstools/postcss-initial@2.0.1(postcss@8.5.12)': dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-is-pseudo-class@5.0.3(postcss@8.5.10)': + '@csstools/postcss-is-pseudo-class@5.0.3(postcss@8.5.12)': dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 - '@csstools/postcss-light-dark-function@2.0.11(postcss@8.5.10)': + '@csstools/postcss-light-dark-function@2.0.11(postcss@8.5.12)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.10)': + '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.12)': dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.10)': + '@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.12)': dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.10)': + '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.12)': dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-logical-resize@3.0.0(postcss@8.5.10)': + '@csstools/postcss-logical-resize@3.0.0(postcss@8.5.12)': dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - '@csstools/postcss-logical-viewport-units@3.0.4(postcss@8.5.10)': + '@csstools/postcss-logical-viewport-units@3.0.4(postcss@8.5.12)': dependencies: '@csstools/css-tokenizer': 3.0.4 - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - '@csstools/postcss-media-minmax@2.0.9(postcss@8.5.10)': + '@csstools/postcss-media-minmax@2.0.9(postcss@8.5.12)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5(postcss@8.5.10)': + '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5(postcss@8.5.12)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-nested-calc@4.0.0(postcss@8.5.10)': + '@csstools/postcss-nested-calc@4.0.0(postcss@8.5.12)': dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 postcss-value-parser: 4.2.0 - '@csstools/postcss-normalize-display-values@4.0.0(postcss@8.5.10)': + '@csstools/postcss-normalize-display-values@4.0.0(postcss@8.5.12)': dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - '@csstools/postcss-oklab-function@4.0.12(postcss@8.5.10)': + '@csstools/postcss-oklab-function@4.0.12(postcss@8.5.12)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - '@csstools/postcss-position-area-property@1.0.0(postcss@8.5.10)': + '@csstools/postcss-position-area-property@1.0.0(postcss@8.5.12)': dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-progressive-custom-properties@4.2.1(postcss@8.5.10)': + '@csstools/postcss-progressive-custom-properties@4.2.1(postcss@8.5.12)': dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - '@csstools/postcss-random-function@2.0.1(postcss@8.5.10)': + '@csstools/postcss-random-function@2.0.1(postcss@8.5.12)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-relative-color-syntax@3.0.12(postcss@8.5.10)': + '@csstools/postcss-relative-color-syntax@3.0.12(postcss@8.5.12)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.10)': + '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.12)': dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 - '@csstools/postcss-sign-functions@1.1.4(postcss@8.5.10)': + '@csstools/postcss-sign-functions@1.1.4(postcss@8.5.12)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-stepped-value-functions@4.0.9(postcss@8.5.10)': + '@csstools/postcss-stepped-value-functions@4.0.9(postcss@8.5.12)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-system-ui-font-family@1.0.0(postcss@8.5.10)': + '@csstools/postcss-system-ui-font-family@1.0.0(postcss@8.5.12)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-text-decoration-shorthand@4.0.3(postcss@8.5.10)': + '@csstools/postcss-text-decoration-shorthand@4.0.3(postcss@8.5.12)': dependencies: '@csstools/color-helpers': 5.1.0 - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - '@csstools/postcss-trigonometric-functions@4.0.9(postcss@8.5.10)': + '@csstools/postcss-trigonometric-functions@4.0.9(postcss@8.5.12)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.10 + postcss: 8.5.12 - '@csstools/postcss-unset-value@4.0.0(postcss@8.5.10)': + '@csstools/postcss-unset-value@4.0.0(postcss@8.5.12)': dependencies: - postcss: 8.5.10 + postcss: 8.5.12 '@csstools/selector-resolve-nested@3.1.0(postcss-selector-parser@7.1.1)': dependencies: @@ -14027,9 +14012,9 @@ snapshots: dependencies: postcss-selector-parser: 7.1.1 - '@csstools/utilities@2.0.0(postcss@8.5.10)': + '@csstools/utilities@2.0.0(postcss@8.5.12)': dependencies: - postcss: 8.5.10 + postcss: 8.5.12 '@discoveryjs/json-ext@0.5.7': {} @@ -14097,14 +14082,14 @@ snapshots: copy-webpack-plugin: 11.0.0(webpack@5.106.2) css-loader: 6.11.0(webpack@5.106.2) css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.106.2) - cssnano: 6.1.2(postcss@8.5.10) + cssnano: 6.1.2(postcss@8.5.12) file-loader: 6.2.0(webpack@5.106.2) html-minifier-terser: 7.2.0 mini-css-extract-plugin: 2.9.4(webpack@5.106.2) null-loader: 4.0.1(webpack@5.106.2) - postcss: 8.5.10 - postcss-loader: 7.3.4(postcss@8.5.10)(typescript@6.0.3)(webpack@5.106.2) - postcss-preset-env: 10.5.0(postcss@8.5.10) + postcss: 8.5.12 + postcss-loader: 7.3.4(postcss@8.5.12)(typescript@6.0.3)(webpack@5.106.2) + postcss-preset-env: 10.5.0(postcss@8.5.12) terser-webpack-plugin: 5.4.0(webpack@5.106.2) tslib: 2.8.1 url-loader: 4.1.1(file-loader@6.2.0(webpack@5.106.2))(webpack@5.106.2) @@ -14190,9 +14175,9 @@ snapshots: '@docusaurus/cssnano-preset@3.10.0': dependencies: - cssnano-preset-advanced: 6.1.2(postcss@8.5.10) - postcss: 8.5.10 - postcss-sort-media-queries: 5.2.0(postcss@8.5.10) + cssnano-preset-advanced: 6.1.2(postcss@8.5.12) + postcss: 8.5.12 + postcss-sort-media-queries: 5.2.0(postcss@8.5.12) tslib: 2.8.1 '@docusaurus/logger@3.10.0': @@ -14626,7 +14611,7 @@ snapshots: infima: 0.2.0-alpha.45 lodash: 4.18.1 nprogress: 0.2.0 - postcss: 8.5.10 + postcss: 8.5.12 prism-react-renderer: 2.4.1(react@19.2.5) prismjs: 1.30.0 react: 19.2.5 @@ -14841,13 +14826,13 @@ snapshots: - uglify-js - webpack-cli - '@emnapi/core@1.9.2': + '@emnapi/core@1.10.0': dependencies: '@emnapi/wasi-threads': 1.2.1 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.9.2': + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true @@ -15323,7 +15308,7 @@ snapshots: '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.9.2 + '@emnapi/runtime': 1.10.0 optional: true '@img/sharp-win32-arm64@0.34.5': @@ -15337,7 +15322,7 @@ snapshots: '@immich/justified-layout-wasm@0.4.3': {} - '@immich/sql-tools@0.5.1': + '@immich/sql-tools@0.5.2': dependencies: commander: 14.0.3 graph-data-structure: 4.5.0 @@ -15349,18 +15334,18 @@ snapshots: '@immich/svelte-markdown-preprocess@0.4.1(svelte@5.55.2)': dependencies: front-matter: 4.0.2 - marked: 17.0.5 + marked: 17.0.6 node-emoji: 2.2.0 svelte: 5.55.2 - '@immich/ui@0.76.0(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)': + '@immich/ui@0.76.2(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)': dependencies: '@immich/svelte-markdown-preprocess': 0.4.1(svelte@5.55.2) - '@internationalized/date': 3.12.0 + '@internationalized/date': 3.12.1 '@mdi/js': 7.4.47 - bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) + bits-ui: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) luxon: 3.7.2 - simple-icons: 16.16.0 + simple-icons: 16.17.0 svelte: 5.55.2 svelte-highlight: 7.9.0 tailwind-merge: 3.5.0 @@ -15509,9 +15494,9 @@ snapshots: optionalDependencies: '@types/node': 24.12.2 - '@internationalized/date@3.12.0': + '@internationalized/date@3.12.1': dependencies: - '@swc/helpers': 0.5.17 + '@swc/helpers': 0.5.21 '@ioredis/commands@1.5.1': {} @@ -15723,8 +15708,8 @@ snapshots: '@koddsson/eslint-plugin-tscompat@0.2.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: '@mdn/browser-compat-data': 6.1.5 - '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) browserslist: 4.28.2 transitivePeerDependencies: - eslint @@ -15822,7 +15807,7 @@ snapshots: rw: 1.3.3 tinyqueue: 3.0.0 - '@maplibre/mlt@1.1.8': + '@maplibre/mlt@1.1.9': dependencies: '@mapbox/point-geometry': 1.1.0 @@ -15910,10 +15895,10 @@ snapshots: '@namnode/store@0.1.0': {} - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 '@tybys/wasm-util': 0.10.1 optional: true @@ -15923,15 +15908,15 @@ snapshots: '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 - '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(bullmq@5.74.1)': + '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(bullmq@5.76.1)': dependencies: '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - bullmq: 5.74.1 + bullmq: 5.76.1 tslib: 2.8.1 - '@nestjs/cli@11.0.21(@swc/core@1.15.26(@swc/helpers@0.5.17))(@types/node@24.12.2)(esbuild@0.28.0)(prettier@3.8.3)': + '@nestjs/cli@11.0.21(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@24.12.2)(esbuild@0.28.0)(prettier@3.8.3)': dependencies: '@angular-devkit/core': 19.2.24(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) @@ -15942,17 +15927,17 @@ snapshots: chokidar: 4.0.3 cli-table3: 0.6.5 commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.26(@swc/helpers@0.5.17))(esbuild@0.28.0)) + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)) glob: 13.0.6 node-emoji: 1.11.0 ora: 5.4.1 tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.2.0 typescript: 5.9.3 - webpack: 5.106.0(@swc/core@1.15.26(@swc/helpers@0.5.17))(esbuild@0.28.0) + webpack: 5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0) webpack-node-externals: 3.0.0 optionalDependencies: - '@swc/core': 1.15.26(@swc/helpers@0.5.17) + '@swc/core': 1.15.30(@swc/helpers@0.5.21) transitivePeerDependencies: - '@types/node' - esbuild @@ -15989,7 +15974,7 @@ snapshots: '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/mapped-types@2.1.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(reflect-metadata@0.2.2)': + '@nestjs/mapped-types@2.1.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(reflect-metadata@0.2.2)': dependencies: '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) reflect-metadata: 0.2.2 @@ -16052,17 +16037,17 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@11.2.6(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2)': + '@nestjs/swagger@11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2)': dependencies: '@microsoft/tsdoc': 0.16.0 '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/mapped-types': 2.1.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(reflect-metadata@0.2.2) + '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(reflect-metadata@0.2.2) js-yaml: 4.1.1 - lodash: 4.17.23 - path-to-regexp: 8.3.0 + lodash: 4.18.1 + path-to-regexp: 8.4.2 reflect-metadata: 0.2.2 - swagger-ui-dist: 5.31.0 + swagger-ui-dist: 5.32.4 optionalDependencies: class-transformer: 0.5.1 @@ -16122,10 +16107,6 @@ snapshots: '@oazapfts/runtime@1.2.0': {} - '@opentelemetry/api-logs@0.214.0': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs@0.215.0': dependencies: '@opentelemetry/api': 1.9.1 @@ -16270,28 +16251,28 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-ioredis@0.62.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-ioredis@0.63.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) - '@opentelemetry/redis-common': 0.38.2 + '@opentelemetry/instrumentation': 0.215.0(@opentelemetry/api@1.9.1) + '@opentelemetry/redis-common': 0.38.3 '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-nestjs-core@0.60.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-nestjs-core@0.61.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.215.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-pg@0.66.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-pg@0.67.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.215.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.1) '@types/pg': 8.15.6 @@ -16299,15 +16280,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.214.0 - import-in-the-middle: 3.0.0 - require-in-the-middle: 8.0.1 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation@0.215.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -16352,7 +16324,7 @@ snapshots: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/redis-common@0.38.2': {} + '@opentelemetry/redis-common@0.38.3': {} '@opentelemetry/resources@2.7.0(@opentelemetry/api@1.9.1)': dependencies: @@ -16426,7 +16398,7 @@ snapshots: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@oxc-project/types@0.124.0': {} + '@oxc-project/types@0.127.0': {} '@paralleldrive/cuid2@2.3.1': dependencies: @@ -16706,56 +16678,56 @@ snapshots: '@codemirror/state': 6.6.0 '@codemirror/view': 6.41.1 - '@rolldown/binding-android-arm64@1.0.0-rc.15': + '@rolldown/binding-android-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.15': + '@rolldown/binding-darwin-x64@1.0.0-rc.17': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': optional: true - '@rolldown/pluginutils@1.0.0-rc.15': {} + '@rolldown/pluginutils@1.0.0-rc.17': {} '@rollup/pluginutils@5.3.0(rollup@4.55.1)': dependencies: @@ -16896,29 +16868,29 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))': dependencies: - '@sveltejs/kit': 2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/kit': 2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@sveltejs/enhanced-img@0.10.4(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(rollup@4.55.1)(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@sveltejs/enhanced-img@0.10.4(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(rollup@4.55.1)(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@sveltejs/vite-plugin-svelte': 7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte': 7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) magic-string: 0.30.21 sharp: 0.34.5 svelte: 5.55.2 svelte-parse-markup: 0.1.5(svelte@5.55.2) - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-imagetools: 9.0.3(rollup@4.55.1) zimmerframe: 1.1.4 transitivePeerDependencies: - rollup - supports-color - '@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@sveltejs/vite-plugin-svelte': 7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte': 7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 0.6.0 @@ -16930,19 +16902,19 @@ snapshots: set-cookie-parser: 3.1.0 sirv: 3.0.2 svelte: 5.55.2 - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) optionalDependencies: '@opentelemetry/api': 1.9.1 typescript: 6.0.3 - '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 svelte: 5.55.2 - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vitefu: 1.1.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vitefu: 1.1.2(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.5)': dependencies: @@ -17037,64 +17009,64 @@ snapshots: - supports-color - typescript - '@swc/core-darwin-arm64@1.15.26': + '@swc/core-darwin-arm64@1.15.30': optional: true - '@swc/core-darwin-x64@1.15.26': + '@swc/core-darwin-x64@1.15.30': optional: true - '@swc/core-linux-arm-gnueabihf@1.15.26': + '@swc/core-linux-arm-gnueabihf@1.15.30': optional: true - '@swc/core-linux-arm64-gnu@1.15.26': + '@swc/core-linux-arm64-gnu@1.15.30': optional: true - '@swc/core-linux-arm64-musl@1.15.26': + '@swc/core-linux-arm64-musl@1.15.30': optional: true - '@swc/core-linux-ppc64-gnu@1.15.26': + '@swc/core-linux-ppc64-gnu@1.15.30': optional: true - '@swc/core-linux-s390x-gnu@1.15.26': + '@swc/core-linux-s390x-gnu@1.15.30': optional: true - '@swc/core-linux-x64-gnu@1.15.26': + '@swc/core-linux-x64-gnu@1.15.30': optional: true - '@swc/core-linux-x64-musl@1.15.26': + '@swc/core-linux-x64-musl@1.15.30': optional: true - '@swc/core-win32-arm64-msvc@1.15.26': + '@swc/core-win32-arm64-msvc@1.15.30': optional: true - '@swc/core-win32-ia32-msvc@1.15.26': + '@swc/core-win32-ia32-msvc@1.15.30': optional: true - '@swc/core-win32-x64-msvc@1.15.26': + '@swc/core-win32-x64-msvc@1.15.30': optional: true - '@swc/core@1.15.26(@swc/helpers@0.5.17)': + '@swc/core@1.15.30(@swc/helpers@0.5.21)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.26 optionalDependencies: - '@swc/core-darwin-arm64': 1.15.26 - '@swc/core-darwin-x64': 1.15.26 - '@swc/core-linux-arm-gnueabihf': 1.15.26 - '@swc/core-linux-arm64-gnu': 1.15.26 - '@swc/core-linux-arm64-musl': 1.15.26 - '@swc/core-linux-ppc64-gnu': 1.15.26 - '@swc/core-linux-s390x-gnu': 1.15.26 - '@swc/core-linux-x64-gnu': 1.15.26 - '@swc/core-linux-x64-musl': 1.15.26 - '@swc/core-win32-arm64-msvc': 1.15.26 - '@swc/core-win32-ia32-msvc': 1.15.26 - '@swc/core-win32-x64-msvc': 1.15.26 - '@swc/helpers': 0.5.17 + '@swc/core-darwin-arm64': 1.15.30 + '@swc/core-darwin-x64': 1.15.30 + '@swc/core-linux-arm-gnueabihf': 1.15.30 + '@swc/core-linux-arm64-gnu': 1.15.30 + '@swc/core-linux-arm64-musl': 1.15.30 + '@swc/core-linux-ppc64-gnu': 1.15.30 + '@swc/core-linux-s390x-gnu': 1.15.30 + '@swc/core-linux-x64-gnu': 1.15.30 + '@swc/core-linux-x64-musl': 1.15.30 + '@swc/core-win32-arm64-msvc': 1.15.30 + '@swc/core-win32-ia32-msvc': 1.15.30 + '@swc/core-win32-x64-msvc': 1.15.30 + '@swc/helpers': 0.5.21 '@swc/counter@0.1.3': {} - '@swc/helpers@0.5.17': + '@swc/helpers@0.5.21': dependencies: tslib: 2.8.1 @@ -17106,73 +17078,73 @@ snapshots: dependencies: defer-to-connect: 2.0.1 - '@tailwindcss/node@4.2.2': + '@tailwindcss/node@4.2.4': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.20.1 + enhanced-resolve: 5.21.0 jiti: 2.6.1 lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.2.2 + tailwindcss: 4.2.4 - '@tailwindcss/oxide-android-arm64@4.2.2': + '@tailwindcss/oxide-android-arm64@4.2.4': optional: true - '@tailwindcss/oxide-darwin-arm64@4.2.2': + '@tailwindcss/oxide-darwin-arm64@4.2.4': optional: true - '@tailwindcss/oxide-darwin-x64@4.2.2': + '@tailwindcss/oxide-darwin-x64@4.2.4': optional: true - '@tailwindcss/oxide-freebsd-x64@4.2.2': + '@tailwindcss/oxide-freebsd-x64@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.2.2': + '@tailwindcss/oxide-linux-x64-musl@4.2.4': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.2.2': + '@tailwindcss/oxide-wasm32-wasi@4.2.4': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': optional: true - '@tailwindcss/oxide@4.2.2': + '@tailwindcss/oxide@4.2.4': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-x64': 4.2.2 - '@tailwindcss/oxide-freebsd-x64': 4.2.2 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-x64-musl': 4.2.2 - '@tailwindcss/oxide-wasm32-wasi': 4.2.2 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 + '@tailwindcss/oxide-android-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-x64': 4.2.4 + '@tailwindcss/oxide-freebsd-x64': 4.2.4 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-x64-musl': 4.2.4 + '@tailwindcss/oxide-wasm32-wasi': 4.2.4 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 - '@tailwindcss/vite@4.2.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@tailwindcss/vite@4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@tailwindcss/node': 4.2.2 - '@tailwindcss/oxide': 4.2.2 - tailwindcss: 4.2.2 - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + '@tailwindcss/node': 4.2.4 + '@tailwindcss/oxide': 4.2.4 + tailwindcss: 4.2.4 + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) '@testing-library/dom@10.4.1': dependencies: @@ -17198,14 +17170,14 @@ snapshots: dependencies: svelte: 5.55.2 - '@testing-library/svelte@5.3.1(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)': + '@testing-library/svelte@5.3.1(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5)': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/svelte-core': 1.0.0(svelte@5.55.2) svelte: 5.55.2 optionalDependencies: - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vitest: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': dependencies: @@ -17822,14 +17794,14 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.58.2 - '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.58.2 + '@typescript-eslint/parser': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/type-utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.0 eslint: 10.2.1(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 @@ -17838,41 +17810,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.58.2 - '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.58.2 + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.0 debug: 4.4.3 eslint: 10.2.1(jiti@2.6.1) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.58.2(typescript@6.0.3)': + '@typescript-eslint/project-service@8.59.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.3) - '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) + '@typescript-eslint/types': 8.59.0 debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.58.2': + '@typescript-eslint/scope-manager@8.59.0': dependencies: - '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/visitor-keys': 8.58.2 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/visitor-keys': 8.59.0 - '@typescript-eslint/tsconfig-utils@8.58.2(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.59.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3) - '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) debug: 4.4.3 eslint: 10.2.1(jiti@2.6.1) ts-api-utils: 2.5.0(typescript@6.0.3) @@ -17880,14 +17852,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.58.2': {} + '@typescript-eslint/types@8.59.0': {} - '@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.59.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.58.2(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.3) - '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/visitor-keys': 8.58.2 + '@typescript-eslint/project-service': 8.59.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/visitor-keys': 8.59.0 debug: 4.4.3 minimatch: 10.2.5 semver: 7.7.4 @@ -17897,20 +17869,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.58.2 - '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) eslint: 10.2.1(jiti@2.6.1) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.58.2': + '@typescript-eslint/visitor-keys@8.59.0': dependencies: - '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/types': 8.59.0 eslint-visitor-keys: 5.0.1 '@ungap/structured-clone@1.3.0': {} @@ -17936,10 +17908,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.1.4(vitest@4.1.4)': + '@vitest/coverage-v8@4.1.5(vitest@4.1.5)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.4 + '@vitest/utils': 4.1.5 ast-v8-to-istanbul: 1.0.0 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 @@ -17948,7 +17920,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/expect@3.2.4': dependencies: @@ -17958,12 +17930,12 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/expect@4.1.4': + '@vitest/expect@4.1.5': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 chai: 6.2.2 tinyrainbow: 3.1.0 @@ -17975,27 +17947,27 @@ snapshots: optionalDependencies: vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@vitest/spy': 4.1.4 + '@vitest/spy': 4.1.5 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@vitest/spy': 4.1.4 + '@vitest/spy': 4.1.5 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/pretty-format@4.1.4': + '@vitest/pretty-format@4.1.5': dependencies: tinyrainbow: 3.1.0 @@ -18005,9 +17977,9 @@ snapshots: pathe: 2.0.3 strip-literal: 3.1.0 - '@vitest/runner@4.1.4': + '@vitest/runner@4.1.5': dependencies: - '@vitest/utils': 4.1.4 + '@vitest/utils': 4.1.5 pathe: 2.0.3 '@vitest/snapshot@3.2.4': @@ -18016,10 +17988,10 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/snapshot@4.1.4': + '@vitest/snapshot@4.1.5': dependencies: - '@vitest/pretty-format': 4.1.4 - '@vitest/utils': 4.1.4 + '@vitest/pretty-format': 4.1.5 + '@vitest/utils': 4.1.5 magic-string: 0.30.21 pathe: 2.0.3 @@ -18027,7 +17999,7 @@ snapshots: dependencies: tinyspy: 4.0.4 - '@vitest/spy@4.1.4': {} + '@vitest/spy@4.1.5': {} '@vitest/utils@3.2.4': dependencies: @@ -18035,9 +18007,9 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 - '@vitest/utils@4.1.4': + '@vitest/utils@4.1.5': dependencies: - '@vitest/pretty-format': 4.1.4 + '@vitest/pretty-format': 4.1.5 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 @@ -18377,13 +18349,13 @@ snapshots: dependencies: immediate: 3.3.0 - autoprefixer@10.5.0(postcss@8.5.10): + autoprefixer@10.5.0(postcss@8.5.12): dependencies: browserslist: 4.28.2 caniuse-lite: 1.0.30001790 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 axobject-query@4.1.0: {} @@ -18493,15 +18465,15 @@ snapshots: binary-extensions@2.3.0: {} - bits-ui@2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2): + bits-ui@2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2): dependencies: '@floating-ui/core': 1.7.5 '@floating-ui/dom': 1.7.6 - '@internationalized/date': 3.12.0 + '@internationalized/date': 3.12.1 esm-env: 1.2.2 - runed: 0.35.1(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) + runed: 0.35.1(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) svelte: 5.55.2 - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) tabbable: 6.4.0 transitivePeerDependencies: - '@sveltejs/kit' @@ -18618,7 +18590,7 @@ snapshots: builtin-modules@5.0.0: {} - bullmq@5.74.1: + bullmq@5.76.1: dependencies: cron-parser: 4.9.0 ioredis: 5.10.1 @@ -19158,30 +19130,30 @@ snapshots: dependencies: type-fest: 1.4.0 - css-blank-pseudo@7.0.1(postcss@8.5.10): + css-blank-pseudo@7.0.1(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 - css-declaration-sorter@7.3.0(postcss@8.5.10): + css-declaration-sorter@7.3.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - css-has-pseudo@7.0.3(postcss@8.5.10): + css-has-pseudo@7.0.3(postcss@8.5.12): dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 css-loader@6.11.0(webpack@5.106.2): dependencies: - icss-utils: 5.1.0(postcss@8.5.10) - postcss: 8.5.10 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.10) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.10) - postcss-modules-scope: 3.2.1(postcss@8.5.10) - postcss-modules-values: 4.0.0(postcss@8.5.10) + icss-utils: 5.1.0(postcss@8.5.12) + postcss: 8.5.12 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.12) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.12) + postcss-modules-scope: 3.2.1(postcss@8.5.12) + postcss-modules-values: 4.0.0(postcss@8.5.12) postcss-value-parser: 4.2.0 semver: 7.7.4 optionalDependencies: @@ -19190,18 +19162,18 @@ snapshots: css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.106.2): dependencies: '@jridgewell/trace-mapping': 0.3.31 - cssnano: 6.1.2(postcss@8.5.10) + cssnano: 6.1.2(postcss@8.5.12) jest-worker: 29.7.0 - postcss: 8.5.10 + postcss: 8.5.12 schema-utils: 4.3.3 serialize-javascript: 6.0.2 webpack: 5.106.2 optionalDependencies: clean-css: 5.3.3 - css-prefers-color-scheme@10.0.0(postcss@8.5.10): + css-prefers-color-scheme@10.0.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 css-select@4.3.0: dependencies: @@ -19239,60 +19211,60 @@ snapshots: cssesc@3.0.0: {} - cssnano-preset-advanced@6.1.2(postcss@8.5.10): + cssnano-preset-advanced@6.1.2(postcss@8.5.12): dependencies: - autoprefixer: 10.5.0(postcss@8.5.10) + autoprefixer: 10.5.0(postcss@8.5.12) browserslist: 4.28.2 - cssnano-preset-default: 6.1.2(postcss@8.5.10) - postcss: 8.5.10 - postcss-discard-unused: 6.0.5(postcss@8.5.10) - postcss-merge-idents: 6.0.3(postcss@8.5.10) - postcss-reduce-idents: 6.0.3(postcss@8.5.10) - postcss-zindex: 6.0.2(postcss@8.5.10) + cssnano-preset-default: 6.1.2(postcss@8.5.12) + postcss: 8.5.12 + postcss-discard-unused: 6.0.5(postcss@8.5.12) + postcss-merge-idents: 6.0.3(postcss@8.5.12) + postcss-reduce-idents: 6.0.3(postcss@8.5.12) + postcss-zindex: 6.0.2(postcss@8.5.12) - cssnano-preset-default@6.1.2(postcss@8.5.10): + cssnano-preset-default@6.1.2(postcss@8.5.12): dependencies: browserslist: 4.28.2 - css-declaration-sorter: 7.3.0(postcss@8.5.10) - cssnano-utils: 4.0.2(postcss@8.5.10) - postcss: 8.5.10 - postcss-calc: 9.0.1(postcss@8.5.10) - postcss-colormin: 6.1.0(postcss@8.5.10) - postcss-convert-values: 6.1.0(postcss@8.5.10) - postcss-discard-comments: 6.0.2(postcss@8.5.10) - postcss-discard-duplicates: 6.0.3(postcss@8.5.10) - postcss-discard-empty: 6.0.3(postcss@8.5.10) - postcss-discard-overridden: 6.0.2(postcss@8.5.10) - postcss-merge-longhand: 6.0.5(postcss@8.5.10) - postcss-merge-rules: 6.1.1(postcss@8.5.10) - postcss-minify-font-values: 6.1.0(postcss@8.5.10) - postcss-minify-gradients: 6.0.3(postcss@8.5.10) - postcss-minify-params: 6.1.0(postcss@8.5.10) - postcss-minify-selectors: 6.0.4(postcss@8.5.10) - postcss-normalize-charset: 6.0.2(postcss@8.5.10) - postcss-normalize-display-values: 6.0.2(postcss@8.5.10) - postcss-normalize-positions: 6.0.2(postcss@8.5.10) - postcss-normalize-repeat-style: 6.0.2(postcss@8.5.10) - postcss-normalize-string: 6.0.2(postcss@8.5.10) - postcss-normalize-timing-functions: 6.0.2(postcss@8.5.10) - postcss-normalize-unicode: 6.1.0(postcss@8.5.10) - postcss-normalize-url: 6.0.2(postcss@8.5.10) - postcss-normalize-whitespace: 6.0.2(postcss@8.5.10) - postcss-ordered-values: 6.0.2(postcss@8.5.10) - postcss-reduce-initial: 6.1.0(postcss@8.5.10) - postcss-reduce-transforms: 6.0.2(postcss@8.5.10) - postcss-svgo: 6.0.3(postcss@8.5.10) - postcss-unique-selectors: 6.0.4(postcss@8.5.10) + css-declaration-sorter: 7.3.0(postcss@8.5.12) + cssnano-utils: 4.0.2(postcss@8.5.12) + postcss: 8.5.12 + postcss-calc: 9.0.1(postcss@8.5.12) + postcss-colormin: 6.1.0(postcss@8.5.12) + postcss-convert-values: 6.1.0(postcss@8.5.12) + postcss-discard-comments: 6.0.2(postcss@8.5.12) + postcss-discard-duplicates: 6.0.3(postcss@8.5.12) + postcss-discard-empty: 6.0.3(postcss@8.5.12) + postcss-discard-overridden: 6.0.2(postcss@8.5.12) + postcss-merge-longhand: 6.0.5(postcss@8.5.12) + postcss-merge-rules: 6.1.1(postcss@8.5.12) + postcss-minify-font-values: 6.1.0(postcss@8.5.12) + postcss-minify-gradients: 6.0.3(postcss@8.5.12) + postcss-minify-params: 6.1.0(postcss@8.5.12) + postcss-minify-selectors: 6.0.4(postcss@8.5.12) + postcss-normalize-charset: 6.0.2(postcss@8.5.12) + postcss-normalize-display-values: 6.0.2(postcss@8.5.12) + postcss-normalize-positions: 6.0.2(postcss@8.5.12) + postcss-normalize-repeat-style: 6.0.2(postcss@8.5.12) + postcss-normalize-string: 6.0.2(postcss@8.5.12) + postcss-normalize-timing-functions: 6.0.2(postcss@8.5.12) + postcss-normalize-unicode: 6.1.0(postcss@8.5.12) + postcss-normalize-url: 6.0.2(postcss@8.5.12) + postcss-normalize-whitespace: 6.0.2(postcss@8.5.12) + postcss-ordered-values: 6.0.2(postcss@8.5.12) + postcss-reduce-initial: 6.1.0(postcss@8.5.12) + postcss-reduce-transforms: 6.0.2(postcss@8.5.12) + postcss-svgo: 6.0.3(postcss@8.5.12) + postcss-unique-selectors: 6.0.4(postcss@8.5.12) - cssnano-utils@4.0.2(postcss@8.5.10): + cssnano-utils@4.0.2(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - cssnano@6.1.2(postcss@8.5.10): + cssnano@6.1.2(postcss@8.5.12): dependencies: - cssnano-preset-default: 6.1.2(postcss@8.5.10) + cssnano-preset-default: 6.1.2(postcss@8.5.12) lilconfig: 3.1.3 - postcss: 8.5.10 + postcss: 8.5.12 csso@5.0.5: dependencies: @@ -19824,7 +19796,7 @@ snapshots: - supports-color - utf-8-validate - enhanced-resolve@5.20.1: + enhanced-resolve@5.21.0: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -19853,7 +19825,7 @@ snapshots: es-module-lexer@1.7.0: {} - es-module-lexer@2.0.0: {} + es-module-lexer@2.1.0: {} es-object-atoms@1.1.1: dependencies: @@ -20026,7 +19998,7 @@ snapshots: '@types/eslint': 9.6.1 eslint-config-prettier: 10.1.8(eslint@10.2.1(jiti@2.6.1)) - eslint-plugin-svelte@3.17.0(eslint@10.2.1(jiti@2.6.1))(svelte@5.55.2): + eslint-plugin-svelte@3.17.1(eslint@10.2.1(jiti@2.6.1))(svelte@5.55.2): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) '@jridgewell/sourcemap-codec': 1.5.5 @@ -20034,9 +20006,9 @@ snapshots: esutils: 2.0.3 globals: 16.5.0 known-css-properties: 0.37.0 - postcss: 8.5.10 - postcss-load-config: 3.1.4(postcss@8.5.10) - postcss-safe-parser: 7.0.1(postcss@8.5.10) + postcss: 8.5.12 + postcss-load-config: 3.1.4(postcss@8.5.12) + postcss-safe-parser: 7.0.1(postcss@8.5.12) semver: 7.7.4 svelte-eslint-parser: 1.6.0(svelte@5.55.2) optionalDependencies: @@ -20154,7 +20126,7 @@ snapshots: esrap@2.2.4: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/types': 8.59.0 esrecurse@4.3.0: dependencies: @@ -20247,21 +20219,21 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - exiftool-vendored.exe@13.53.0: + exiftool-vendored.exe@13.57.0: optional: true - exiftool-vendored.pl@13.53.0: {} + exiftool-vendored.pl@13.57.0: {} - exiftool-vendored@35.15.1: + exiftool-vendored@35.18.0: dependencies: '@photostructure/tz-lookup': 11.5.0 '@types/luxon': 3.7.1 batch-cluster: 17.3.1 - exiftool-vendored.pl: 13.53.0 + exiftool-vendored.pl: 13.57.0 he: 1.2.0 luxon: 3.7.2 optionalDependencies: - exiftool-vendored.exe: 13.53.0 + exiftool-vendored.exe: 13.57.0 expect-type@1.3.0: {} @@ -20346,7 +20318,7 @@ snapshots: extend@3.0.2: {} - fabric@7.2.0: + fabric@7.3.1: optionalDependencies: canvas: 2.11.2 jsdom: 26.1.0(canvas@2.11.2) @@ -20502,7 +20474,7 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.26(@swc/helpers@0.5.17))(esbuild@0.28.0)): + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)): dependencies: '@babel/code-frame': 7.29.0 chalk: 4.1.2 @@ -20517,7 +20489,7 @@ snapshots: semver: 7.7.4 tapable: 2.3.3 typescript: 5.9.3 - webpack: 5.106.0(@swc/core@1.15.26(@swc/helpers@0.5.17))(esbuild@0.28.0) + webpack: 5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0) form-data-encoder@2.1.4: {} @@ -21139,9 +21111,9 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.5.10): + icss-utils@5.1.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 ieee754@1.2.1: {} @@ -21797,8 +21769,6 @@ snapshots: lodash.uniq@4.5.0: {} - lodash@4.17.23: {} - lodash@4.18.1: {} log-symbols@4.1.0: @@ -21896,7 +21866,7 @@ snapshots: transitivePeerDependencies: - supports-color - maplibre-gl@5.23.0: + maplibre-gl@5.24.0: dependencies: '@mapbox/jsonlint-lines-primitives': 2.0.2 '@mapbox/point-geometry': 1.1.0 @@ -21906,7 +21876,7 @@ snapshots: '@mapbox/whoots-js': 3.1.0 '@maplibre/geojson-vt': 6.1.0 '@maplibre/maplibre-gl-style-spec': 24.8.1 - '@maplibre/mlt': 1.1.8 + '@maplibre/mlt': 1.1.9 '@maplibre/vt-pbf': 4.3.0 '@types/geojson': 7946.0.16 earcut: 3.0.2 @@ -21928,7 +21898,7 @@ snapshots: marked@16.4.2: {} - marked@17.0.5: {} + marked@17.0.6: {} math-intrinsics@1.1.0: {} @@ -22734,14 +22704,14 @@ snapshots: response-time: 2.3.4 tslib: 2.8.1 - nestjs-zod@5.3.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.2.6(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6): + nestjs-zod@5.3.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6): dependencies: '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) deepmerge: 4.3.1 rxjs: 7.8.2 zod: 4.3.6 optionalDependencies: - '@nestjs/swagger': 11.2.6(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2) + '@nestjs/swagger': 11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2) next-tick@1.1.0: {} @@ -23137,8 +23107,6 @@ snapshots: path-to-regexp@3.3.0: {} - path-to-regexp@8.3.0: {} - path-to-regexp@8.4.2: {} path-type@4.0.0: {} @@ -23247,446 +23215,446 @@ snapshots: path-data-parser: 0.1.0 points-on-curve: 0.2.0 - postcss-attribute-case-insensitive@7.0.1(postcss@8.5.10): + postcss-attribute-case-insensitive@7.0.1(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 - postcss-calc@9.0.1(postcss@8.5.10): + postcss-calc@9.0.1(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 - postcss-clamp@4.1.0(postcss@8.5.10): + postcss-clamp@4.1.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-color-functional-notation@7.0.12(postcss@8.5.10): + postcss-color-functional-notation@7.0.12(postcss@8.5.12): dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - postcss-color-hex-alpha@10.0.0(postcss@8.5.10): + postcss-color-hex-alpha@10.0.0(postcss@8.5.12): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-color-rebeccapurple@10.0.0(postcss@8.5.10): + postcss-color-rebeccapurple@10.0.0(postcss@8.5.12): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-colormin@6.1.0(postcss@8.5.10): + postcss-colormin@6.1.0(postcss@8.5.12): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-convert-values@6.1.0(postcss@8.5.10): + postcss-convert-values@6.1.0(postcss@8.5.12): dependencies: browserslist: 4.28.2 - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-custom-media@11.0.6(postcss@8.5.10): + postcss-custom-media@11.0.6(postcss@8.5.12): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.10 + postcss: 8.5.12 - postcss-custom-properties@14.0.6(postcss@8.5.10): + postcss-custom-properties@14.0.6(postcss@8.5.12): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-custom-selectors@8.0.5(postcss@8.5.10): + postcss-custom-selectors@8.0.5(postcss@8.5.12): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 - postcss-dir-pseudo-class@9.0.1(postcss@8.5.10): + postcss-dir-pseudo-class@9.0.1(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 - postcss-discard-comments@6.0.2(postcss@8.5.10): + postcss-discard-comments@6.0.2(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-discard-duplicates@6.0.3(postcss@8.5.10): + postcss-discard-duplicates@6.0.3(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-discard-empty@6.0.3(postcss@8.5.10): + postcss-discard-empty@6.0.3(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-discard-overridden@6.0.2(postcss@8.5.10): + postcss-discard-overridden@6.0.2(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-discard-unused@6.0.5(postcss@8.5.10): + postcss-discard-unused@6.0.5(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 6.1.2 - postcss-double-position-gradients@6.0.4(postcss@8.5.10): + postcss-double-position-gradients@6.0.4(postcss@8.5.12): dependencies: - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-focus-visible@10.0.1(postcss@8.5.10): + postcss-focus-visible@10.0.1(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 - postcss-focus-within@9.0.1(postcss@8.5.10): + postcss-focus-within@9.0.1(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 - postcss-font-variant@5.0.0(postcss@8.5.10): + postcss-font-variant@5.0.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-gap-properties@6.0.0(postcss@8.5.10): + postcss-gap-properties@6.0.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-image-set-function@7.0.0(postcss@8.5.10): + postcss-image-set-function@7.0.0(postcss@8.5.12): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-import@15.1.0(postcss@8.5.10): + postcss-import@15.1.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.11 - postcss-js@4.1.0(postcss@8.5.10): + postcss-js@4.1.0(postcss@8.5.12): dependencies: camelcase-css: 2.0.1 - postcss: 8.5.10 + postcss: 8.5.12 - postcss-lab-function@7.0.12(postcss@8.5.10): + postcss-lab-function@7.0.12(postcss@8.5.12): dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/utilities': 2.0.0(postcss@8.5.10) - postcss: 8.5.10 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/utilities': 2.0.0(postcss@8.5.12) + postcss: 8.5.12 - postcss-load-config@3.1.4(postcss@8.5.10): + postcss-load-config@3.1.4(postcss@8.5.12): dependencies: lilconfig: 2.1.0 yaml: 1.10.3 optionalDependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.10)(tsx@4.21.0)(yaml@2.8.3): + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.12)(tsx@4.21.0)(yaml@2.8.3): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 1.21.7 - postcss: 8.5.10 + postcss: 8.5.12 tsx: 4.21.0 yaml: 2.8.3 - postcss-loader@7.3.4(postcss@8.5.10)(typescript@6.0.3)(webpack@5.106.2): + postcss-loader@7.3.4(postcss@8.5.12)(typescript@6.0.3)(webpack@5.106.2): dependencies: cosmiconfig: 8.3.6(typescript@6.0.3) jiti: 1.21.7 - postcss: 8.5.10 + postcss: 8.5.12 semver: 7.7.4 webpack: 5.106.2 transitivePeerDependencies: - typescript - postcss-logical@8.1.0(postcss@8.5.10): + postcss-logical@8.1.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-merge-idents@6.0.3(postcss@8.5.10): + postcss-merge-idents@6.0.3(postcss@8.5.12): dependencies: - cssnano-utils: 4.0.2(postcss@8.5.10) - postcss: 8.5.10 + cssnano-utils: 4.0.2(postcss@8.5.12) + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-merge-longhand@6.0.5(postcss@8.5.10): + postcss-merge-longhand@6.0.5(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - stylehacks: 6.1.1(postcss@8.5.10) + stylehacks: 6.1.1(postcss@8.5.12) - postcss-merge-rules@6.1.1(postcss@8.5.10): + postcss-merge-rules@6.1.1(postcss@8.5.12): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 - cssnano-utils: 4.0.2(postcss@8.5.10) - postcss: 8.5.10 + cssnano-utils: 4.0.2(postcss@8.5.12) + postcss: 8.5.12 postcss-selector-parser: 6.1.2 - postcss-minify-font-values@6.1.0(postcss@8.5.10): + postcss-minify-font-values@6.1.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-minify-gradients@6.0.3(postcss@8.5.10): + postcss-minify-gradients@6.0.3(postcss@8.5.12): dependencies: colord: 2.9.3 - cssnano-utils: 4.0.2(postcss@8.5.10) - postcss: 8.5.10 + cssnano-utils: 4.0.2(postcss@8.5.12) + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-minify-params@6.1.0(postcss@8.5.10): + postcss-minify-params@6.1.0(postcss@8.5.12): dependencies: browserslist: 4.28.2 - cssnano-utils: 4.0.2(postcss@8.5.10) - postcss: 8.5.10 + cssnano-utils: 4.0.2(postcss@8.5.12) + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-minify-selectors@6.0.4(postcss@8.5.10): + postcss-minify-selectors@6.0.4(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 6.1.2 - postcss-modules-extract-imports@3.1.0(postcss@8.5.10): + postcss-modules-extract-imports@3.1.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-modules-local-by-default@4.2.0(postcss@8.5.10): + postcss-modules-local-by-default@4.2.0(postcss@8.5.12): dependencies: - icss-utils: 5.1.0(postcss@8.5.10) - postcss: 8.5.10 + icss-utils: 5.1.0(postcss@8.5.12) + postcss: 8.5.12 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.1(postcss@8.5.10): + postcss-modules-scope@3.2.1(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 - postcss-modules-values@4.0.0(postcss@8.5.10): + postcss-modules-values@4.0.0(postcss@8.5.12): dependencies: - icss-utils: 5.1.0(postcss@8.5.10) - postcss: 8.5.10 + icss-utils: 5.1.0(postcss@8.5.12) + postcss: 8.5.12 - postcss-nested@6.2.0(postcss@8.5.10): + postcss-nested@6.2.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 6.1.2 - postcss-nesting@13.0.2(postcss@8.5.10): + postcss-nesting@13.0.2(postcss@8.5.12): dependencies: '@csstools/selector-resolve-nested': 3.1.0(postcss-selector-parser@7.1.1) '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 - postcss-normalize-charset@6.0.2(postcss@8.5.10): + postcss-normalize-charset@6.0.2(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-normalize-display-values@6.0.2(postcss@8.5.10): + postcss-normalize-display-values@6.0.2(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-normalize-positions@6.0.2(postcss@8.5.10): + postcss-normalize-positions@6.0.2(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@6.0.2(postcss@8.5.10): + postcss-normalize-repeat-style@6.0.2(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-normalize-string@6.0.2(postcss@8.5.10): + postcss-normalize-string@6.0.2(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@6.0.2(postcss@8.5.10): + postcss-normalize-timing-functions@6.0.2(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@6.1.0(postcss@8.5.10): + postcss-normalize-unicode@6.1.0(postcss@8.5.12): dependencies: browserslist: 4.28.2 - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-normalize-url@6.0.2(postcss@8.5.10): + postcss-normalize-url@6.0.2(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@6.0.2(postcss@8.5.10): + postcss-normalize-whitespace@6.0.2(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-opacity-percentage@3.0.0(postcss@8.5.10): + postcss-opacity-percentage@3.0.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-ordered-values@6.0.2(postcss@8.5.10): + postcss-ordered-values@6.0.2(postcss@8.5.12): dependencies: - cssnano-utils: 4.0.2(postcss@8.5.10) - postcss: 8.5.10 + cssnano-utils: 4.0.2(postcss@8.5.12) + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-overflow-shorthand@6.0.0(postcss@8.5.10): + postcss-overflow-shorthand@6.0.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-page-break@3.0.4(postcss@8.5.10): + postcss-page-break@3.0.4(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-place@10.0.0(postcss@8.5.10): + postcss-place@10.0.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-preset-env@10.5.0(postcss@8.5.10): + postcss-preset-env@10.5.0(postcss@8.5.12): dependencies: - '@csstools/postcss-alpha-function': 1.0.1(postcss@8.5.10) - '@csstools/postcss-cascade-layers': 5.0.2(postcss@8.5.10) - '@csstools/postcss-color-function': 4.0.12(postcss@8.5.10) - '@csstools/postcss-color-function-display-p3-linear': 1.0.1(postcss@8.5.10) - '@csstools/postcss-color-mix-function': 3.0.12(postcss@8.5.10) - '@csstools/postcss-color-mix-variadic-function-arguments': 1.0.2(postcss@8.5.10) - '@csstools/postcss-content-alt-text': 2.0.8(postcss@8.5.10) - '@csstools/postcss-contrast-color-function': 2.0.12(postcss@8.5.10) - '@csstools/postcss-exponential-functions': 2.0.9(postcss@8.5.10) - '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.10) - '@csstools/postcss-gamut-mapping': 2.0.11(postcss@8.5.10) - '@csstools/postcss-gradients-interpolation-method': 5.0.12(postcss@8.5.10) - '@csstools/postcss-hwb-function': 4.0.12(postcss@8.5.10) - '@csstools/postcss-ic-unit': 4.0.4(postcss@8.5.10) - '@csstools/postcss-initial': 2.0.1(postcss@8.5.10) - '@csstools/postcss-is-pseudo-class': 5.0.3(postcss@8.5.10) - '@csstools/postcss-light-dark-function': 2.0.11(postcss@8.5.10) - '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.10) - '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.10) - '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.10) - '@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.10) - '@csstools/postcss-logical-viewport-units': 3.0.4(postcss@8.5.10) - '@csstools/postcss-media-minmax': 2.0.9(postcss@8.5.10) - '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.5(postcss@8.5.10) - '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.10) - '@csstools/postcss-normalize-display-values': 4.0.0(postcss@8.5.10) - '@csstools/postcss-oklab-function': 4.0.12(postcss@8.5.10) - '@csstools/postcss-position-area-property': 1.0.0(postcss@8.5.10) - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.10) - '@csstools/postcss-random-function': 2.0.1(postcss@8.5.10) - '@csstools/postcss-relative-color-syntax': 3.0.12(postcss@8.5.10) - '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.10) - '@csstools/postcss-sign-functions': 1.1.4(postcss@8.5.10) - '@csstools/postcss-stepped-value-functions': 4.0.9(postcss@8.5.10) - '@csstools/postcss-system-ui-font-family': 1.0.0(postcss@8.5.10) - '@csstools/postcss-text-decoration-shorthand': 4.0.3(postcss@8.5.10) - '@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.10) - '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.10) - autoprefixer: 10.5.0(postcss@8.5.10) + '@csstools/postcss-alpha-function': 1.0.1(postcss@8.5.12) + '@csstools/postcss-cascade-layers': 5.0.2(postcss@8.5.12) + '@csstools/postcss-color-function': 4.0.12(postcss@8.5.12) + '@csstools/postcss-color-function-display-p3-linear': 1.0.1(postcss@8.5.12) + '@csstools/postcss-color-mix-function': 3.0.12(postcss@8.5.12) + '@csstools/postcss-color-mix-variadic-function-arguments': 1.0.2(postcss@8.5.12) + '@csstools/postcss-content-alt-text': 2.0.8(postcss@8.5.12) + '@csstools/postcss-contrast-color-function': 2.0.12(postcss@8.5.12) + '@csstools/postcss-exponential-functions': 2.0.9(postcss@8.5.12) + '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.12) + '@csstools/postcss-gamut-mapping': 2.0.11(postcss@8.5.12) + '@csstools/postcss-gradients-interpolation-method': 5.0.12(postcss@8.5.12) + '@csstools/postcss-hwb-function': 4.0.12(postcss@8.5.12) + '@csstools/postcss-ic-unit': 4.0.4(postcss@8.5.12) + '@csstools/postcss-initial': 2.0.1(postcss@8.5.12) + '@csstools/postcss-is-pseudo-class': 5.0.3(postcss@8.5.12) + '@csstools/postcss-light-dark-function': 2.0.11(postcss@8.5.12) + '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.12) + '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.12) + '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.12) + '@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.12) + '@csstools/postcss-logical-viewport-units': 3.0.4(postcss@8.5.12) + '@csstools/postcss-media-minmax': 2.0.9(postcss@8.5.12) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.5(postcss@8.5.12) + '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.12) + '@csstools/postcss-normalize-display-values': 4.0.0(postcss@8.5.12) + '@csstools/postcss-oklab-function': 4.0.12(postcss@8.5.12) + '@csstools/postcss-position-area-property': 1.0.0(postcss@8.5.12) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) + '@csstools/postcss-random-function': 2.0.1(postcss@8.5.12) + '@csstools/postcss-relative-color-syntax': 3.0.12(postcss@8.5.12) + '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.12) + '@csstools/postcss-sign-functions': 1.1.4(postcss@8.5.12) + '@csstools/postcss-stepped-value-functions': 4.0.9(postcss@8.5.12) + '@csstools/postcss-system-ui-font-family': 1.0.0(postcss@8.5.12) + '@csstools/postcss-text-decoration-shorthand': 4.0.3(postcss@8.5.12) + '@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.12) + '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.12) + autoprefixer: 10.5.0(postcss@8.5.12) browserslist: 4.28.2 - css-blank-pseudo: 7.0.1(postcss@8.5.10) - css-has-pseudo: 7.0.3(postcss@8.5.10) - css-prefers-color-scheme: 10.0.0(postcss@8.5.10) + css-blank-pseudo: 7.0.1(postcss@8.5.12) + css-has-pseudo: 7.0.3(postcss@8.5.12) + css-prefers-color-scheme: 10.0.0(postcss@8.5.12) cssdb: 8.5.2 - postcss: 8.5.10 - postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.10) - postcss-clamp: 4.1.0(postcss@8.5.10) - postcss-color-functional-notation: 7.0.12(postcss@8.5.10) - postcss-color-hex-alpha: 10.0.0(postcss@8.5.10) - postcss-color-rebeccapurple: 10.0.0(postcss@8.5.10) - postcss-custom-media: 11.0.6(postcss@8.5.10) - postcss-custom-properties: 14.0.6(postcss@8.5.10) - postcss-custom-selectors: 8.0.5(postcss@8.5.10) - postcss-dir-pseudo-class: 9.0.1(postcss@8.5.10) - postcss-double-position-gradients: 6.0.4(postcss@8.5.10) - postcss-focus-visible: 10.0.1(postcss@8.5.10) - postcss-focus-within: 9.0.1(postcss@8.5.10) - postcss-font-variant: 5.0.0(postcss@8.5.10) - postcss-gap-properties: 6.0.0(postcss@8.5.10) - postcss-image-set-function: 7.0.0(postcss@8.5.10) - postcss-lab-function: 7.0.12(postcss@8.5.10) - postcss-logical: 8.1.0(postcss@8.5.10) - postcss-nesting: 13.0.2(postcss@8.5.10) - postcss-opacity-percentage: 3.0.0(postcss@8.5.10) - postcss-overflow-shorthand: 6.0.0(postcss@8.5.10) - postcss-page-break: 3.0.4(postcss@8.5.10) - postcss-place: 10.0.0(postcss@8.5.10) - postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.10) - postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.10) - postcss-selector-not: 8.0.1(postcss@8.5.10) + postcss: 8.5.12 + postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.12) + postcss-clamp: 4.1.0(postcss@8.5.12) + postcss-color-functional-notation: 7.0.12(postcss@8.5.12) + postcss-color-hex-alpha: 10.0.0(postcss@8.5.12) + postcss-color-rebeccapurple: 10.0.0(postcss@8.5.12) + postcss-custom-media: 11.0.6(postcss@8.5.12) + postcss-custom-properties: 14.0.6(postcss@8.5.12) + postcss-custom-selectors: 8.0.5(postcss@8.5.12) + postcss-dir-pseudo-class: 9.0.1(postcss@8.5.12) + postcss-double-position-gradients: 6.0.4(postcss@8.5.12) + postcss-focus-visible: 10.0.1(postcss@8.5.12) + postcss-focus-within: 9.0.1(postcss@8.5.12) + postcss-font-variant: 5.0.0(postcss@8.5.12) + postcss-gap-properties: 6.0.0(postcss@8.5.12) + postcss-image-set-function: 7.0.0(postcss@8.5.12) + postcss-lab-function: 7.0.12(postcss@8.5.12) + postcss-logical: 8.1.0(postcss@8.5.12) + postcss-nesting: 13.0.2(postcss@8.5.12) + postcss-opacity-percentage: 3.0.0(postcss@8.5.12) + postcss-overflow-shorthand: 6.0.0(postcss@8.5.12) + postcss-page-break: 3.0.4(postcss@8.5.12) + postcss-place: 10.0.0(postcss@8.5.12) + postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.12) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.12) + postcss-selector-not: 8.0.1(postcss@8.5.12) - postcss-pseudo-class-any-link@10.0.1(postcss@8.5.10): + postcss-pseudo-class-any-link@10.0.1(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 - postcss-reduce-idents@6.0.3(postcss@8.5.10): + postcss-reduce-idents@6.0.3(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-reduce-initial@6.1.0(postcss@8.5.10): + postcss-reduce-initial@6.1.0(postcss@8.5.12): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 - postcss: 8.5.10 + postcss: 8.5.12 - postcss-reduce-transforms@6.0.2(postcss@8.5.10): + postcss-reduce-transforms@6.0.2(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 - postcss-replace-overflow-wrap@4.0.0(postcss@8.5.10): + postcss-replace-overflow-wrap@4.0.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-safe-parser@7.0.1(postcss@8.5.10): + postcss-safe-parser@7.0.1(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-scss@4.0.9(postcss@8.5.10): + postcss-scss@4.0.9(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss-selector-not@8.0.1(postcss@8.5.10): + postcss-selector-not@8.0.1(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 7.1.1 postcss-selector-parser@6.1.2: @@ -23699,29 +23667,29 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-sort-media-queries@5.2.0(postcss@8.5.10): + postcss-sort-media-queries@5.2.0(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 sort-css-media-queries: 2.2.0 - postcss-svgo@6.0.3(postcss@8.5.10): + postcss-svgo@6.0.3(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-value-parser: 4.2.0 svgo: 3.3.2 - postcss-unique-selectors@6.0.4(postcss@8.5.10): + postcss-unique-selectors@6.0.4(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 6.1.2 postcss-value-parser@4.2.0: {} - postcss-zindex@6.0.2(postcss@8.5.10): + postcss-zindex@6.0.2(postcss@8.5.12): dependencies: - postcss: 8.5.10 + postcss: 8.5.12 - postcss@8.5.10: + postcss@8.5.12: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -24293,35 +24261,35 @@ snapshots: robust-predicates@3.0.3: {} - rolldown@1.0.0-rc.15: + rolldown@1.0.0-rc.17: dependencies: - '@oxc-project/types': 0.124.0 - '@rolldown/pluginutils': 1.0.0-rc.15 + '@oxc-project/types': 0.127.0 + '@rolldown/pluginutils': 1.0.0-rc.17 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.15 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.15 - '@rolldown/binding-darwin-x64': 1.0.0-rc.15 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.15 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.15 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.15 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.15 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.15 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.15 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.15 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.15 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.15 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.15 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.15 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.15 + '@rolldown/binding-android-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-x64': 1.0.0-rc.17 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 - rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-rc.15)(rollup@4.55.1): + rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-rc.17)(rollup@4.55.1): dependencies: open: 11.0.0 picomatch: 4.0.4 source-map: 0.7.6 yargs: 18.0.0 optionalDependencies: - rolldown: 1.0.0-rc.15 + rolldown: 1.0.0-rc.17 rollup: 4.55.1 rollup@4.55.1: @@ -24379,7 +24347,7 @@ snapshots: dependencies: escalade: 3.2.0 picocolors: 1.1.1 - postcss: 8.5.10 + postcss: 8.5.12 strip-json-comments: 3.1.1 run-applescript@7.1.0: {} @@ -24390,14 +24358,14 @@ snapshots: dependencies: queue-microtask: 1.2.3 - runed@0.35.1(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2): + runed@0.35.1(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 svelte: 5.55.2 optionalDependencies: - '@sveltejs/kit': 2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/kit': 2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) rw@1.3.3: {} @@ -24678,7 +24646,7 @@ snapshots: simple-concat: 1.0.1 optional: true - simple-icons@16.16.0: {} + simple-icons@16.17.0: {} sirv@2.0.4: dependencies: @@ -24960,10 +24928,10 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - stylehacks@6.1.1(postcss@8.5.10): + stylehacks@6.1.1(postcss@8.5.12): dependencies: browserslist: 4.28.2 - postcss: 8.5.10 + postcss: 8.5.12 postcss-selector-parser: 6.1.2 stylis@4.3.6: {} @@ -25035,8 +25003,8 @@ snapshots: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 - postcss: 8.5.10 - postcss-scss: 4.0.9(postcss@8.5.10) + postcss: 8.5.12 + postcss-scss: 4.0.9(postcss@8.5.12) postcss-selector-parser: 7.1.1 semver: 7.7.4 optionalDependencies: @@ -25101,7 +25069,7 @@ snapshots: d3-geo: 3.1.1 dequal: 2.0.3 just-compare: 2.3.0 - maplibre-gl: 5.23.0 + maplibre-gl: 5.24.0 pmtiles: 3.2.1 svelte: 5.55.2 @@ -25117,10 +25085,10 @@ snapshots: dependencies: svelte-floating-ui: 1.5.8 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2): + svelte-toolbelt@0.10.6(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2): dependencies: clsx: 2.1.1 - runed: 0.35.1(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) + runed: 0.35.1(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) style-to-object: 1.0.14 svelte: 5.55.2 transitivePeerDependencies: @@ -25157,7 +25125,7 @@ snapshots: csso: 5.0.5 picocolors: 1.1.1 - swagger-ui-dist@5.31.0: + swagger-ui-dist@5.32.4: dependencies: '@scarf/scarf': 1.4.0 @@ -25220,11 +25188,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.10 - postcss-import: 15.1.0(postcss@8.5.10) - postcss-js: 4.1.0(postcss@8.5.10) - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.10)(tsx@4.21.0)(yaml@2.8.3) - postcss-nested: 6.2.0(postcss@8.5.10) + postcss: 8.5.12 + postcss-import: 15.1.0(postcss@8.5.12) + postcss-js: 4.1.0(postcss@8.5.12) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.12)(tsx@4.21.0)(yaml@2.8.3) + postcss-nested: 6.2.0(postcss@8.5.12) postcss-selector-parser: 6.1.2 resolve: 1.22.11 sucrase: 3.35.1 @@ -25232,8 +25200,6 @@ snapshots: - tsx - yaml - tailwindcss@4.2.2: {} - tailwindcss@4.2.4: {} tapable@2.3.3: {} @@ -25300,15 +25266,15 @@ snapshots: - bare-abort-controller - react-native-b4a - terser-webpack-plugin@5.4.0(@swc/core@1.15.26(@swc/helpers@0.5.17))(esbuild@0.28.0)(webpack@5.106.0(@swc/core@1.15.26(@swc/helpers@0.5.17))(esbuild@0.28.0)): + terser-webpack-plugin@5.4.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)(webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.46.1 - webpack: 5.106.0(@swc/core@1.15.26(@swc/helpers@0.5.17))(esbuild@0.28.0) + webpack: 5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0) optionalDependencies: - '@swc/core': 1.15.26(@swc/helpers@0.5.17) + '@swc/core': 1.15.30(@swc/helpers@0.5.21) esbuild: 0.28.0 terser-webpack-plugin@5.4.0(webpack@5.106.2): @@ -25494,7 +25460,7 @@ snapshots: tsconfig-paths-webpack-plugin@4.2.0: dependencies: chalk: 4.1.2 - enhanced-resolve: 5.20.1 + enhanced-resolve: 5.21.0 tapable: 2.3.3 tsconfig-paths: 4.2.0 @@ -25550,12 +25516,12 @@ snapshots: typedarray@0.0.6: {} - typescript-eslint@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3): + typescript-eslint@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/parser': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3) - '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) eslint: 10.2.1(jiti@2.6.1) typescript: 6.0.3 transitivePeerDependencies: @@ -25692,10 +25658,10 @@ snapshots: unpipe@1.0.0: {} - unplugin-swc@1.5.9(@swc/core@1.15.26(@swc/helpers@0.5.17))(rollup@4.55.1): + unplugin-swc@1.5.9(@swc/core@1.15.30(@swc/helpers@0.5.21))(rollup@4.55.1): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.55.1) - '@swc/core': 1.15.26(@swc/helpers@0.5.17) + '@swc/core': 1.15.30(@swc/helpers@0.5.21) load-tsconfig: 0.2.5 unplugin: 2.3.11 transitivePeerDependencies: @@ -25779,6 +25745,8 @@ snapshots: uuid@11.1.0: {} + uuid@14.0.0: {} + uuid@8.3.2: {} validator@13.15.35: {} @@ -25850,12 +25818,12 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@6.0.3) - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color - typescript @@ -25865,7 +25833,7 @@ snapshots: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.10 + postcss: 8.5.12 rollup: 4.55.1 tinyglobby: 0.2.16 optionalDependencies: @@ -25878,12 +25846,12 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.10 - rolldown: 1.0.0-rc.15 + postcss: 8.5.12 + rolldown: 1.0.0-rc.17 tinyglobby: 0.2.16 optionalDependencies: '@types/node': 24.12.2 @@ -25895,12 +25863,12 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.10 - rolldown: 1.0.0-rc.15 + postcss: 8.5.12 + rolldown: 1.0.0-rc.17 tinyglobby: 0.2.16 optionalDependencies: '@types/node': 25.6.0 @@ -25912,13 +25880,13 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vitefu@1.1.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vitefu@1.1.2(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): optionalDependencies: - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vitest-fetch-mock@0.4.5(vitest@4.1.4): + vitest-fetch-mock@0.4.5(vitest@4.1.5): dependencies: - vitest: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: @@ -25964,16 +25932,16 @@ snapshots: - tsx - yaml - vitest@4.1.4(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: - '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.4 - '@vitest/runner': 4.1.4 - '@vitest/snapshot': 4.1.4 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 - es-module-lexer: 2.0.0 + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 obug: 2.1.1 @@ -25984,27 +25952,27 @@ snapshots: tinyexec: 1.1.1 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 24.12.2 - '@vitest/coverage-v8': 4.1.4(vitest@4.1.4) + '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) happy-dom: 20.9.0 jsdom: 26.1.0(canvas@2.11.2(encoding@0.1.13)) transitivePeerDependencies: - msw - vitest@4.1.4(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: - '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.4 - '@vitest/runner': 4.1.4 - '@vitest/snapshot': 4.1.4 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 - es-module-lexer: 2.0.0 + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 obug: 2.1.1 @@ -26015,27 +25983,27 @@ snapshots: tinyexec: 1.1.1 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 24.12.2 - '@vitest/coverage-v8': 4.1.4(vitest@4.1.4) + '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) happy-dom: 20.9.0 jsdom: 26.1.0(canvas@2.11.2) transitivePeerDependencies: - msw - vitest@4.1.4(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: - '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.4 - '@vitest/runner': 4.1.4 - '@vitest/snapshot': 4.1.4 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 - es-module-lexer: 2.0.0 + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 obug: 2.1.1 @@ -26046,12 +26014,12 @@ snapshots: tinyexec: 1.1.1 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 25.6.0 - '@vitest/coverage-v8': 4.1.4(vitest@4.1.4) + '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) happy-dom: 20.9.0 jsdom: 26.1.0(canvas@2.11.2) transitivePeerDependencies: @@ -26191,7 +26159,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.106.0(@swc/core@1.15.26(@swc/helpers@0.5.17))(esbuild@0.28.0): + webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -26203,8 +26171,8 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.2 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.20.1 - es-module-lexer: 2.0.0 + enhanced-resolve: 5.21.0 + es-module-lexer: 2.1.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -26215,7 +26183,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.4.0(@swc/core@1.15.26(@swc/helpers@0.5.17))(esbuild@0.28.0)(webpack@5.106.0(@swc/core@1.15.26(@swc/helpers@0.5.17))(esbuild@0.28.0)) + terser-webpack-plugin: 5.4.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)(webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)) watchpack: 2.5.1 webpack-sources: 3.3.4 transitivePeerDependencies: @@ -26235,8 +26203,8 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.2 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.20.1 - es-module-lexer: 2.0.0 + enhanced-resolve: 5.21.0 + es-module-lexer: 2.1.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 diff --git a/server/package.json b/server/package.json index 7eab3b4842..ea8e327a0a 100644 --- a/server/package.json +++ b/server/package.json @@ -46,15 +46,15 @@ "@nestjs/platform-express": "^11.0.4", "@nestjs/platform-socket.io": "^11.0.4", "@nestjs/schedule": "^6.0.0", - "@nestjs/swagger": "11.2.6", + "@nestjs/swagger": "^11.4.2", "@nestjs/websockets": "^11.0.4", "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^2.0.0", "@opentelemetry/exporter-prometheus": "^0.215.0", "@opentelemetry/instrumentation-http": "^0.215.0", - "@opentelemetry/instrumentation-ioredis": "^0.62.0", - "@opentelemetry/instrumentation-nestjs-core": "^0.60.0", - "@opentelemetry/instrumentation-pg": "^0.66.0", + "@opentelemetry/instrumentation-ioredis": "^0.63.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.61.0", + "@opentelemetry/instrumentation-pg": "^0.67.0", "@opentelemetry/resources": "^2.0.1", "@opentelemetry/sdk-metrics": "^2.0.1", "@opentelemetry/sdk-node": "^0.215.0", @@ -114,7 +114,7 @@ "thumbhash": "^0.1.1", "transformation-matrix": "^3.1.0", "ua-parser-js": "^2.0.0", - "uuid": "^11.1.0", + "uuid": "^14.0.0", "validator": "^13.12.0", "zod": "^4.3.6" }, diff --git a/server/src/constants.ts b/server/src/constants.ts index 20249bf1ca..5af37ef797 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -14,7 +14,6 @@ export const ErrorMessages = { export const POSTGRES_VERSION_RANGE = '>=14.0.0'; export const VECTORCHORD_VERSION_RANGE = '>=0.3 <2'; -export const VECTORS_VERSION_RANGE = '>=0.2 <0.4'; export const VECTOR_VERSION_RANGE = '>=0.5 <1'; export const JOBS_ASSET_PAGINATION_SIZE = 1000; @@ -24,15 +23,10 @@ export const EXTENSION_NAMES: Record = { cube: 'cube', earthdistance: 'earthdistance', vector: 'pgvector', - vectors: 'pgvecto.rs', vchord: 'VectorChord', } as const; -export const VECTOR_EXTENSIONS = [ - DatabaseExtension.VectorChord, - DatabaseExtension.Vectors, - DatabaseExtension.Vector, -] as const; +export const VECTOR_EXTENSIONS = [DatabaseExtension.VectorChord, DatabaseExtension.Vector] as const; export const VECTOR_INDEX_TABLES = { [VectorIndex.Clip]: 'smart_search', diff --git a/server/src/controllers/search.controller.spec.ts b/server/src/controllers/search.controller.spec.ts index 4df247031a..81cdb5ef6a 100644 --- a/server/src/controllers/search.controller.spec.ts +++ b/server/src/controllers/search.controller.spec.ts @@ -49,7 +49,7 @@ describe(SearchController.name, () => { }); it('should reject an invalid size', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/search/metadata').send({ size: -1.5 }); + const { status, body } = await request(ctx.getHttpServer()).post('/search/metadata').send({ size: -1 }); expect(status).toBe(400); expect(body).toEqual(errorDto.badRequest(['[size] Too small: expected number to be >=1'])); }); diff --git a/server/src/dtos/asset-media.dto.ts b/server/src/dtos/asset-media.dto.ts index cd9c7de641..8515ecc0b3 100644 --- a/server/src/dtos/asset-media.dto.ts +++ b/server/src/dtos/asset-media.dto.ts @@ -38,7 +38,7 @@ export enum UploadFieldName { const AssetMediaBaseSchema = z.object({ fileCreatedAt: isoDatetimeToDate.describe('File creation date'), fileModifiedAt: isoDatetimeToDate.describe('File modification date'), - duration: z.string().optional().describe('Duration (for videos)'), + duration: z.int32().min(0).optional().describe('Duration in milliseconds (for videos)'), filename: z.string().optional().describe('Filename'), /** The properties below are added to correctly generate the API docs and client SDKs. Validation should be handled in the controller. */ [UploadFieldName.ASSET_DATA]: z.any().describe('Asset file data').meta({ type: 'string', format: 'binary' }), diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index faa1db4afb..99d1fe7e25 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -47,11 +47,11 @@ const SanitizedAssetResponseSchema = z .describe( 'The local date and time when the photo/video was taken, derived from EXIF metadata. This represents the photographer\'s local time regardless of timezone, stored as a timezone-agnostic timestamp. Used for timeline grouping by "local" days and months.', ), - duration: z.string().nullable().describe('Video/gif duration in hh:mm:ss.SSS format (null for static images)'), + duration: z.int32().min(0).nullable().describe('Video/gif duration in milliseconds (null for static images)'), livePhotoVideoId: z.string().nullish().describe('Live photo video ID'), hasMetadata: z.boolean().describe('Whether asset has metadata'), - width: z.number().min(0).nullable().describe('Asset width'), - height: z.number().min(0).nullable().describe('Asset height'), + width: z.int().min(0).nullable().describe('Asset width'), + height: z.int().min(0).nullable().describe('Asset height'), }) .meta({ id: 'SanitizedAssetResponseDto' }); @@ -136,7 +136,7 @@ export type MapAsset = { checksum: Buffer; checksumAlgorithm: ChecksumAlgorithm; duplicateId: string | null; - duration: string | null; + duration: number | null; edits?: ShallowDehydrateObject[]; exifInfo?: ShallowDehydrateObject> | null; faces?: ShallowDehydrateObject[]; diff --git a/server/src/dtos/asset.dto.ts b/server/src/dtos/asset.dto.ts index 1362a86ed7..1f6124b7db 100644 --- a/server/src/dtos/asset.dto.ts +++ b/server/src/dtos/asset.dto.ts @@ -40,7 +40,7 @@ const UpdateAssetBaseSchema = z const AssetBulkUpdateBaseSchema = UpdateAssetBaseSchema.extend({ ids: z.array(z.uuidv4()).describe('Asset IDs to update'), duplicateId: z.string().nullish().describe('Duplicate ID'), - dateTimeRelative: z.number().optional().describe('Relative time offset in seconds'), + dateTimeRelative: z.int().optional().describe('Relative time offset in seconds'), timeZone: z.string().optional().describe('Time zone (IANA timezone)'), }); diff --git a/server/src/dtos/database-backup.dto.ts b/server/src/dtos/database-backup.dto.ts index 34dd8f2a62..bc16f0aebf 100644 --- a/server/src/dtos/database-backup.dto.ts +++ b/server/src/dtos/database-backup.dto.ts @@ -4,7 +4,7 @@ import z from 'zod'; const DatabaseBackupSchema = z .object({ filename: z.string().describe('Backup filename'), - filesize: z.number().describe('Backup file size'), + filesize: z.int().describe('Backup file size'), timezone: z.string().describe('Backup timezone'), }) .meta({ id: 'DatabaseBackupDto' }); diff --git a/server/src/dtos/editing.dto.ts b/server/src/dtos/editing.dto.ts index 9f5b352195..e862a117ee 100644 --- a/server/src/dtos/editing.dto.ts +++ b/server/src/dtos/editing.dto.ts @@ -21,10 +21,10 @@ const MirrorAxisSchema = z.enum(['horizontal', 'vertical']).describe('Axis to mi const CropParametersSchema = z .object({ - x: z.number().min(0).describe('Top-Left X coordinate of crop'), - y: z.number().min(0).describe('Top-Left Y coordinate of crop'), - width: z.number().min(1).describe('Width of the crop'), - height: z.number().min(1).describe('Height of the crop'), + x: z.int().min(0).describe('Top-Left X coordinate of crop'), + y: z.int().min(0).describe('Top-Left Y coordinate of crop'), + width: z.int().min(1).describe('Width of the crop'), + height: z.int().min(1).describe('Height of the crop'), }) .meta({ id: 'CropParameters' }); diff --git a/server/src/dtos/env.dto.ts b/server/src/dtos/env.dto.ts index fc30875b5a..7716c4b30b 100644 --- a/server/src/dtos/env.dto.ts +++ b/server/src/dtos/env.dto.ts @@ -77,7 +77,7 @@ export const EnvSchema = z DB_SSL_MODE: DatabaseSslModeSchema.optional(), DB_URL: z.string().optional(), DB_USERNAME: z.string().optional(), - DB_VECTOR_EXTENSION: z.enum(['pgvector', 'pgvecto.rs', 'vectorchord']).optional(), + DB_VECTOR_EXTENSION: z.enum(['pgvector', 'vectorchord']).optional(), NO_COLOR: z.string().optional(), REDIS_HOSTNAME: z.string().optional(), REDIS_PORT: z.coerce.number().int().optional(), diff --git a/server/src/dtos/exif.dto.ts b/server/src/dtos/exif.dto.ts index c3e1ab36c8..37274ee1f9 100644 --- a/server/src/dtos/exif.dto.ts +++ b/server/src/dtos/exif.dto.ts @@ -8,8 +8,8 @@ export const ExifResponseSchema = z .object({ make: z.string().nullish().default(null).describe('Camera make'), model: z.string().nullish().default(null).describe('Camera model'), - exifImageWidth: z.number().min(0).nullish().default(null).describe('Image width in pixels'), - exifImageHeight: z.number().min(0).nullish().default(null).describe('Image height in pixels'), + exifImageWidth: z.int().min(0).nullish().default(null).describe('Image width in pixels'), + exifImageHeight: z.int().min(0).nullish().default(null).describe('Image height in pixels'), fileSizeInByte: z.int().min(0).nullish().default(null).describe('File size in bytes'), orientation: z.string().nullish().default(null).describe('Image orientation'), // TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers. @@ -20,7 +20,7 @@ export const ExifResponseSchema = z lensModel: z.string().nullish().default(null).describe('Lens model'), fNumber: z.number().nullish().default(null).describe('F-number (aperture)'), focalLength: z.number().nullish().default(null).describe('Focal length in mm'), - iso: z.number().nullish().default(null).describe('ISO sensitivity'), + iso: z.int().nullish().default(null).describe('ISO sensitivity'), exposureTime: z.string().nullish().default(null).describe('Exposure time'), latitude: z.number().nullish().default(null).describe('GPS latitude'), longitude: z.number().nullish().default(null).describe('GPS longitude'), @@ -29,7 +29,7 @@ export const ExifResponseSchema = z country: z.string().nullish().default(null).describe('Country name'), description: z.string().nullish().default(null).describe('Image description'), projectionType: z.string().nullish().default(null).describe('Projection type'), - rating: z.number().nullish().default(null).describe('Rating'), + rating: z.int().nullish().default(null).describe('Rating'), }) .describe('EXIF response') .meta({ id: 'ExifResponseDto' }); diff --git a/server/src/dtos/maintenance.dto.ts b/server/src/dtos/maintenance.dto.ts index 9b1c0b63c0..96b376c5e6 100644 --- a/server/src/dtos/maintenance.dto.ts +++ b/server/src/dtos/maintenance.dto.ts @@ -29,7 +29,7 @@ const MaintenanceStatusResponseSchema = z .object({ active: z.boolean(), action: MaintenanceActionSchema, - progress: z.number().optional(), + progress: z.int().optional(), task: z.string().optional(), error: z.string().optional(), }) @@ -40,7 +40,7 @@ const MaintenanceDetectInstallStorageFolderSchema = z folder: StorageFolderSchema, readable: z.boolean().describe('Whether the folder is readable'), writable: z.boolean().describe('Whether the folder is writable'), - files: z.number().describe('Number of files in the folder'), + files: z.int().describe('Number of files in the folder'), }) .meta({ id: 'MaintenanceDetectInstallStorageFolderDto' }); diff --git a/server/src/dtos/person.dto.ts b/server/src/dtos/person.dto.ts index 1f8f080905..8cbbe6df78 100644 --- a/server/src/dtos/person.dto.ts +++ b/server/src/dtos/person.dto.ts @@ -51,8 +51,8 @@ const PersonSearchSchema = z withHidden: stringToBool.optional().describe('Include hidden people'), closestPersonId: z.uuidv4().optional().describe('Closest person ID for similarity search'), closestAssetId: z.uuidv4().optional().describe('Closest asset ID for similarity search'), - page: z.coerce.number().min(1).default(1).describe('Page number for pagination'), - size: z.coerce.number().min(1).max(1000).default(500).describe('Number of items per page'), + page: z.coerce.number().int().min(1).default(1).describe('Page number for pagination'), + size: z.coerce.number().int().min(1).max(1000).default(500).describe('Number of items per page'), }) .meta({ id: 'PersonSearchDto' }); diff --git a/server/src/dtos/search.dto.ts b/server/src/dtos/search.dto.ts index c0362cdb5d..ae5b5e2c8c 100644 --- a/server/src/dtos/search.dto.ts +++ b/server/src/dtos/search.dto.ts @@ -34,7 +34,7 @@ const BaseSearchSchema = z.object({ tagIds: z.array(z.uuidv4()).nullish().describe('Filter by tag IDs'), albumIds: z.array(z.uuidv4()).optional().describe('Filter by album IDs'), rating: z - .number() + .int() .min(-1) .max(5) .nullish() @@ -52,7 +52,7 @@ const BaseSearchSchema = z.object({ const BaseSearchWithResultsSchema = BaseSearchSchema.extend({ withDeleted: z.boolean().optional().describe('Include deleted assets'), withExif: z.boolean().optional().describe('Include EXIF data in response'), - size: z.number().min(1).max(1000).optional().describe('Number of results to return'), + size: z.int().min(1).max(1000).optional().describe('Number of results to return'), }); const RandomSearchSchema = BaseSearchWithResultsSchema.extend({ @@ -62,7 +62,7 @@ const RandomSearchSchema = BaseSearchWithResultsSchema.extend({ const LargeAssetSearchSchema = BaseSearchWithResultsSchema.extend({ minFileSize: z.coerce.number().int().min(0).optional().describe('Minimum file size in bytes'), - size: z.coerce.number().min(1).max(1000).optional().describe('Number of results to return'), + size: z.coerce.number().int().min(1).max(1000).optional().describe('Number of results to return'), }).meta({ id: 'LargeAssetSearchDto' }); const MetadataSearchSchema = RandomSearchSchema.extend({ @@ -75,7 +75,7 @@ const MetadataSearchSchema = RandomSearchSchema.extend({ thumbnailPath: z.string().optional().describe('Filter by thumbnail file path'), encodedVideoPath: z.string().optional().describe('Filter by encoded video file path'), order: AssetOrderSchema.default(AssetOrder.Desc).optional().describe('Sort order'), - page: z.number().min(1).optional().describe('Page number'), + page: z.int().min(1).optional().describe('Page number'), }).meta({ id: 'MetadataSearchDto' }); const StatisticsSearchSchema = BaseSearchSchema.extend({ @@ -86,7 +86,7 @@ const SmartSearchSchema = BaseSearchWithResultsSchema.extend({ query: z.string().trim().optional().describe('Natural language search query'), queryAssetId: z.uuidv4().optional().describe('Asset ID to use as search reference'), language: z.string().optional().describe('Search language code'), - page: z.number().min(1).optional().describe('Page number'), + page: z.int().min(1).optional().describe('Page number'), }).meta({ id: 'SmartSearchDto' }); const SearchPlacesSchema = z diff --git a/server/src/dtos/session.dto.ts b/server/src/dtos/session.dto.ts index 179a1dfb76..424d104053 100644 --- a/server/src/dtos/session.dto.ts +++ b/server/src/dtos/session.dto.ts @@ -4,7 +4,7 @@ import z from 'zod'; const SessionCreateSchema = z .object({ - duration: z.number().min(1).optional().describe('Session duration in seconds'), + duration: z.int().min(1).optional().describe('Session duration in seconds'), deviceType: z.string().optional().describe('Device type'), deviceOS: z.string().optional().describe('Device OS'), }) diff --git a/server/src/dtos/sync.dto.ts b/server/src/dtos/sync.dto.ts index dcd42b248f..821e53bafc 100644 --- a/server/src/dtos/sync.dto.ts +++ b/server/src/dtos/sync.dto.ts @@ -90,6 +90,30 @@ const SyncAssetV1Schema = z }) .meta({ id: 'SyncAssetV1' }); +const SyncAssetV2Schema = z + .object({ + id: z.string().describe('Asset ID'), + ownerId: z.string().describe('Owner ID'), + originalFileName: z.string().describe('Original file name'), + thumbhash: z.string().nullable().describe('Thumbhash'), + checksum: z.string().describe('Checksum'), + fileCreatedAt: isoDatetimeToDate.nullable().describe('File created at'), + fileModifiedAt: isoDatetimeToDate.nullable().describe('File modified at'), + localDateTime: isoDatetimeToDate.nullable().describe('Local date time'), + duration: z.int32().min(0).nullable().describe('Duration'), + type: AssetTypeSchema, + deletedAt: isoDatetimeToDate.nullable().describe('Deleted at'), + isFavorite: z.boolean().describe('Is favorite'), + visibility: AssetVisibilitySchema, + livePhotoVideoId: z.string().nullable().describe('Live photo video ID'), + stackId: z.string().nullable().describe('Stack ID'), + libraryId: z.string().nullable().describe('Library ID'), + width: z.int().nullable().describe('Asset width'), + height: z.int().nullable().describe('Asset height'), + isEdited: z.boolean().describe('Is edited'), + }) + .meta({ id: 'SyncAssetV2' }); + @ExtraModel() class SyncUserV1 extends createZodDto(SyncUserV1Schema) {} @ExtraModel() @@ -102,6 +126,8 @@ class SyncPartnerV1 extends createZodDto(SyncPartnerV1Schema) {} class SyncPartnerDeleteV1 extends createZodDto(SyncPartnerDeleteV1Schema) {} @ExtraModel() export class SyncAssetV1 extends createZodDto(SyncAssetV1Schema) {} +@ExtraModel() +export class SyncAssetV2 extends createZodDto(SyncAssetV2Schema) {} const SyncAssetDeleteV1Schema = z .object({ assetId: z.string().describe('Asset ID') }) @@ -429,12 +455,6 @@ class SyncPersonDeleteV1 extends createZodDto(SyncPersonDeleteV1Schema) {} class SyncAssetFaceV1 extends createZodDto(SyncAssetFaceV1Schema) {} @ExtraModel() class SyncAssetFaceV2 extends createZodDto(SyncAssetFaceV2Schema) {} - -export function syncAssetFaceV2ToV1(faceV2: SyncAssetFaceV2): SyncAssetFaceV1 { - const { deletedAt: _, isVisible: __, ...faceV1 } = faceV2; - - return faceV1; -} @ExtraModel() class SyncAssetFaceDeleteV1 extends createZodDto(SyncAssetFaceDeleteV1Schema) {} @ExtraModel() @@ -454,7 +474,7 @@ export type SyncItem = { [SyncEntityType.UserDeleteV1]: SyncUserDeleteV1; [SyncEntityType.PartnerV1]: SyncPartnerV1; [SyncEntityType.PartnerDeleteV1]: SyncPartnerDeleteV1; - [SyncEntityType.AssetV1]: SyncAssetV1; + [SyncEntityType.AssetV2]: SyncAssetV2; [SyncEntityType.AssetDeleteV1]: SyncAssetDeleteV1; [SyncEntityType.AssetMetadataV1]: SyncAssetMetadataV1; [SyncEntityType.AssetMetadataDeleteV1]: SyncAssetMetadataDeleteV1; @@ -463,8 +483,8 @@ export type SyncItem = { [SyncEntityType.AssetOcrDeleteV1]: SyncAssetOcrDeleteV1; [SyncEntityType.AssetEditV1]: SyncAssetEditV1; [SyncEntityType.AssetEditDeleteV1]: SyncAssetEditDeleteV1; - [SyncEntityType.PartnerAssetV1]: SyncAssetV1; - [SyncEntityType.PartnerAssetBackfillV1]: SyncAssetV1; + [SyncEntityType.PartnerAssetV2]: SyncAssetV2; + [SyncEntityType.PartnerAssetBackfillV2]: SyncAssetV2; [SyncEntityType.PartnerAssetDeleteV1]: SyncAssetDeleteV1; [SyncEntityType.PartnerAssetExifV1]: SyncAssetExifV1; [SyncEntityType.PartnerAssetExifBackfillV1]: SyncAssetExifV1; @@ -474,9 +494,9 @@ export type SyncItem = { [SyncEntityType.AlbumUserV1]: SyncAlbumUserV1; [SyncEntityType.AlbumUserBackfillV1]: SyncAlbumUserV1; [SyncEntityType.AlbumUserDeleteV1]: SyncAlbumUserDeleteV1; - [SyncEntityType.AlbumAssetCreateV1]: SyncAssetV1; - [SyncEntityType.AlbumAssetUpdateV1]: SyncAssetV1; - [SyncEntityType.AlbumAssetBackfillV1]: SyncAssetV1; + [SyncEntityType.AlbumAssetCreateV2]: SyncAssetV2; + [SyncEntityType.AlbumAssetUpdateV2]: SyncAssetV2; + [SyncEntityType.AlbumAssetBackfillV2]: SyncAssetV2; [SyncEntityType.AlbumAssetExifCreateV1]: SyncAssetExifV1; [SyncEntityType.AlbumAssetExifUpdateV1]: SyncAssetExifV1; [SyncEntityType.AlbumAssetExifBackfillV1]: SyncAssetExifV1; diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts index 35f61032b0..4563405093 100644 --- a/server/src/dtos/system-config.dto.ts +++ b/server/src/dtos/system-config.dto.ts @@ -51,7 +51,7 @@ const DatabaseBackupSchema = z .object({ enabled: configBool.describe('Enabled'), cronExpression: cronExpressionSchema, - keepLastAmount: z.number().min(1).describe('Keep last amount'), + keepLastAmount: z.int().min(1).describe('Keep last amount'), }) .meta({ id: 'DatabaseBackupConfig' }); @@ -130,8 +130,8 @@ const SystemConfigLoggingSchema = z const MachineLearningAvailabilityChecksSchema = z .object({ enabled: configBool.describe('Enabled'), - timeout: z.number(), - interval: z.number(), + timeout: z.int(), + interval: z.int(), }) .meta({ id: 'MachineLearningAvailabilityChecksDto' }); @@ -180,7 +180,7 @@ const SystemConfigOAuthSchema = z tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethodSchema, timeout: z.int().min(1).describe('Timeout'), allowInsecureRequests: configBool.describe('Allow insecure requests'), - defaultStorageQuota: z.number().min(0).nullable().describe('Default storage quota'), + defaultStorageQuota: z.int().min(0).nullable().describe('Default storage quota'), enabled: configBool.describe('Enabled'), issuerUrl: z .string() @@ -254,7 +254,7 @@ const SystemConfigSmtpTransportSchema = z .object({ ignoreCert: configBool.describe('Whether to ignore SSL certificate errors'), host: z.string().describe('SMTP server hostname'), - port: z.number().min(0).max(65_535).describe('SMTP server port'), + port: z.int().min(0).max(65_535).describe('SMTP server port'), secure: configBool.describe('Whether to use secure connection (TLS/SSL)'), username: z.string().describe('SMTP username'), password: z.string().describe('SMTP password'), diff --git a/server/src/dtos/time-bucket.dto.ts b/server/src/dtos/time-bucket.dto.ts index 0b4be5cba1..88a352e20e 100644 --- a/server/src/dtos/time-bucket.dto.ts +++ b/server/src/dtos/time-bucket.dto.ts @@ -89,8 +89,8 @@ const TimeBucketAssetResponseSchema = z "Array of UTC offset hours at the time each photo was taken. Positive values are east of UTC, negative values are west of UTC. Values may be fractional (e.g., 5.5 for +05:30, -9.75 for -09:45). Applying this offset to 'fileCreatedAt' will give you the time the photo was taken from the photographer's perspective.", ), duration: z - .array(z.string().nullable()) - .describe('Array of video/gif durations in hh:mm:ss.SSS format (null for static images)'), + .array(z.int32().min(0).nullable()) + .describe('Array of video/gif durations in milliseconds (null for static images)'), stack: z .array(stackTupleSchema) .optional() diff --git a/server/src/dtos/workflow.dto.ts b/server/src/dtos/workflow.dto.ts index 0307c7f483..f94e4bed92 100644 --- a/server/src/dtos/workflow.dto.ts +++ b/server/src/dtos/workflow.dto.ts @@ -46,7 +46,7 @@ const WorkflowFilterResponseSchema = z workflowId: z.string().describe('Workflow ID'), pluginFilterId: z.string().describe('Plugin filter ID'), filterConfig: FilterConfigSchema.nullable(), - order: z.number().describe('Filter order'), + order: z.int().describe('Filter order'), }) .meta({ id: 'WorkflowFilterResponseDto' }); @@ -56,7 +56,7 @@ const WorkflowActionResponseSchema = z workflowId: z.string().describe('Workflow ID'), pluginActionId: z.string().describe('Plugin action ID'), actionConfig: ActionConfigSchema.nullable(), - order: z.number().describe('Action order'), + order: z.int().describe('Action order'), }) .meta({ id: 'WorkflowActionResponseDto' }); diff --git a/server/src/enum.ts b/server/src/enum.ts index 308071399e..f84cffac6c 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -22,7 +22,7 @@ export enum ImmichHeader { SharedLinkKey = 'x-immich-share-key', SharedLinkSlug = 'x-immich-share-slug', Checksum = 'x-immich-checksum', - Cid = 'x-immich-cid', + CorrelationId = 'X-Correlation-ID', } export enum ImmichQuery { @@ -445,6 +445,12 @@ export enum VideoCodec { export const VideoCodecSchema = z.enum(VideoCodec).describe('Target video codec').meta({ id: 'VideoCodec' }); +export enum VideoSegmentCodec { + Av1 = 'av1', + Hevc = 'hevc', + H264 = 'h264', +} + export enum AudioCodec { Mp3 = 'mp3', Aac = 'aac', @@ -601,7 +607,6 @@ export enum DatabaseExtension { Cube = 'cube', EarthDistance = 'earthdistance', Vector = 'vector', - Vectors = 'vectors', VectorChord = 'vchord', } @@ -801,9 +806,13 @@ export enum SyncRequestType { AlbumsV2 = 'AlbumsV2', AlbumUsersV1 = 'AlbumUsersV1', AlbumToAssetsV1 = 'AlbumToAssetsV1', + /** @deprecated */ AlbumAssetsV1 = 'AlbumAssetsV1', + AlbumAssetsV2 = 'AlbumAssetsV2', AlbumAssetExifsV1 = 'AlbumAssetExifsV1', + /** @deprecated */ AssetsV1 = 'AssetsV1', + AssetsV2 = 'AssetsV2', AssetExifsV1 = 'AssetExifsV1', AssetEditsV1 = 'AssetEditsV1', AssetMetadataV1 = 'AssetMetadataV1', @@ -812,12 +821,15 @@ export enum SyncRequestType { MemoriesV1 = 'MemoriesV1', MemoryToAssetsV1 = 'MemoryToAssetsV1', PartnersV1 = 'PartnersV1', + /** @deprecated */ PartnerAssetsV1 = 'PartnerAssetsV1', + PartnerAssetsV2 = 'PartnerAssetsV2', PartnerAssetExifsV1 = 'PartnerAssetExifsV1', PartnerStacksV1 = 'PartnerStacksV1', StacksV1 = 'StacksV1', UsersV1 = 'UsersV1', PeopleV1 = 'PeopleV1', + /** @deprecated */ AssetFacesV1 = 'AssetFacesV1', AssetFacesV2 = 'AssetFacesV2', UserMetadataV1 = 'UserMetadataV1', @@ -834,7 +846,9 @@ export enum SyncEntityType { UserV1 = 'UserV1', UserDeleteV1 = 'UserDeleteV1', + /** @deprecated */ AssetV1 = 'AssetV1', + AssetV2 = 'AssetV2', AssetDeleteV1 = 'AssetDeleteV1', AssetExifV1 = 'AssetExifV1', AssetEditV1 = 'AssetEditV1', @@ -847,8 +861,12 @@ export enum SyncEntityType { PartnerV1 = 'PartnerV1', PartnerDeleteV1 = 'PartnerDeleteV1', + /** @deprecated */ PartnerAssetV1 = 'PartnerAssetV1', + PartnerAssetV2 = 'PartnerAssetV2', + /** @deprecated */ PartnerAssetBackfillV1 = 'PartnerAssetBackfillV1', + PartnerAssetBackfillV2 = 'PartnerAssetBackfillV2', PartnerAssetDeleteV1 = 'PartnerAssetDeleteV1', PartnerAssetExifV1 = 'PartnerAssetExifV1', PartnerAssetExifBackfillV1 = 'PartnerAssetExifBackfillV1', @@ -864,9 +882,15 @@ export enum SyncEntityType { AlbumUserBackfillV1 = 'AlbumUserBackfillV1', AlbumUserDeleteV1 = 'AlbumUserDeleteV1', + /** @deprecated */ AlbumAssetCreateV1 = 'AlbumAssetCreateV1', + AlbumAssetCreateV2 = 'AlbumAssetCreateV2', + /** @deprecated */ AlbumAssetUpdateV1 = 'AlbumAssetUpdateV1', + AlbumAssetUpdateV2 = 'AlbumAssetUpdateV2', + /** @deprecated */ AlbumAssetBackfillV1 = 'AlbumAssetBackfillV1', + AlbumAssetBackfillV2 = 'AlbumAssetBackfillV2', AlbumAssetExifCreateV1 = 'AlbumAssetExifCreateV1', AlbumAssetExifUpdateV1 = 'AlbumAssetExifUpdateV1', AlbumAssetExifBackfillV1 = 'AlbumAssetExifBackfillV1', diff --git a/server/src/middleware/global-exception.filter.ts b/server/src/middleware/global-exception.filter.ts index f91bb2b122..f331df9147 100644 --- a/server/src/middleware/global-exception.filter.ts +++ b/server/src/middleware/global-exception.filter.ts @@ -2,6 +2,7 @@ import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/co import { Response } from 'express'; import { ClsService } from 'nestjs-cls'; import { ZodSerializationException, ZodValidationException } from 'nestjs-zod'; +import { ImmichHeader } from 'src/enum'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { logGlobalError } from 'src/utils/logger'; import { ZodError } from 'zod'; @@ -16,18 +17,13 @@ export class GlobalExceptionFilter implements ExceptionFilter { } catch(error: Error, host: ArgumentsHost) { - const ctx = host.switchToHttp(); - const response = ctx.getResponse(); - const { status, body } = this.fromError(error); - if (!response.headersSent) { - response.status(status).json({ ...body, statusCode: status, correlationId: this.cls.getId() }); - } + this.handleError(host.switchToHttp().getResponse(), error); } handleError(res: Response, error: Error) { const { status, body } = this.fromError(error); if (!res.headersSent) { - res.status(status).json({ ...body, statusCode: status, correlationId: this.cls.getId() }); + res.header(ImmichHeader.CorrelationId, this.cls.getId()).status(status).json(body); } } @@ -36,26 +32,24 @@ export class GlobalExceptionFilter implements ExceptionFilter { if (error instanceof HttpException) { const status = error.getStatus(); - let body = error.getResponse(); - - // unclear what circumstances would return a string - if (typeof body === 'string') { - body = { message: body }; - } + const response = error.getResponse(); + const body: Record = + typeof response === 'string' ? { message: response } : { ...(response as object) }; // handle both request and response validation errors if (error instanceof ZodValidationException || error instanceof ZodSerializationException) { const zodError = error.getZodError(); if (zodError instanceof ZodError && zodError.issues.length > 0) { - body = { - message: zodError.issues.map((issue) => - issue.path.length > 0 ? `[${issue.path.join('.')}] ${issue.message}` : issue.message, - ), - error: 'Bad Request', - }; + body['message'] = zodError.issues.map((issue) => + issue.path.length > 0 ? `[${issue.path.join('.')}] ${issue.message}` : issue.message, + ); } } + // remove fields that duplicate the HTTP response line or will be reformatted in a later step + delete body['error']; + delete body['statusCode']; + delete body['errors']; return { status, body }; } diff --git a/server/src/queries/video.stream.repository.sql b/server/src/queries/video.stream.repository.sql new file mode 100644 index 0000000000..c77882d77d --- /dev/null +++ b/server/src/queries/video.stream.repository.sql @@ -0,0 +1,46 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- VideoStreamRepository.getSession +select + * +from + "video_stream_session" +where + "id" = $1 + +-- VideoStreamRepository.getVariant +select + * +from + "video_stream_variant" +where + "id" = $1 + +-- VideoStreamRepository.getSegment +select + * +from + "video_stream_segment" +where + "variantId" = $1 + and "index" = $2 + +-- VideoStreamRepository.getExpiredSessions +select + "id" +from + "video_stream_session" +where + "expiresAt" <= $1 + +-- VideoStreamRepository.extendSession +update "video_stream_session" +set + "expiresAt" = $1 +where + "id" = $2 + +-- VideoStreamRepository.deleteSession +delete from "video_stream_session" +where + "id" = $1 diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index 97ec3f1cdc..ddbdb2a856 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -248,10 +248,6 @@ const getEnv = (): EnvData => { vectorExtension = DatabaseExtension.Vector; break; } - case 'pgvecto.rs': { - vectorExtension = DatabaseExtension.Vectors; - break; - } case 'vectorchord': { vectorExtension = DatabaseExtension.VectorChord; break; @@ -301,11 +297,9 @@ const getEnv = (): EnvData => { mount: true, generateId: true, setup: (cls, req: Request, res: Response) => { - const headerValues = req.headers[ImmichHeader.Cid]; - const headerValue = Array.isArray(headerValues) ? headerValues[0] : headerValues; - const cid = headerValue || cls.get(CLS_ID); + const cid = req.header(ImmichHeader.CorrelationId) || cls.get(CLS_ID); cls.set(CLS_ID, cid); - res.header(ImmichHeader.Cid, cid); + res.header(ImmichHeader.CorrelationId, cid); }, }, }, diff --git a/server/src/repositories/database.repository.ts b/server/src/repositories/database.repository.ts index 7ae1119bbc..a86e929ef4 100644 --- a/server/src/repositories/database.repository.ts +++ b/server/src/repositories/database.repository.ts @@ -1,7 +1,7 @@ import { schemaDiff, schemaFromCode, schemaFromDatabase } from '@immich/sql-tools'; import { Injectable } from '@nestjs/common'; import AsyncLock from 'async-lock'; -import { FileMigrationProvider, Kysely, Migrator, sql, Transaction } from 'kysely'; +import { FileMigrationProvider, Kysely, Migrator, sql } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; import { readdir } from 'node:fs/promises'; import { join } from 'node:path'; @@ -14,7 +14,6 @@ import { VECTOR_VERSION_RANGE, VECTORCHORD_LIST_SLACK_FACTOR, VECTORCHORD_VERSION_RANGE, - VECTORS_VERSION_RANGE, } from 'src/constants'; import { GenerateSql } from 'src/decorators'; import { DatabaseExtension, DatabaseLock, VectorIndex } from 'src/enum'; @@ -23,7 +22,7 @@ import { LoggingRepository } from 'src/repositories/logging.repository'; import 'src/schema'; // make sure all schema definitions are imported for schemaFromCode import { DB } from 'src/schema'; import { immich_uuid_v7 } from 'src/schema/functions'; -import { ExtensionVersion, VectorExtension, VectorUpdateResult } from 'src/types'; +import { ExtensionVersion, VectorExtension } from 'src/types'; import { vectorIndexQuery } from 'src/utils/database'; import { isValidInteger } from 'src/validation'; @@ -73,7 +72,7 @@ export class DatabaseRepository { return getVectorExtension(this.db); } - @GenerateSql({ params: [[DatabaseExtension.Vectors]] }) + @GenerateSql({ params: [[DatabaseExtension.Vector]] }) async getExtensionVersions(extensions: readonly DatabaseExtension[]): Promise { const { rows } = await sql` SELECT name, default_version as "availableVersion", installed_version as "installedVersion" @@ -88,9 +87,6 @@ export class DatabaseRepository { case DatabaseExtension.VectorChord: { return VECTORCHORD_VERSION_RANGE; } - case DatabaseExtension.Vectors: { - return VECTORS_VERSION_RANGE; - } case DatabaseExtension.Vector: { return VECTOR_VERSION_RANGE; } @@ -125,7 +121,7 @@ export class DatabaseRepository { await sql`DROP EXTENSION IF EXISTS ${sql.raw(extension)}`.execute(this.db); } - async updateVectorExtension(extension: VectorExtension, targetVersion?: string): Promise { + async updateVectorExtension(extension: VectorExtension, targetVersion?: string): Promise { const [{ availableVersion, installedVersion }] = await this.getExtensionVersions([extension]); if (!installedVersion) { throw new Error(`${EXTENSION_NAMES[extension]} extension is not installed`); @@ -136,10 +132,8 @@ export class DatabaseRepository { } targetVersion ??= availableVersion; - let restartRequired = false; - const diff = semver.diff(installedVersion, targetVersion); - if (!diff) { - return { restartRequired: false }; + if (!semver.diff(installedVersion, targetVersion)) { + return; } await Promise.all([ @@ -147,22 +141,8 @@ export class DatabaseRepository { this.db.schema.dropIndex(VectorIndex.Face).ifExists().execute(), ]); - await this.db.transaction().execute(async (tx) => { - await this.setSearchPath(tx); - - await sql`ALTER EXTENSION ${sql.raw(extension)} UPDATE TO ${sql.lit(targetVersion)}`.execute(tx); - - if (extension === DatabaseExtension.Vectors && (diff === 'major' || diff === 'minor')) { - await sql`SELECT pgvectors_upgrade()`.execute(tx); - restartRequired = true; - } - }); - - if (!restartRequired) { - await Promise.all([this.reindexVectors(VectorIndex.Clip), this.reindexVectors(VectorIndex.Face)]); - } - - return { restartRequired }; + await sql`ALTER EXTENSION ${sql.raw(extension)} UPDATE TO ${sql.lit(targetVersion)}`.execute(this.db); + await Promise.all([this.reindexVectors(VectorIndex.Clip), this.reindexVectors(VectorIndex.Face)]); } async prewarm(index: VectorIndex): Promise { @@ -198,12 +178,6 @@ export class DatabaseRepository { } break; } - case DatabaseExtension.Vectors: { - if (!row.indexdef.toLowerCase().includes('using vectors')) { - promises.push(this.reindexVectors(indexName)); - } - break; - } case DatabaseExtension.VectorChord: { const matches = row.indexdef.match(/(?<=lists = \[)\d+/g); const lists = matches && matches.length > 0 ? Number(matches[0]) : 1; @@ -260,11 +234,10 @@ export class DatabaseRepository { await sql`ALTER TABLE ${sql.raw(table)} ADD COLUMN embedding real[] NOT NULL`.execute(tx); } await sql`ALTER TABLE ${sql.raw(table)} ALTER COLUMN embedding SET DATA TYPE real[]`.execute(tx); - const schema = vectorExtension === DatabaseExtension.Vectors ? 'vectors.' : ''; await sql` ALTER TABLE ${sql.raw(table)} ALTER COLUMN embedding - SET DATA TYPE ${sql.raw(schema)}vector(${sql.raw(String(dimSize))})`.execute(tx); + SET DATA TYPE vector(${sql.raw(String(dimSize))})`.execute(tx); await sql.raw(vectorIndexQuery({ vectorExtension, table, indexName, lists })).execute(tx); }); try { @@ -275,10 +248,6 @@ export class DatabaseRepository { this.logger.log(`Reindexed ${indexName}`); } - private async setSearchPath(tx: Transaction): Promise { - await sql`SET search_path TO "$user", public, vectors`.execute(tx); - } - private async getDatabaseName(): Promise { const { rows } = await sql<{ db: string }>`SELECT current_database() as db`.execute(this.db); return rows[0].db; diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index fcff171a5e..886f925ee8 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -46,6 +46,7 @@ import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TrashRepository } from 'src/repositories/trash.repository'; import { UserRepository } from 'src/repositories/user.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; +import { VideoStreamRepository } from 'src/repositories/video-stream.repository'; import { ViewRepository } from 'src/repositories/view-repository'; import { WebsocketRepository } from 'src/repositories/websocket.repository'; import { WorkflowRepository } from 'src/repositories/workflow.repository'; @@ -100,6 +101,7 @@ export const repositories = [ UserRepository, ViewRepository, VersionHistoryRepository, + VideoStreamRepository, WebsocketRepository, WorkflowRepository, ]; diff --git a/server/src/repositories/video-stream.repository.ts b/server/src/repositories/video-stream.repository.ts new file mode 100644 index 0000000000..e23ee4ca4c --- /dev/null +++ b/server/src/repositories/video-stream.repository.ts @@ -0,0 +1,62 @@ +import { Injectable } from '@nestjs/common'; +import { Insertable, Kysely } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { DB } from 'src/schema'; +import { + VideoStreamSegmentTable, + VideoStreamSessionTable, + VideoStreamVariantTable, +} from 'src/schema/tables/video-stream.table'; + +@Injectable() +export class VideoStreamRepository { + constructor(@InjectKysely() private db: Kysely) {} + + createSession(session: Insertable) { + return this.db.insertInto('video_stream_session').values(session).returning(['id']).executeTakeFirstOrThrow(); + } + + createVariant(variant: Insertable) { + return this.db.insertInto('video_stream_variant').values(variant).returning(['id']).executeTakeFirstOrThrow(); + } + + async createSegment(segment: Insertable) { + await this.db.insertInto('video_stream_segment').values(segment).execute(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + getSession(id: string) { + return this.db.selectFrom('video_stream_session').selectAll().where('id', '=', id).executeTakeFirst(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + getVariant(id: string) { + return this.db.selectFrom('video_stream_variant').selectAll().where('id', '=', id).executeTakeFirst(); + } + + @GenerateSql({ params: [DummyValue.UUID, DummyValue.NUMBER] }) + getSegment(variantId: string, index: number) { + return this.db + .selectFrom('video_stream_segment') + .selectAll() + .where('variantId', '=', variantId) + .where('index', '=', index) + .executeTakeFirst(); + } + + @GenerateSql() + getExpiredSessions() { + return this.db.selectFrom('video_stream_session').select(['id']).where('expiresAt', '<=', new Date()).execute(); + } + + @GenerateSql({ params: [DummyValue.UUID, DummyValue.DATE] }) + async extendSession(id: string, expiresAt: Date) { + await this.db.updateTable('video_stream_session').set({ expiresAt }).where('id', '=', id).execute(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + async deleteSession(id: string) { + await this.db.deleteFrom('video_stream_session').where('id', '=', id).execute(); + } +} diff --git a/server/src/repositories/websocket.repository.ts b/server/src/repositories/websocket.repository.ts index 235d2f2a84..b4a0fcc00a 100644 --- a/server/src/repositories/websocket.repository.ts +++ b/server/src/repositories/websocket.repository.ts @@ -11,7 +11,7 @@ import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { NotificationDto } from 'src/dtos/notification.dto'; import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; -import { SyncAssetEditV1, SyncAssetExifV1, SyncAssetV1 } from 'src/dtos/sync.dto'; +import { SyncAssetEditV1, SyncAssetExifV1, SyncAssetV2 } from 'src/dtos/sync.dto'; import { AppRestartEvent, ArgsOf, EventRepository } from 'src/repositories/event.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { handlePromiseError } from 'src/utils/misc'; @@ -35,9 +35,9 @@ export interface ClientEventMap { on_notification: [NotificationDto]; on_session_delete: [string]; - AssetUploadReadyV1: [{ asset: SyncAssetV1; exif: SyncAssetExifV1 }]; + AssetUploadReadyV2: [{ asset: SyncAssetV2; exif: SyncAssetExifV1 }]; AppRestartV1: [AppRestartEvent]; - AssetEditReadyV1: [{ asset: SyncAssetV1; edit: SyncAssetEditV1[] }]; + AssetEditReadyV2: [{ asset: SyncAssetV2; edit: SyncAssetEditV1[] }]; } export type AuthFn = (client: Socket) => Promise; diff --git a/server/src/schema/enums.ts b/server/src/schema/enums.ts index 2bfa4a3340..73f8133441 100644 --- a/server/src/schema/enums.ts +++ b/server/src/schema/enums.ts @@ -1,5 +1,12 @@ import { registerEnum } from '@immich/sql-tools'; -import { AlbumUserRole, AssetStatus, AssetVisibility, ChecksumAlgorithm, SourceType } from 'src/enum'; +import { + AlbumUserRole, + AssetStatus, + AssetVisibility, + ChecksumAlgorithm, + SourceType, + VideoSegmentCodec, +} from 'src/enum'; export const album_user_role_enum = registerEnum({ name: 'album_user_role_enum', @@ -25,3 +32,8 @@ export const asset_checksum_algorithm_enum = registerEnum({ name: 'asset_checksum_algorithm_enum', values: Object.values(ChecksumAlgorithm), }); + +export const video_stream_variant_codec_enum = registerEnum({ + name: 'video_stream_variant_codec_enum', + values: Object.values(VideoSegmentCodec), +}); diff --git a/server/src/schema/index.ts b/server/src/schema/index.ts index 61210a531f..f5a26cf7f6 100644 --- a/server/src/schema/index.ts +++ b/server/src/schema/index.ts @@ -78,6 +78,11 @@ import { UserMetadataAuditTable } from 'src/schema/tables/user-metadata-audit.ta import { UserMetadataTable } from 'src/schema/tables/user-metadata.table'; import { UserTable } from 'src/schema/tables/user.table'; import { VersionHistoryTable } from 'src/schema/tables/version-history.table'; +import { + VideoStreamSegmentTable, + VideoStreamSessionTable, + VideoStreamVariantTable, +} from 'src/schema/tables/video-stream.table'; import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; @Extensions(['uuid-ossp', 'unaccent', 'cube', 'earthdistance', 'pg_trgm', 'plpgsql']) @@ -136,6 +141,9 @@ export class ImmichDatabase { UserMetadataAuditTable, UserTable, VersionHistoryTable, + VideoStreamSessionTable, + VideoStreamVariantTable, + VideoStreamSegmentTable, PluginTable, PluginFilterTable, PluginActionTable, @@ -252,6 +260,10 @@ export interface DB { version_history: VersionHistoryTable; + video_stream_session: VideoStreamSessionTable; + video_stream_variant: VideoStreamVariantTable; + video_stream_segment: VideoStreamSegmentTable; + plugin: PluginTable; plugin_filter: PluginFilterTable; plugin_action: PluginActionTable; diff --git a/server/src/schema/migrations/1744910873969-InitialMigration.ts b/server/src/schema/migrations/1744910873969-InitialMigration.ts index 530b084f83..9611d878da 100644 --- a/server/src/schema/migrations/1744910873969-InitialMigration.ts +++ b/server/src/schema/migrations/1744910873969-InitialMigration.ts @@ -1,6 +1,5 @@ import { Kysely, sql } from 'kysely'; import { ErrorMessages } from 'src/constants'; -import { DatabaseExtension } from 'src/enum'; import { getVectorExtension } from 'src/repositories/database.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { vectorIndexQuery } from 'src/utils/database'; @@ -107,9 +106,6 @@ export async function up(db: Kysely): Promise { RETURN NULL; END; $$;`.execute(db); - if (vectorExtension === DatabaseExtension.Vectors) { - await sql`SET search_path TO "$user", public, vectors`.execute(db); - } await sql`CREATE TYPE "assets_status_enum" AS ENUM ('active','trashed','deleted');`.execute(db); await sql`CREATE TYPE "sourcetype" AS ENUM ('machine-learning','exif','manual');`.execute(db); await sql`CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "email" character varying NOT NULL, "password" character varying NOT NULL DEFAULT '', "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "profileImagePath" character varying NOT NULL DEFAULT '', "isAdmin" boolean NOT NULL DEFAULT false, "shouldChangePassword" boolean NOT NULL DEFAULT true, "deletedAt" timestamp with time zone, "oauthId" character varying NOT NULL DEFAULT '', "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "storageLabel" character varying, "name" character varying NOT NULL DEFAULT '', "quotaSizeInBytes" bigint, "quotaUsageInBytes" bigint NOT NULL DEFAULT 0, "status" character varying NOT NULL DEFAULT 'active', "profileChangedAt" timestamp with time zone NOT NULL DEFAULT now(), "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( diff --git a/server/src/schema/migrations/1776735180298-ChangeDurationToInteger.ts b/server/src/schema/migrations/1776735180298-ChangeDurationToInteger.ts new file mode 100644 index 0000000000..61f7f06b06 --- /dev/null +++ b/server/src/schema/migrations/1776735180298-ChangeDurationToInteger.ts @@ -0,0 +1,31 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql` + ALTER TABLE asset + ALTER COLUMN duration TYPE integer + USING ( + CASE + WHEN duration ~ '^\\d{2}:\\d{2}:\\d{2}\\.\\d{3}$' + THEN substr(duration, 1, 2)::int * 3600000 + + substr(duration, 4, 2)::int * 60000 + + substr(duration, 7, 2)::int * 1000 + + substr(duration, 10, 3)::int + END + );`.execute(db); +} + +export async function down(db: Kysely): Promise { + await sql` + ALTER TABLE asset + ALTER COLUMN duration TYPE varchar + USING ( + CASE + WHEN duration IS NULL THEN NULL + ELSE lpad((duration / 3600000)::text, 2, '0') + || ':' || lpad(((duration / 60000) % 60)::text, 2, '0') + || ':' || lpad(((duration / 1000) % 60)::text, 2, '0') + || '.' || lpad((duration % 1000)::text, 3, '0') + END + );`.execute(db); +} diff --git a/server/src/schema/migrations/1777415973792-AddVideoStreamTables.ts b/server/src/schema/migrations/1777415973792-AddVideoStreamTables.ts new file mode 100644 index 0000000000..d71c17627a --- /dev/null +++ b/server/src/schema/migrations/1777415973792-AddVideoStreamTables.ts @@ -0,0 +1,40 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql`CREATE TYPE "video_stream_variant_codec_enum" AS ENUM ('av1','hevc','h264');`.execute(db); + await sql`CREATE TABLE "video_stream_session" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "assetId" uuid NOT NULL, + "expiresAt" timestamp with time zone NOT NULL, + "createdAt" timestamp with time zone NOT NULL DEFAULT now(), + CONSTRAINT "video_stream_session_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "asset" ("id") ON UPDATE NO ACTION ON DELETE CASCADE, + CONSTRAINT "video_stream_session_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "video_stream_session_assetId_idx" ON "video_stream_session" ("assetId");`.execute(db); + await sql`CREATE INDEX "video_stream_session_expiresAt_idx" ON "video_stream_session" ("expiresAt");`.execute(db); + await sql`CREATE TABLE "video_stream_variant" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "sessionId" uuid NOT NULL, + "createdAt" timestamp with time zone NOT NULL DEFAULT now(), + "bitrate" integer NOT NULL, + "codec" video_stream_variant_codec_enum NOT NULL, + "resolution" smallint NOT NULL, + CONSTRAINT "video_stream_variant_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "video_stream_session" ("id") ON UPDATE NO ACTION ON DELETE CASCADE, + CONSTRAINT "video_stream_variant_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE UNIQUE INDEX "video_stream_variant_sessionId_bitrate_resolution_codec_idx" ON "video_stream_variant" ("sessionId", "bitrate", "resolution", "codec");`.execute(db); + await sql`CREATE TABLE "video_stream_segment" ( + "variantId" uuid NOT NULL, + "index" integer NOT NULL, + "durationUs" integer NOT NULL, + CONSTRAINT "video_stream_segment_variantId_fkey" FOREIGN KEY ("variantId") REFERENCES "video_stream_variant" ("id") ON UPDATE NO ACTION ON DELETE CASCADE, + CONSTRAINT "video_stream_segment_pkey" PRIMARY KEY ("variantId", "index") +);`.execute(db); +} + +export async function down(db: Kysely): Promise { + await sql`DROP TABLE "video_stream_segment";`.execute(db); + await sql`DROP TABLE "video_stream_variant";`.execute(db); + await sql`DROP TABLE "video_stream_session";`.execute(db); + await sql`DROP TYPE "asset_checksum_algorithm_enum";`.execute(db); +} diff --git a/server/src/schema/tables/asset.table.ts b/server/src/schema/tables/asset.table.ts index 718c19be5a..d4832648dd 100644 --- a/server/src/schema/tables/asset.table.ts +++ b/server/src/schema/tables/asset.table.ts @@ -83,8 +83,8 @@ export class AssetTable { @Column({ type: 'boolean', default: false }) isFavorite!: Generated; - @Column({ type: 'character varying', nullable: true }) - duration!: string | null; + @Column({ type: 'integer', nullable: true }) + duration!: number | null; @Column({ type: 'bytea', index: true }) checksum!: Buffer; // sha1 checksum diff --git a/server/src/schema/tables/video-stream.table.ts b/server/src/schema/tables/video-stream.table.ts new file mode 100644 index 0000000000..1545b19d83 --- /dev/null +++ b/server/src/schema/tables/video-stream.table.ts @@ -0,0 +1,63 @@ +import { + Column, + CreateDateColumn, + ForeignKeyColumn, + Generated, + Index, + PrimaryColumn, + PrimaryGeneratedColumn, + Table, + Timestamp, +} from '@immich/sql-tools'; +import { VideoSegmentCodec } from 'src/enum'; +import { video_stream_variant_codec_enum } from 'src/schema/enums'; +import { AssetTable } from 'src/schema/tables/asset.table'; + +@Table('video_stream_session') +export class VideoStreamSessionTable { + @PrimaryGeneratedColumn() + id!: Generated; + + @ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE' }) + assetId!: string; + + @Column({ type: 'timestamp with time zone', index: true }) + expiresAt!: Timestamp; + + @CreateDateColumn() + createdAt!: Generated; +} + +@Index({ columns: ['sessionId', 'bitrate', 'resolution', 'codec'], unique: true }) +@Table('video_stream_variant') +export class VideoStreamVariantTable { + @PrimaryGeneratedColumn() + id!: Generated; + + @ForeignKeyColumn(() => VideoStreamSessionTable, { onDelete: 'CASCADE', index: false }) + sessionId!: string; + + @CreateDateColumn() + createdAt!: Generated; + + @Column({ type: 'integer' }) + bitrate!: number; + + @Column({ enum: video_stream_variant_codec_enum }) + codec!: VideoSegmentCodec; + + @Column({ type: 'smallint' }) + resolution!: number; +} + +@Table('video_stream_segment') +export class VideoStreamSegmentTable { + @ForeignKeyColumn(() => VideoStreamVariantTable, { onDelete: 'CASCADE', primary: true, index: false }) + variantId!: string; + + @PrimaryColumn({ type: 'integer' }) + index!: number; + + @Column({ type: 'integer' }) + durationUs!: number; +} diff --git a/server/src/services/album.service.spec.ts b/server/src/services/album.service.spec.ts index 1d61272d5c..24e28a9701 100644 --- a/server/src/services/album.service.spec.ts +++ b/server/src/services/album.service.spec.ts @@ -196,6 +196,7 @@ describe(AlbumService.name, () => { expect(mocks.user.get).toHaveBeenCalledWith(albumUser.userId, {}); expect(mocks.user.getMetadata).toHaveBeenCalledWith(owner.id); expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([assetId]), false); + expect(mocks.event.emit).toHaveBeenCalledTimes(1); expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { id: album.id, userId: albumUser.userId, diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index ef8a31dcb5..24b0668eb2 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -114,7 +114,6 @@ export class AlbumService extends BaseService { throw new BadRequestException('Cannot share album with owner'); } } - albumUsers.unshift({ userId: auth.user.id, role: AlbumUserRole.Owner }); const allowedAssetIdsSet = await this.checkAccess({ auth, @@ -133,7 +132,7 @@ export class AlbumService extends BaseService { order: getPreferences(userMetadata).albums.defaultAssetOrder, }, assetIds, - albumUsers, + [{ userId: auth.user.id, role: AlbumUserRole.Owner }, ...albumUsers], auth.user.id, ); diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index 4b02d6e944..dc402592cd 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -53,6 +53,7 @@ import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TrashRepository } from 'src/repositories/trash.repository'; import { UserRepository } from 'src/repositories/user.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; +import { VideoStreamRepository } from 'src/repositories/video-stream.repository'; import { ViewRepository } from 'src/repositories/view-repository'; import { WebsocketRepository } from 'src/repositories/websocket.repository'; import { WorkflowRepository } from 'src/repositories/workflow.repository'; @@ -109,6 +110,7 @@ export const BASE_SERVICE_DEPENDENCIES = [ TrashRepository, UserRepository, VersionHistoryRepository, + VideoStreamRepository, ViewRepository, WebsocketRepository, WorkflowRepository, @@ -167,6 +169,7 @@ export class BaseService { protected trashRepository: TrashRepository, protected userRepository: UserRepository, protected versionRepository: VersionHistoryRepository, + protected videoStreamRepository: VideoStreamRepository, protected viewRepository: ViewRepository, protected websocketRepository: WebsocketRepository, protected workflowRepository: WorkflowRepository, diff --git a/server/src/services/database.service.spec.ts b/server/src/services/database.service.spec.ts index bae3a705a4..c735d42c5d 100644 --- a/server/src/services/database.service.spec.ts +++ b/server/src/services/database.service.spec.ts @@ -2,7 +2,7 @@ import { EXTENSION_NAMES } from 'src/constants'; import { DatabaseExtension, VectorIndex } from 'src/enum'; import { DatabaseService } from 'src/services/database.service'; import { VectorExtension } from 'src/types'; -import { mockEnvData } from 'test/repositories/config.repository.mock'; +import { envData, mockEnvData } from 'test/repositories/config.repository.mock'; import { newTestService, ServiceMocks } from 'test/utils'; describe(DatabaseService.name, () => { @@ -55,7 +55,6 @@ describe(DatabaseService.name, () => { describe.each(>[ { extension: DatabaseExtension.Vector, extensionName: EXTENSION_NAMES[DatabaseExtension.Vector] }, - { extension: DatabaseExtension.Vectors, extensionName: EXTENSION_NAMES[DatabaseExtension.Vectors] }, { extension: DatabaseExtension.VectorChord, extensionName: EXTENSION_NAMES[DatabaseExtension.VectorChord] }, ])('should work with $extensionName', ({ extension, extensionName }) => { beforeEach(() => { @@ -68,20 +67,7 @@ describe(DatabaseService.name, () => { ]); mocks.database.getVectorExtension.mockResolvedValue(extension); mocks.config.getEnv.mockReturnValue( - mockEnvData({ - database: { - config: { - connectionType: 'parts', - host: 'database', - port: 5432, - username: 'postgres', - password: 'postgres', - database: 'immich', - }, - skipMigrations: false, - vectorExtension: extension, - }, - }), + mockEnvData({ database: { ...envData.database, vectorExtension: extension } }), ); }); @@ -157,7 +143,6 @@ describe(DatabaseService.name, () => { installedVersion: minVersionInRange, }, ]); - mocks.database.updateVectorExtension.mockResolvedValue({ restartRequired: false }); await expect(sut.onBootstrap()).resolves.toBeUndefined(); @@ -278,27 +263,6 @@ describe(DatabaseService.name, () => { expect(mocks.database.runMigrations).not.toHaveBeenCalled(); }); - it(`should warn if ${extension} extension update requires restart`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: extension, - availableVersion: updateInRange, - installedVersion: minVersionInRange, - }, - ]); - mocks.database.updateVectorExtension.mockResolvedValue({ restartRequired: true }); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.logger.warn.mock.calls).toEqual( - expect.arrayContaining([expect.arrayContaining([expect.stringContaining(extensionName)])]), - ); - - expect(mocks.database.updateVectorExtension).toHaveBeenCalledWith(extension, updateInRange); - expect(mocks.database.runMigrations).toHaveBeenCalledTimes(1); - expect(mocks.logger.fatal).not.toHaveBeenCalled(); - }); - it(`should reindex ${extension} indices if needed`, async () => { await expect(sut.onBootstrap()).resolves.toBeUndefined(); @@ -329,22 +293,7 @@ describe(DatabaseService.name, () => { }); it('should skip migrations if DB_SKIP_MIGRATIONS=true', async () => { - mocks.config.getEnv.mockReturnValue( - mockEnvData({ - database: { - config: { - connectionType: 'parts', - host: 'database', - port: 5432, - username: 'postgres', - password: 'postgres', - database: 'immich', - }, - skipMigrations: true, - vectorExtension: DatabaseExtension.Vectors, - }, - }), - ); + mocks.config.getEnv.mockReturnValue(mockEnvData({ database: { ...envData.database, skipMigrations: true } })); await expect(sut.onBootstrap()).resolves.toBeUndefined(); @@ -352,7 +301,6 @@ describe(DatabaseService.name, () => { }); it(`should throw error if extension could not be created`, async () => { - mocks.database.updateVectorExtension.mockResolvedValue({ restartRequired: false }); mocks.database.createExtension.mockRejectedValue(new Error('Failed to create extension')); await expect(sut.onBootstrap()).rejects.toThrow('Failed to create extension'); @@ -365,35 +313,42 @@ describe(DatabaseService.name, () => { }); it(`should drop unused extension`, async () => { + mocks.config.getEnv.mockReturnValue( + mockEnvData({ database: { ...envData.database, vectorExtension: DatabaseExtension.Vector } }), + ); + mocks.database.getVectorExtension.mockResolvedValue(DatabaseExtension.Vector); mocks.database.getExtensionVersions.mockResolvedValue([ { - name: DatabaseExtension.Vectors, + name: DatabaseExtension.Vector, installedVersion: minVersionInRange, availableVersion: minVersionInRange, }, { name: DatabaseExtension.VectorChord, - installedVersion: null, + installedVersion: minVersionInRange, availableVersion: minVersionInRange, }, ]); await expect(sut.onBootstrap()).resolves.toBeUndefined(); - expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord); - expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.Vectors); + expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord); }); it(`should warn if unused extension could not be dropped`, async () => { + mocks.config.getEnv.mockReturnValue( + mockEnvData({ database: { ...envData.database, vectorExtension: DatabaseExtension.Vector } }), + ); + mocks.database.getVectorExtension.mockResolvedValue(DatabaseExtension.Vector); mocks.database.getExtensionVersions.mockResolvedValue([ { - name: DatabaseExtension.Vectors, + name: DatabaseExtension.Vector, installedVersion: minVersionInRange, availableVersion: minVersionInRange, }, { name: DatabaseExtension.VectorChord, - installedVersion: null, + installedVersion: minVersionInRange, availableVersion: minVersionInRange, }, ]); @@ -401,10 +356,9 @@ describe(DatabaseService.name, () => { await expect(sut.onBootstrap()).resolves.toBeUndefined(); - expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord); - expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.Vectors); + expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord); expect(mocks.logger.warn).toHaveBeenCalledTimes(1); - expect(mocks.logger.warn.mock.calls[0][0]).toContain('DROP EXTENSION vectors'); + expect(mocks.logger.warn.mock.calls[0][0]).toContain('DROP EXTENSION vchord'); }); it(`should not try to drop pgvector when using vectorchord`, async () => { @@ -426,21 +380,5 @@ describe(DatabaseService.name, () => { expect(mocks.database.dropExtension).not.toHaveBeenCalled(); }); - - it(`should warn if using pgvecto.rs`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: DatabaseExtension.Vectors, - installedVersion: minVersionInRange, - availableVersion: minVersionInRange, - }, - ]); - mocks.database.getVectorExtension.mockResolvedValue(DatabaseExtension.Vectors); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.logger.warn).toHaveBeenCalledTimes(1); - expect(mocks.logger.warn.mock.calls[0][0]).toContain('DEPRECATION WARNING'); - }); }); }); diff --git a/server/src/services/database.service.ts b/server/src/services/database.service.ts index 1b2289e6e3..3201f76dab 100644 --- a/server/src/services/database.service.ts +++ b/server/src/services/database.service.ts @@ -9,7 +9,6 @@ import { VectorExtension } from 'src/types'; type CreateFailedArgs = { name: string; extension: string }; type UpdateFailedArgs = { name: string; extension: string; availableVersion: string }; type DropFailedArgs = { name: string; extension: string }; -type RestartRequiredArgs = { name: string; availableVersion: string }; type NightlyVersionArgs = { name: string; extension: string; version: string }; type OutOfRangeArgs = { name: string; extension: string; version: string; range: string }; type InvalidDowngradeArgs = { name: string; extension: string; installedVersion: string; availableVersion: string }; @@ -46,16 +45,10 @@ const messages = { Please run 'DROP EXTENSION ${extension};' manually as a superuser. See https://docs.immich.app/guides/database-queries for how to query the database.`, - restartRequired: ({ name, availableVersion }: RestartRequiredArgs) => - `The ${name} extension has been updated to ${availableVersion}. - Please restart the Postgres instance to complete the update.`, invalidDowngrade: ({ name, installedVersion, availableVersion }: InvalidDowngradeArgs) => `The database currently has ${name} ${installedVersion} activated, but the Postgres instance only has ${availableVersion} available. This most likely means the extension was downgraded. If ${name} ${installedVersion} is compatible with Immich, please ensure the Postgres instance has this available.`, - deprecatedExtension: (name: string) => - `DEPRECATION WARNING: The ${name} extension is deprecated and support for it will be removed very soon. - See https://docs.immich.app/install/upgrading#migrating-to-vectorchord in order to switch to the VectorChord extension instead.`, }; @Injectable() @@ -74,9 +67,6 @@ export class DatabaseService extends BaseService { await this.databaseRepository.withLock(DatabaseLock.Migrations, async () => { const extension = await this.databaseRepository.getVectorExtension(); const name = EXTENSION_NAMES[extension]; - if (extension === DatabaseExtension.Vectors) { - this.logger.warn(messages.deprecatedExtension(name)); - } const extensionRange = this.databaseRepository.getExtensionVersionRange(extension); const extensionVersions = await this.databaseRepository.getExtensionVersions(VECTOR_EXTENSIONS); @@ -156,10 +146,7 @@ export class DatabaseService extends BaseService { private async updateExtension(extension: VectorExtension, availableVersion: string) { this.logger.log(`Updating ${EXTENSION_NAMES[extension]} extension to ${availableVersion}`); try { - const { restartRequired } = await this.databaseRepository.updateVectorExtension(extension, availableVersion); - if (restartRequired) { - this.logger.warn(messages.restartRequired({ name: EXTENSION_NAMES[extension], availableVersion })); - } + await this.databaseRepository.updateVectorExtension(extension, availableVersion); } catch (error) { this.logger.warn(messages.updateFailed({ name: EXTENSION_NAMES[extension], extension, availableVersion })); throw error; diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index 98f369c31a..8a714615c9 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -101,7 +101,7 @@ export class JobService extends BaseService { const edits = await this.assetEditRepository.getWithSyncInfo(item.data.id); if (asset) { - this.websocketRepository.clientSend('AssetEditReadyV1', asset.ownerId, { + this.websocketRepository.clientSend('AssetEditReadyV2', asset.ownerId, { asset: { id: asset.id, ownerId: asset.ownerId, @@ -156,7 +156,7 @@ export class JobService extends BaseService { this.websocketRepository.clientSend('on_upload_success', asset.ownerId, mapAsset(asset)); if (asset.exifInfo) { const exif = asset.exifInfo; - this.websocketRepository.clientSend('AssetUploadReadyV1', asset.ownerId, { + this.websocketRepository.clientSend('AssetUploadReadyV2', asset.ownerId, { // TODO remove `on_upload_success` and then modify the query to select only the required fields) asset: { id: asset.id, diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index 245bb441a6..b796094bb5 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -999,7 +999,7 @@ describe(MetadataService.name, () => { expect(mocks.asset.update).toHaveBeenCalledWith( expect.objectContaining({ id: asset.id, - duration: '00:00:06.210', + duration: 6210, }), ); }); @@ -1067,7 +1067,7 @@ describe(MetadataService.name, () => { expect(mocks.asset.update).toHaveBeenCalledWith( expect.objectContaining({ id: asset.id, - duration: '168:00:00.000', + duration: 604_800_000, }), ); }); @@ -1080,7 +1080,7 @@ describe(MetadataService.name, () => { await sut.handleMetadataExtraction({ id: asset.id }); expect(mocks.metadata.readTags).toHaveBeenCalledTimes(1); - expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: '00:02:03.000' })); + expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: 123_000 })); }); it('should prefer Duration from exif over sidecar', async () => { @@ -1092,7 +1092,7 @@ describe(MetadataService.name, () => { await sut.handleMetadataExtraction({ id: asset.id }); expect(mocks.metadata.readTags).toHaveBeenCalledTimes(2); - expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: '00:02:03.000' })); + expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: 123_000 })); }); it('should ignore all Duration tags for definitely static images', async () => { @@ -1121,7 +1121,7 @@ describe(MetadataService.name, () => { await sut.handleMetadataExtraction({ id: asset.id }); expect(mocks.metadata.readTags).toHaveBeenCalledTimes(1); - expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: '00:07:36.000' })); + expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: 456_000 })); }); it('should trim whitespace from description', async () => { diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index c548d94c74..168f7634fd 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -1001,18 +1001,10 @@ export class MetadataService extends BaseService { return bitsPerSample; } - private getDuration(tags: ImmichTags): string | null { + private getDuration(tags: ImmichTags): number | null { const duration = tags.Duration; - - if (typeof duration === 'string') { - return duration; - } - - if (typeof duration === 'number') { - return Duration.fromObject({ seconds: duration }).toFormat('hh:mm:ss.SSS'); - } - - return null; + const seconds = typeof duration === 'number' ? duration : Number.parseFloat(duration as string); + return Number.isFinite(seconds) ? Math.round(Duration.fromObject({ seconds }).toMillis()) : null; } private async getVideoTags(originalPath: string) { diff --git a/server/src/services/sync.service.ts b/server/src/services/sync.service.ts index 91978a5dc7..2feb5a0b76 100644 --- a/server/src/services/sync.service.ts +++ b/server/src/services/sync.service.ts @@ -8,8 +8,7 @@ import { SyncAckDeleteDto, SyncAckSetDto, syncAlbumV2ToV1, - syncAssetFaceV2ToV1, - SyncAssetV1, + SyncAssetV2, SyncItem, SyncStreamDto, } from 'src/dtos/sync.dto'; @@ -22,7 +21,7 @@ import { hexOrBufferToBase64 } from 'src/utils/bytes'; import { fromAck, serialize, SerializeOptions, toAck } from 'src/utils/sync'; type CheckpointMap = Partial>; -type AssetLike = Omit & { +type AssetLike = Omit & { checksum: Buffer; thumbhash: Buffer | null; }; @@ -31,7 +30,7 @@ const COMPLETE_ID = 'complete'; const MAX_DAYS = 30; const MAX_DURATION = Duration.fromObject({ days: MAX_DAYS }); -const mapSyncAssetV1 = ({ checksum, thumbhash, ...data }: AssetLike): SyncAssetV1 => ({ +const mapSyncAssetV2 = ({ checksum, thumbhash, ...data }: AssetLike): SyncAssetV2 => ({ ...data, checksum: hexOrBufferToBase64(checksum), thumbhash: thumbhash ? hexOrBufferToBase64(thumbhash) : null, @@ -56,10 +55,13 @@ export const SYNC_TYPES_ORDER = [ SyncRequestType.UsersV1, SyncRequestType.PartnersV1, SyncRequestType.AssetsV1, + SyncRequestType.AssetsV2, SyncRequestType.StacksV1, SyncRequestType.PartnerAssetsV1, + SyncRequestType.PartnerAssetsV2, SyncRequestType.PartnerStacksV1, SyncRequestType.AlbumAssetsV1, + SyncRequestType.AlbumAssetsV2, SyncRequestType.AlbumsV1, SyncRequestType.AlbumsV2, SyncRequestType.AlbumUsersV1, @@ -157,20 +159,26 @@ export class SyncService extends BaseService { const options: SyncQueryOptions = { nowId, userId: auth.user.id }; const handlers: Record Promise> = { + // deprecated handlers + [SyncRequestType.AssetsV1]: () => this.syncAssetsV1(), + [SyncRequestType.AssetFacesV1]: () => this.syncAssetFacesV1(), + [SyncRequestType.PartnerAssetsV1]: () => this.syncPartnerAssetsV1(), + [SyncRequestType.AlbumAssetsV1]: () => this.syncAlbumAssetsV1(), + [SyncRequestType.AuthUsersV1]: () => this.syncAuthUsersV1(options, response, checkpointMap), [SyncRequestType.UsersV1]: () => this.syncUsersV1(options, response, checkpointMap), [SyncRequestType.PartnersV1]: () => this.syncPartnersV1(options, response, checkpointMap), - [SyncRequestType.AssetsV1]: () => this.syncAssetsV1(options, response, checkpointMap), + [SyncRequestType.AssetsV2]: () => this.syncAssetsV2(options, response, checkpointMap), [SyncRequestType.AssetExifsV1]: () => this.syncAssetExifsV1(options, response, checkpointMap), [SyncRequestType.AssetEditsV1]: () => this.syncAssetEditsV1(options, response, checkpointMap), - [SyncRequestType.PartnerAssetsV1]: () => this.syncPartnerAssetsV1(options, response, checkpointMap, session.id), + [SyncRequestType.PartnerAssetsV2]: () => this.syncPartnerAssetsV2(options, response, checkpointMap, session.id), [SyncRequestType.AssetMetadataV1]: () => this.syncAssetMetadataV1(options, response, checkpointMap, auth), [SyncRequestType.PartnerAssetExifsV1]: () => this.syncPartnerAssetExifsV1(options, response, checkpointMap, session.id), [SyncRequestType.AlbumsV1]: () => this.syncAlbumsV1(options, response, checkpointMap), [SyncRequestType.AlbumsV2]: () => this.syncAlbumsV2(options, response, checkpointMap), [SyncRequestType.AlbumUsersV1]: () => this.syncAlbumUsersV1(options, response, checkpointMap, session.id), - [SyncRequestType.AlbumAssetsV1]: () => this.syncAlbumAssetsV1(options, response, checkpointMap, session.id), + [SyncRequestType.AlbumAssetsV2]: () => this.syncAlbumAssetsV2(options, response, checkpointMap, session.id), [SyncRequestType.AlbumToAssetsV1]: () => this.syncAlbumToAssetsV1(options, response, checkpointMap, session.id), [SyncRequestType.AlbumAssetExifsV1]: () => this.syncAlbumAssetExifsV1(options, response, checkpointMap, session.id), @@ -179,14 +187,13 @@ export class SyncService extends BaseService { [SyncRequestType.StacksV1]: () => this.syncStackV1(options, response, checkpointMap), [SyncRequestType.PartnerStacksV1]: () => this.syncPartnerStackV1(options, response, checkpointMap, session.id), [SyncRequestType.PeopleV1]: () => this.syncPeopleV1(options, response, checkpointMap), - [SyncRequestType.AssetFacesV1]: async () => this.syncAssetFacesV1(options, response, checkpointMap), - [SyncRequestType.AssetFacesV2]: async () => this.syncAssetFacesV2(options, response, checkpointMap), + [SyncRequestType.AssetFacesV2]: () => this.syncAssetFacesV2(options, response, checkpointMap), [SyncRequestType.UserMetadataV1]: () => this.syncUserMetadataV1(options, response, checkpointMap), [SyncRequestType.AssetOcrV1]: () => this.syncAssetOcrV1(options, response, checkpointMap, auth), - }; + } as const; for (const type of SYNC_TYPES_ORDER.filter((type) => dto.types.includes(type))) { - const handler = handlers[type]; + const handler = handlers[type as keyof typeof handlers]; await handler(); } @@ -263,21 +270,31 @@ export class SyncService extends BaseService { } } - private async syncAssetsV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { + private syncAssetsV1(): Promise { + throw new BadRequestException('SyncRequestType.AssetsV1 is deprecated, use SyncRequestType.AssetsV2 instead'); + } + + private async syncAssetsV2(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { const deleteType = SyncEntityType.AssetDeleteV1; const deletes = this.syncRepository.asset.getDeletes({ ...options, ack: checkpointMap[deleteType] }); for await (const { id, ...data } of deletes) { send(response, { type: deleteType, ids: [id], data }); } - const upsertType = SyncEntityType.AssetV1; + const upsertType = SyncEntityType.AssetV2; const upserts = this.syncRepository.asset.getUpserts({ ...options, ack: checkpointMap[upsertType] }); for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV1(data) }); + send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV2(data) }); } } - private async syncPartnerAssetsV1( + private syncPartnerAssetsV1(): Promise { + throw new BadRequestException( + 'SyncRequestType.PartnerAssetsV1 is deprecated, use SyncRequestType.PartnerAssetsV2 instead', + ); + } + + private async syncPartnerAssetsV2( options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap, @@ -289,13 +306,13 @@ export class SyncService extends BaseService { send(response, { type: deleteType, ids: [id], data }); } - const backfillType = SyncEntityType.PartnerAssetBackfillV1; + const backfillType = SyncEntityType.PartnerAssetBackfillV2; const backfillCheckpoint = checkpointMap[backfillType]; const partners = await this.syncRepository.partner.getCreatedAfter({ ...options, afterCreateId: backfillCheckpoint?.updateId, }); - const upsertType = SyncEntityType.PartnerAssetV1; + const upsertType = SyncEntityType.PartnerAssetV2; const upsertCheckpoint = checkpointMap[upsertType]; if (upsertCheckpoint) { const endId = upsertCheckpoint.updateId; @@ -316,7 +333,7 @@ export class SyncService extends BaseService { send(response, { type: backfillType, ids: [createId, updateId], - data: mapSyncAssetV1(data), + data: mapSyncAssetV2(data), }); } @@ -332,7 +349,7 @@ export class SyncService extends BaseService { const upserts = this.syncRepository.partnerAsset.getUpserts({ ...options, ack: checkpointMap[upsertType] }); for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV1(data) }); + send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV2(data) }); } } @@ -493,20 +510,26 @@ export class SyncService extends BaseService { } } - private async syncAlbumAssetsV1( + private syncAlbumAssetsV1(): Promise { + throw new BadRequestException( + 'SyncRequestType.AlbumAssetsV1 is deprecated, use SyncRequestType.AlbumAssetsV2 instead', + ); + } + + private async syncAlbumAssetsV2( options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap, sessionId: string, ) { - const backfillType = SyncEntityType.AlbumAssetBackfillV1; + const backfillType = SyncEntityType.AlbumAssetBackfillV2; const backfillCheckpoint = checkpointMap[backfillType]; const albums = await this.syncRepository.album.getCreatedAfter({ ...options, afterCreateId: backfillCheckpoint?.updateId, }); - const updateType = SyncEntityType.AlbumAssetUpdateV1; - const createType = SyncEntityType.AlbumAssetCreateV1; + const updateType = SyncEntityType.AlbumAssetUpdateV2; + const createType = SyncEntityType.AlbumAssetCreateV2; const updateCheckpoint = checkpointMap[updateType]; const createCheckpoint = checkpointMap[createType]; if (createCheckpoint) { @@ -525,7 +548,7 @@ export class SyncService extends BaseService { ); for await (const { updateId, ...data } of backfill) { - send(response, { type: backfillType, ids: [createId, updateId], data: mapSyncAssetV1(data) }); + send(response, { type: backfillType, ids: [createId, updateId], data: mapSyncAssetV2(data) }); } sendEntityBackfillCompleteAck(response, backfillType, createId); @@ -544,7 +567,7 @@ export class SyncService extends BaseService { createCheckpoint, ); for await (const { updateId, ...data } of updates) { - send(response, { type: updateType, ids: [updateId], data: mapSyncAssetV1(data) }); + send(response, { type: updateType, ids: [updateId], data: mapSyncAssetV2(data) }); } } @@ -555,12 +578,12 @@ export class SyncService extends BaseService { send(response, { type: SyncEntityType.SyncAckV1, data: {}, - ackType: SyncEntityType.AlbumAssetUpdateV1, + ackType: SyncEntityType.AlbumAssetUpdateV2, ids: [options.nowId], }); first = false; } - send(response, { type: createType, ids: [updateId], data: mapSyncAssetV1(data) }); + send(response, { type: createType, ids: [updateId], data: mapSyncAssetV2(data) }); } } @@ -805,19 +828,10 @@ export class SyncService extends BaseService { } } - private async syncAssetFacesV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { - const deleteType = SyncEntityType.AssetFaceDeleteV1; - const deletes = this.syncRepository.assetFace.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const upsertType = SyncEntityType.AssetFaceV1; - const upserts = this.syncRepository.assetFace.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - const v1 = syncAssetFaceV2ToV1(data); - send(response, { type: upsertType, ids: [updateId], data: v1 }); - } + private syncAssetFacesV1(): Promise { + throw new BadRequestException( + 'SyncRequestType.AssetFacesV1 is deprecated, use SyncRequestType.AssetFacesV2 instead', + ); } private async syncAssetFacesV2(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { diff --git a/server/src/types.ts b/server/src/types.ts index 179f9d1b61..c33b5a18ad 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -394,10 +394,6 @@ export interface ExtensionVersion { installedVersion: string | null; } -export interface VectorUpdateResult { - restartRequired: boolean; -} - export interface ImmichFile extends Express.Multer.File { uuid: string; /** sha1 hash of file */ diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts index 05b1e0b199..837aa9c8d5 100644 --- a/server/src/utils/database.ts +++ b/server/src/utils/database.ts @@ -427,16 +427,6 @@ export function vectorIndexQuery({ vectorExtension, table, indexName, lists }: V sampling_factor = 1024 $$)`; } - case DatabaseExtension.Vectors: { - return ` - CREATE INDEX IF NOT EXISTS ${indexName} ON ${table} - USING vectors (embedding vector_cos_ops) WITH (options = $$ - optimizing.optimizing_threads = 4 - [indexing.hnsw] - m = 16 - ef_construction = 300 - $$)`; - } case DatabaseExtension.Vector: { return ` CREATE INDEX IF NOT EXISTS ${indexName} ON ${table} diff --git a/server/src/utils/duplicate.spec.ts b/server/src/utils/duplicate.spec.ts index d63f0d3e32..155438f1bd 100644 --- a/server/src/utils/duplicate.spec.ts +++ b/server/src/utils/duplicate.spec.ts @@ -16,7 +16,7 @@ const createAsset = ( type: AssetType.Image, thumbhash: null, localDateTime: new Date().toISOString(), - duration: '0:00:00.00000', + duration: 0, hasMetadata: true, width: 1920, height: 1080, diff --git a/server/test/medium/responses.ts b/server/test/medium/responses.ts index dcc4cdd177..2fcab5b2dc 100644 --- a/server/test/medium/responses.ts +++ b/server/test/medium/responses.ts @@ -2,68 +2,36 @@ import { expect } from 'vitest'; export const errorDto = { unauthorized: { - error: 'Unauthorized', - statusCode: 401, message: 'Authentication required', - correlationId: expect.any(String), }, forbidden: { - error: 'Forbidden', - statusCode: 403, message: expect.any(String), - correlationId: expect.any(String), }, missingPermission: (permission: string) => ({ - error: 'Forbidden', - statusCode: 403, message: `Missing required permission: ${permission}`, - correlationId: expect.any(String), }), wrongPassword: { - error: 'Bad Request', - statusCode: 400, message: 'Wrong password', - correlationId: expect.any(String), }, invalidToken: { - error: 'Unauthorized', - statusCode: 401, message: 'Invalid user token', - correlationId: expect.any(String), }, invalidShareKey: { - error: 'Unauthorized', - statusCode: 401, message: 'Invalid share key', - correlationId: expect.any(String), }, invalidSharePassword: { - error: 'Unauthorized', - statusCode: 401, message: 'Invalid password', - correlationId: expect.any(String), }, badRequest: (message: any = null) => ({ - error: 'Bad Request', - statusCode: 400, message: message ?? expect.anything(), }), noPermission: { - error: 'Bad Request', - statusCode: 400, message: expect.stringContaining('Not found or no'), - correlationId: expect.any(String), }, incorrectLogin: { - error: 'Unauthorized', - statusCode: 401, message: 'Incorrect email or password', - correlationId: expect.any(String), }, alreadyHasAdmin: { - error: 'Bad Request', - statusCode: 400, message: 'The server already has an admin', - correlationId: expect.any(String), }, }; diff --git a/server/test/medium/specs/sync/sync-album-asset.spec.ts b/server/test/medium/specs/sync/sync-album-asset.spec.ts index 123b6f9484..1418f35589 100644 --- a/server/test/medium/specs/sync/sync-album-asset.spec.ts +++ b/server/test/medium/specs/sync/sync-album-asset.spec.ts @@ -15,13 +15,13 @@ const setup = async (db?: Kysely) => { }; const updateSyncAck = { - ack: expect.stringContaining(SyncEntityType.AlbumAssetUpdateV1), + ack: expect.stringContaining(SyncEntityType.AlbumAssetUpdateV2), data: {}, type: SyncEntityType.SyncAckV1, }; const backfillSyncAck = { - ack: expect.stringContaining(SyncEntityType.AlbumAssetBackfillV1), + ack: expect.stringContaining(SyncEntityType.AlbumAssetBackfillV2), data: {}, type: SyncEntityType.SyncAckV1, }; @@ -30,7 +30,7 @@ beforeAll(async () => { defaultDatabase = await getKyselyDB(); }); -describe(SyncRequestType.AlbumAssetsV1, () => { +describe(SyncRequestType.AlbumAssetsV2, () => { it('should detect and sync the first album asset', async () => { const originalFileName = 'firstAsset'; const checksum = '1115vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA='; @@ -48,7 +48,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => { fileModifiedAt: date, localDateTime: date, deletedAt: null, - duration: '0:10:00.00000', + duration: 600_000, livePhotoVideoId: null, stackId: null, libraryId: null, @@ -59,7 +59,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => { await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id }); await ctx.newAlbumUser({ albumId: album.id, userId: auth.user.id, role: AlbumUserRole.Editor }); - const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]); expect(response).toEqual([ updateSyncAck, { @@ -85,13 +85,13 @@ describe(SyncRequestType.AlbumAssetsV1, () => { height: asset.height, isEdited: asset.isEdited, }, - type: SyncEntityType.AlbumAssetCreateV1, + type: SyncEntityType.AlbumAssetCreateV2, }, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); await ctx.syncAckAll(auth, response); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV2]); }); it('should sync album asset for own user', async () => { @@ -100,13 +100,13 @@ describe(SyncRequestType.AlbumAssetsV1, () => { const { album } = await ctx.newAlbum({ ownerId: auth.user.id }); await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id }); - await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([ - expect.objectContaining({ type: SyncEntityType.AssetV1 }), + await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([ + expect.objectContaining({ type: SyncEntityType.AssetV2 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); - await expect(ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1])).resolves.toEqual([ + await expect(ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2])).resolves.toEqual([ expect.objectContaining({ type: SyncEntityType.SyncAckV1 }), - expect.objectContaining({ type: SyncEntityType.AlbumAssetCreateV1 }), + expect.objectContaining({ type: SyncEntityType.AlbumAssetCreateV2 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); }); @@ -122,11 +122,11 @@ describe(SyncRequestType.AlbumAssetsV1, () => { const { session } = await ctx.newSession({ userId: user3.id }); const authUser3 = factory.auth({ session, user: user3 }); - await expect(ctx.syncStream(authUser3, [SyncRequestType.AssetsV1])).resolves.toEqual([ - expect.objectContaining({ type: SyncEntityType.AssetV1 }), + await expect(ctx.syncStream(authUser3, [SyncRequestType.AssetsV2])).resolves.toEqual([ + expect.objectContaining({ type: SyncEntityType.AssetV2 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV2]); }); it('should backfill album assets when a user shares an album with you', async () => { @@ -147,7 +147,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => { await wait(2); await ctx.newAlbumUser({ albumId: album1.id, userId: auth.user.id, role: AlbumUserRole.Editor }); - const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]); expect(response).toEqual([ updateSyncAck, { @@ -155,7 +155,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => { data: expect.objectContaining({ id: asset2User2.id, }), - type: SyncEntityType.AlbumAssetCreateV1, + type: SyncEntityType.AlbumAssetCreateV2, }, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); @@ -166,21 +166,21 @@ describe(SyncRequestType.AlbumAssetsV1, () => { await ctx.newAlbumUser({ albumId: album2.id, userId: auth.user.id, role: AlbumUserRole.Editor }); // should backfill the album user - const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]); + const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]); expect(newResponse).toEqual([ { ack: expect.any(String), data: expect.objectContaining({ id: asset1User2.id, }), - type: SyncEntityType.AlbumAssetBackfillV1, + type: SyncEntityType.AlbumAssetBackfillV2, }, { ack: expect.any(String), data: expect.objectContaining({ id: asset2User2.id, }), - type: SyncEntityType.AlbumAssetBackfillV1, + type: SyncEntityType.AlbumAssetBackfillV2, }, backfillSyncAck, updateSyncAck, @@ -189,13 +189,13 @@ describe(SyncRequestType.AlbumAssetsV1, () => { data: expect.objectContaining({ id: asset3User2.id, }), - type: SyncEntityType.AlbumAssetCreateV1, + type: SyncEntityType.AlbumAssetCreateV2, }, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); await ctx.syncAckAll(auth, newResponse); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV2]); }); it('should sync old assets when a user adds them to an album they share you', async () => { @@ -211,7 +211,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => { await ctx.newAlbumAsset({ albumId: album1.id, assetId: album1Asset.id }); await ctx.newAlbumUser({ albumId: album1.id, userId: auth.user.id, role: AlbumUserRole.Editor }); - const firstAlbumResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]); + const firstAlbumResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]); expect(firstAlbumResponse).toEqual([ updateSyncAck, { @@ -219,7 +219,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => { data: expect.objectContaining({ id: album1Asset.id, }), - type: SyncEntityType.AlbumAssetCreateV1, + type: SyncEntityType.AlbumAssetCreateV2, }, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); @@ -228,14 +228,14 @@ describe(SyncRequestType.AlbumAssetsV1, () => { await ctx.newAlbumUser({ albumId: album2.id, userId: auth.user.id, role: AlbumUserRole.Editor }); - const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]); expect(response).toEqual([ { ack: expect.any(String), data: expect.objectContaining({ id: firstAsset.id, }), - type: SyncEntityType.AlbumAssetBackfillV1, + type: SyncEntityType.AlbumAssetBackfillV2, }, backfillSyncAck, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), @@ -248,7 +248,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => { await wait(2); // should backfill the new asset even though it's older than the first asset - const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]); + const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]); expect(newResponse).toEqual([ updateSyncAck, { @@ -256,13 +256,13 @@ describe(SyncRequestType.AlbumAssetsV1, () => { data: expect.objectContaining({ id: secondAsset.id, }), - type: SyncEntityType.AlbumAssetCreateV1, + type: SyncEntityType.AlbumAssetCreateV2, }, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); await ctx.syncAckAll(auth, newResponse); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV2]); }); it('should sync asset updates for an album shared with you', async () => { @@ -274,7 +274,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => { await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id }); await ctx.newAlbumUser({ albumId: album.id, userId: auth.user.id, role: AlbumUserRole.Editor }); - const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]); expect(response).toEqual([ updateSyncAck, { @@ -282,7 +282,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => { data: expect.objectContaining({ id: asset.id, }), - type: SyncEntityType.AlbumAssetCreateV1, + type: SyncEntityType.AlbumAssetCreateV2, }, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); @@ -296,7 +296,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => { isFavorite: true, }); - const updateResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]); + const updateResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]); expect(updateResponse).toEqual([ { ack: expect.any(String), @@ -304,7 +304,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => { id: asset.id, isFavorite: true, }), - type: SyncEntityType.AlbumAssetUpdateV1, + type: SyncEntityType.AlbumAssetUpdateV2, }, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); diff --git a/server/test/medium/specs/sync/sync-asset-face.spec.ts b/server/test/medium/specs/sync/sync-asset-face.spec.ts index 34a1e8e73c..74d4c536f1 100644 --- a/server/test/medium/specs/sync/sync-asset-face.spec.ts +++ b/server/test/medium/specs/sync/sync-asset-face.spec.ts @@ -18,14 +18,14 @@ beforeAll(async () => { defaultDatabase = await getKyselyDB(); }); -describe(SyncEntityType.AssetFaceV1, () => { +describe(SyncEntityType.AssetFaceV2, () => { it('should detect and sync the first asset face', async () => { const { auth, ctx } = await setup(); const { asset } = await ctx.newAsset({ ownerId: auth.user.id }); const { person } = await ctx.newPerson({ ownerId: auth.user.id }); const { assetFace } = await ctx.newAssetFace({ assetId: asset.id, personId: person.id }); - const response = await ctx.syncStream(auth, [SyncRequestType.AssetFacesV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.AssetFacesV2]); expect(response).toEqual([ { ack: expect.any(String), @@ -41,13 +41,13 @@ describe(SyncEntityType.AssetFaceV1, () => { boundingBoxY2: assetFace.boundingBoxY2, sourceType: assetFace.sourceType, }), - type: 'AssetFaceV1', + type: 'AssetFaceV2', }, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); await ctx.syncAckAll(auth, response); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV2]); }); it('should detect and sync a deleted asset face', async () => { @@ -57,7 +57,7 @@ describe(SyncEntityType.AssetFaceV1, () => { const { assetFace } = await ctx.newAssetFace({ assetId: asset.id }); await personRepo.deleteAssetFace(assetFace.id); - const response = await ctx.syncStream(auth, [SyncRequestType.AssetFacesV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.AssetFacesV2]); expect(response).toEqual([ { ack: expect.any(String), @@ -70,7 +70,7 @@ describe(SyncEntityType.AssetFaceV1, () => { ]); await ctx.syncAckAll(auth, response); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV2]); }); it('should not sync an asset face or asset face delete for an unrelated user', async () => { @@ -82,19 +82,19 @@ describe(SyncEntityType.AssetFaceV1, () => { const { assetFace } = await ctx.newAssetFace({ assetId: asset.id }); const auth2 = factory.auth({ session, user: user2 }); - expect(await ctx.syncStream(auth2, [SyncRequestType.AssetFacesV1])).toEqual([ - expect.objectContaining({ type: SyncEntityType.AssetFaceV1 }), + expect(await ctx.syncStream(auth2, [SyncRequestType.AssetFacesV2])).toEqual([ + expect.objectContaining({ type: SyncEntityType.AssetFaceV2 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV2]); await personRepo.deleteAssetFace(assetFace.id); - expect(await ctx.syncStream(auth2, [SyncRequestType.AssetFacesV1])).toEqual([ + expect(await ctx.syncStream(auth2, [SyncRequestType.AssetFacesV2])).toEqual([ expect.objectContaining({ type: SyncEntityType.AssetFaceDeleteV1 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV2]); }); }); diff --git a/server/test/medium/specs/sync/sync-asset.spec.ts b/server/test/medium/specs/sync/sync-asset.spec.ts index a1a898d9b3..8b06036854 100644 --- a/server/test/medium/specs/sync/sync-asset.spec.ts +++ b/server/test/medium/specs/sync/sync-asset.spec.ts @@ -18,7 +18,7 @@ beforeAll(async () => { defaultDatabase = await getKyselyDB(); }); -describe(SyncEntityType.AssetV1, () => { +describe(SyncEntityType.AssetV2, () => { it('should detect and sync the first asset', async () => { const originalFileName = 'firstAsset'; const checksum = '1115vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA='; @@ -35,13 +35,13 @@ describe(SyncEntityType.AssetV1, () => { fileModifiedAt: date, localDateTime: date, deletedAt: null, - duration: '0:10:00.00000', + duration: 600_000, libraryId: null, width: 1920, height: 1080, }); - const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]); expect(response).toEqual([ { ack: expect.any(String), @@ -66,13 +66,13 @@ describe(SyncEntityType.AssetV1, () => { height: asset.height, isEdited: asset.isEdited, }, - type: 'AssetV1', + type: 'AssetV2', }, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); await ctx.syncAckAll(auth, response); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]); }); it('should detect and sync a deleted asset', async () => { @@ -81,7 +81,7 @@ describe(SyncEntityType.AssetV1, () => { const { asset } = await ctx.newAsset({ ownerId: auth.user.id }); await assetRepo.remove(asset); - const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]); expect(response).toEqual([ { ack: expect.any(String), @@ -94,7 +94,7 @@ describe(SyncEntityType.AssetV1, () => { ]); await ctx.syncAckAll(auth, response); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]); }); it('should not sync an asset or asset delete for an unrelated user', async () => { @@ -105,17 +105,17 @@ describe(SyncEntityType.AssetV1, () => { const { asset } = await ctx.newAsset({ ownerId: user2.id }); const auth2 = factory.auth({ session, user: user2 }); - expect(await ctx.syncStream(auth2, [SyncRequestType.AssetsV1])).toEqual([ - expect.objectContaining({ type: SyncEntityType.AssetV1 }), + expect(await ctx.syncStream(auth2, [SyncRequestType.AssetsV2])).toEqual([ + expect.objectContaining({ type: SyncEntityType.AssetV2 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]); await assetRepo.remove(asset); - expect(await ctx.syncStream(auth2, [SyncRequestType.AssetsV1])).toEqual([ + expect(await ctx.syncStream(auth2, [SyncRequestType.AssetsV2])).toEqual([ expect.objectContaining({ type: SyncEntityType.AssetDeleteV1 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]); }); }); diff --git a/server/test/medium/specs/sync/sync-complete.spec.ts b/server/test/medium/specs/sync/sync-complete.spec.ts index 8a94061631..95beb7b294 100644 --- a/server/test/medium/specs/sync/sync-complete.spec.ts +++ b/server/test/medium/specs/sync/sync-complete.spec.ts @@ -24,7 +24,7 @@ describe(SyncEntityType.SyncCompleteV1, () => { it('should work', async () => { const { auth, ctx } = await setup(); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]); }); it('should detect an old checkpoint and send back a reset', async () => { @@ -39,7 +39,7 @@ describe(SyncEntityType.SyncCompleteV1, () => { }, ]); - const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]); expect(response).toEqual([{ type: SyncEntityType.SyncResetV1, data: {}, ack: 'SyncResetV1|reset' }]); }); @@ -55,6 +55,6 @@ describe(SyncEntityType.SyncCompleteV1, () => { }, ]); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]); }); }); diff --git a/server/test/medium/specs/sync/sync-partner-asset.spec.ts b/server/test/medium/specs/sync/sync-partner-asset.spec.ts index 345d4a1e29..face352970 100644 --- a/server/test/medium/specs/sync/sync-partner-asset.spec.ts +++ b/server/test/medium/specs/sync/sync-partner-asset.spec.ts @@ -20,7 +20,7 @@ beforeAll(async () => { defaultDatabase = await getKyselyDB(); }); -describe(SyncRequestType.PartnerAssetsV1, () => { +describe(SyncRequestType.PartnerAssetsV2, () => { it('should detect and sync the first partner asset', async () => { const { auth, ctx } = await setup(); @@ -39,13 +39,13 @@ describe(SyncRequestType.PartnerAssetsV1, () => { fileModifiedAt: date, localDateTime: date, deletedAt: null, - duration: '0:10:00.00000', + duration: 600_000, libraryId: null, }); await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id }); - const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]); expect(response).toEqual([ { ack: expect.any(String), @@ -70,13 +70,13 @@ describe(SyncRequestType.PartnerAssetsV1, () => { width: null, height: null, }, - type: SyncEntityType.PartnerAssetV1, + type: SyncEntityType.PartnerAssetV2, }, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); await ctx.syncAckAll(auth, response); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]); }); it('should detect and sync a deleted partner asset', async () => { @@ -88,7 +88,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => { await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id }); await assetRepo.remove(asset); - const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]); expect(response).toEqual([ { ack: expect.any(String), @@ -101,7 +101,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => { ]); await ctx.syncAckAll(auth, response); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]); }); it('should not sync a deleted partner asset due to a user delete', async () => { @@ -112,7 +112,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => { await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id }); await ctx.newAsset({ ownerId: user2.id }); await userRepo.delete({ id: user2.id }, true); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]); }); it('should not sync a deleted partner asset due to a partner delete (unshare)', async () => { @@ -122,12 +122,12 @@ describe(SyncRequestType.PartnerAssetsV1, () => { const { user: user2 } = await ctx.newUser(); await ctx.newAsset({ ownerId: user2.id }); const { partner } = await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id }); - await expect(ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toEqual([ - expect.objectContaining({ type: SyncEntityType.PartnerAssetV1 }), + await expect(ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2])).resolves.toEqual([ + expect.objectContaining({ type: SyncEntityType.PartnerAssetV2 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); await partnerRepo.remove(partner); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]); }); it('should not sync an asset or asset delete for own user', async () => { @@ -138,19 +138,19 @@ describe(SyncRequestType.PartnerAssetsV1, () => { const { asset } = await ctx.newAsset({ ownerId: auth.user.id }); await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id }); - await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([ - expect.objectContaining({ type: SyncEntityType.AssetV1 }), + await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([ + expect.objectContaining({ type: SyncEntityType.AssetV2 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]); await assetRepo.remove(asset); - await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([ + await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([ expect.objectContaining({ type: SyncEntityType.AssetDeleteV1 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]); }); it('should not sync an asset or asset delete for unrelated user', async () => { @@ -162,19 +162,19 @@ describe(SyncRequestType.PartnerAssetsV1, () => { const { asset } = await ctx.newAsset({ ownerId: user2.id }); const auth2 = factory.auth({ session, user: user2 }); - await expect(ctx.syncStream(auth2, [SyncRequestType.AssetsV1])).resolves.toEqual([ - expect.objectContaining({ type: SyncEntityType.AssetV1 }), + await expect(ctx.syncStream(auth2, [SyncRequestType.AssetsV2])).resolves.toEqual([ + expect.objectContaining({ type: SyncEntityType.AssetV2 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]); await assetRepo.remove(asset); - await expect(ctx.syncStream(auth2, [SyncRequestType.AssetsV1])).resolves.toEqual([ + await expect(ctx.syncStream(auth2, [SyncRequestType.AssetsV2])).resolves.toEqual([ expect.objectContaining({ type: SyncEntityType.AssetDeleteV1 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]); }); it('should backfill partner assets when a partner shared their library with you', async () => { @@ -187,14 +187,14 @@ describe(SyncRequestType.PartnerAssetsV1, () => { const { asset: assetUser2 } = await ctx.newAsset({ ownerId: user2.id }); await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id }); - const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]); expect(response).toEqual([ { ack: expect.any(String), data: expect.objectContaining({ id: assetUser2.id, }), - type: SyncEntityType.PartnerAssetV1, + type: SyncEntityType.PartnerAssetV2, }, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); @@ -202,17 +202,17 @@ describe(SyncRequestType.PartnerAssetsV1, () => { await ctx.syncAckAll(auth, response); await ctx.newPartner({ sharedById: user3.id, sharedWithId: auth.user.id }); - const newResponse = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]); + const newResponse = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]); expect(newResponse).toEqual([ { ack: expect.any(String), data: expect.objectContaining({ id: assetUser3.id, }), - type: SyncEntityType.PartnerAssetBackfillV1, + type: SyncEntityType.PartnerAssetBackfillV2, }, { - ack: expect.stringContaining(SyncEntityType.PartnerAssetBackfillV1), + ack: expect.stringContaining(SyncEntityType.PartnerAssetBackfillV2), data: {}, type: SyncEntityType.SyncAckV1, }, @@ -220,7 +220,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => { ]); await ctx.syncAckAll(auth, newResponse); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]); }); it('should only backfill partner assets created prior to the current partner asset checkpoint', async () => { @@ -235,31 +235,31 @@ describe(SyncRequestType.PartnerAssetsV1, () => { const { asset: asset2User3 } = await ctx.newAsset({ ownerId: user3.id }); await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id }); - const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]); expect(response).toEqual([ { ack: expect.any(String), data: expect.objectContaining({ id: assetUser2.id, }), - type: SyncEntityType.PartnerAssetV1, + type: SyncEntityType.PartnerAssetV2, }, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); await ctx.syncAckAll(auth, response); await ctx.newPartner({ sharedById: user3.id, sharedWithId: auth.user.id }); - const newResponse = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]); + const newResponse = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]); expect(newResponse).toEqual([ { ack: expect.any(String), data: expect.objectContaining({ id: assetUser3.id, }), - type: SyncEntityType.PartnerAssetBackfillV1, + type: SyncEntityType.PartnerAssetBackfillV2, }, { - ack: expect.stringContaining(SyncEntityType.PartnerAssetBackfillV1), + ack: expect.stringContaining(SyncEntityType.PartnerAssetBackfillV2), data: {}, type: SyncEntityType.SyncAckV1, }, @@ -268,12 +268,12 @@ describe(SyncRequestType.PartnerAssetsV1, () => { data: expect.objectContaining({ id: asset2User3.id, }), - type: SyncEntityType.PartnerAssetV1, + type: SyncEntityType.PartnerAssetV2, }, expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); await ctx.syncAckAll(auth, newResponse); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]); }); }); diff --git a/server/test/medium/specs/sync/sync-reset.spec.ts b/server/test/medium/specs/sync/sync-reset.spec.ts index 9a4c33c1f2..b99d8d7df0 100644 --- a/server/test/medium/specs/sync/sync-reset.spec.ts +++ b/server/test/medium/specs/sync/sync-reset.spec.ts @@ -21,7 +21,7 @@ describe(SyncEntityType.SyncResetV1, () => { it('should work', async () => { const { auth, ctx } = await setup(); - await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]); }); it('should detect a pending sync reset', async () => { @@ -31,7 +31,7 @@ describe(SyncEntityType.SyncResetV1, () => { isPendingSyncReset: true, }); - const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]); expect(response).toEqual([{ type: SyncEntityType.SyncResetV1, data: {}, ack: 'SyncResetV1|reset' }]); }); @@ -40,8 +40,8 @@ describe(SyncEntityType.SyncResetV1, () => { await ctx.newAsset({ ownerId: user.id }); - await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([ - expect.objectContaining({ type: SyncEntityType.AssetV1 }), + await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([ + expect.objectContaining({ type: SyncEntityType.AssetV2 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); @@ -49,7 +49,7 @@ describe(SyncEntityType.SyncResetV1, () => { isPendingSyncReset: true, }); - await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([ + await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([ { type: SyncEntityType.SyncResetV1, data: {}, ack: 'SyncResetV1|reset' }, ]); }); @@ -63,8 +63,8 @@ describe(SyncEntityType.SyncResetV1, () => { isPendingSyncReset: true, }); - await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1], true)).resolves.toEqual([ - expect.objectContaining({ type: SyncEntityType.AssetV1 }), + await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2], true)).resolves.toEqual([ + expect.objectContaining({ type: SyncEntityType.AssetV2 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); }); @@ -74,20 +74,20 @@ describe(SyncEntityType.SyncResetV1, () => { await ctx.newAsset({ ownerId: user.id }); - const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]); + const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]); await ctx.syncAckAll(auth, response); await ctx.get(SessionRepository).update(auth.session!.id, { isPendingSyncReset: true, }); - const resetResponse = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]); + const resetResponse = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]); await ctx.syncAckAll(auth, resetResponse); - const postResetResponse = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]); + const postResetResponse = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]); expect(postResetResponse).toEqual([ - expect.objectContaining({ type: SyncEntityType.AssetV1 }), + expect.objectContaining({ type: SyncEntityType.AssetV2 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), ]); }); diff --git a/server/test/repositories/config.repository.mock.ts b/server/test/repositories/config.repository.mock.ts index 797c08c4db..9dd92603c8 100644 --- a/server/test/repositories/config.repository.mock.ts +++ b/server/test/repositories/config.repository.mock.ts @@ -3,7 +3,7 @@ import { ConfigRepository, EnvData } from 'src/repositories/config.repository'; import { RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; -const envData: EnvData = { +export const envData: EnvData = { port: 2283, environment: ImmichEnvironment.Production, logFormat: LogFormat.Console, @@ -30,9 +30,8 @@ const envData: EnvData = { username: 'postgres', password: 'postgres', }, - skipMigrations: false, - vectorExtension: DatabaseExtension.Vectors, + vectorExtension: DatabaseExtension.VectorChord, }, helmet: { diff --git a/server/test/small.factory.ts b/server/test/small.factory.ts index 656d023bae..d5e6b27e0c 100644 --- a/server/test/small.factory.ts +++ b/server/test/small.factory.ts @@ -248,8 +248,6 @@ export const factory = { date: newDate, responses: { badRequest: (message: any = null) => ({ - error: 'Bad Request', - statusCode: 400, message: message ?? expect.anything(), }), }, diff --git a/server/test/utils.ts b/server/test/utils.ts index 3c5967ae50..791a457783 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -64,6 +64,7 @@ import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TrashRepository } from 'src/repositories/trash.repository'; import { UserRepository } from 'src/repositories/user.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; +import { VideoStreamRepository } from 'src/repositories/video-stream.repository'; import { ViewRepository } from 'src/repositories/view-repository'; import { WebsocketRepository } from 'src/repositories/websocket.repository'; import { WorkflowRepository } from 'src/repositories/workflow.repository'; @@ -260,6 +261,7 @@ export type ServiceOverrides = { trash: TrashRepository; user: UserRepository; versionHistory: VersionHistoryRepository; + videoStream: VideoStreamRepository; view: ViewRepository; websocket: WebsocketRepository; workflow: WorkflowRepository; @@ -344,6 +346,7 @@ export const getMocks = () => { trash: automock(TrashRepository), user: automock(UserRepository, { strict: false }), versionHistory: automock(VersionHistoryRepository), + videoStream: automock(VideoStreamRepository), view: automock(ViewRepository), // eslint-disable-next-line no-sparse-arrays websocket: automock(WebsocketRepository, { args: [, loggerMock], strict: false }), @@ -408,6 +411,7 @@ export const newTestService = ( overrides.trash || (mocks.trash as As), overrides.user || (mocks.user as As), overrides.versionHistory || (mocks.versionHistory as As), + overrides.videoStream || (mocks.videoStream as As), overrides.view || (mocks.view as As), overrides.websocket || (mocks.websocket as As), overrides.workflow || (mocks.workflow as As), diff --git a/web/src/lib/components/SharedLinkExpiration.svelte b/web/src/lib/components/SharedLinkExpiration.svelte index bfe1241482..25ee96242a 100644 --- a/web/src/lib/components/SharedLinkExpiration.svelte +++ b/web/src/lib/components/SharedLinkExpiration.svelte @@ -35,7 +35,7 @@ const setSelectedDate = (value: DateTime | undefined) => { selectedPresetValue = null; // Clear preset when manually setting date - expiresAt = value ? value.toISO() : null; + expiresAt = value ? value.toUTC().toISO() : null; }; const selectPreset = (value: number) => { @@ -44,8 +44,8 @@ expiresAt = null; return; } - const newDate = DateTime.now().plus(value); - expiresAt = newDate.toISO(); + const newDate = DateTime.now().plus({ milliseconds: value }); + expiresAt = newDate.toUTC().toISO(); }; const isSelected = (value: number) => { diff --git a/web/src/lib/components/asset-viewer/AssetViewer.svelte b/web/src/lib/components/asset-viewer/AssetViewer.svelte index fecce4c410..834b22e299 100644 --- a/web/src/lib/components/asset-viewer/AssetViewer.svelte +++ b/web/src/lib/components/asset-viewer/AssetViewer.svelte @@ -67,7 +67,7 @@ preAction?: PreAction; onAction?: OnAction; onUndoDelete?: OnUndoDelete; - onClose?: (asset: AssetResponseDto) => void; + onClose?: (assetId: string) => void; onRemoveFromAlbum?: (assetIds: string[]) => void; onRandom?: () => Promise<{ id: string } | undefined>; } @@ -179,7 +179,7 @@ }); const closeViewer = () => { - onClose?.(asset); + onClose?.(asset.id); }; const closeEditor = async () => { @@ -474,7 +474,7 @@ onAction={handleAction} {onUndoDelete} onPlaySlideshow={() => ($slideshowState = SlideshowState.PlaySlideshow)} - onClose={onClose ? () => onClose(asset) : undefined} + onClose={onClose ? () => onClose(stack?.primaryAssetId ?? asset.id) : undefined} {onRemoveFromAlbum} {playOriginalVideo} {setPlayOriginalVideo} diff --git a/web/src/lib/components/asset-viewer/DetailPanel.svelte b/web/src/lib/components/asset-viewer/DetailPanel.svelte index f46ddee4d6..8f627a87b5 100644 --- a/web/src/lib/components/asset-viewer/DetailPanel.svelte +++ b/web/src/lib/components/asset-viewer/DetailPanel.svelte @@ -10,9 +10,8 @@ import { authManager } from '$lib/managers/auth-manager.svelte'; import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import { Route } from '$lib/route'; - import { boundingBoxesArray } from '$lib/stores/people.store'; import { locale } from '$lib/stores/preferences.store'; - import { getAssetMediaUrl, getPeopleThumbnailUrl } from '$lib/utils'; + import { getAssetMediaUrl } from '$lib/utils'; import { delay, getDimensions } from '$lib/utils/asset-utils'; import { getByteUnitString } from '$lib/utils/byte-units'; import { handleError } from '$lib/utils/handle-error'; @@ -25,26 +24,15 @@ type AssetResponseDto, } from '@immich/sdk'; import { Icon, IconButton, LoadingSpinner, Text } from '@immich/ui'; - import { - mdiCamera, - mdiCameraIris, - mdiClose, - mdiEye, - mdiEyeOff, - mdiImageOutline, - mdiInformationOutline, - mdiPencil, - mdiPlus, - } from '@mdi/js'; - import { DateTime } from 'luxon'; + import { mdiCamera, mdiCameraIris, mdiClose, mdiImageOutline, mdiInformationOutline } from '@mdi/js'; import { onDestroy } from 'svelte'; import { t } from 'svelte-i18n'; import { slide } from 'svelte/transition'; - import ImageThumbnail from '../assets/thumbnail/ImageThumbnail.svelte'; import PersonSidePanel from '../faces-page/PersonSidePanel.svelte'; import OnEvents from '../OnEvents.svelte'; import UserAvatar from '../shared-components/UserAvatar.svelte'; import AlbumListItemDetails from './AlbumListItemDetails.svelte'; + import DetailPanelPeople from '$lib/components/asset-viewer/DetailPanelPeople.svelte'; interface Props { asset: AssetResponseDto; @@ -54,9 +42,6 @@ let { asset, currentAlbum = null }: Props = $props(); let isOwner = $derived(authManager.authenticated && authManager.user.id === asset.ownerId); - let people = $derived(asset.people || []); - let unassignedFaces = $derived(asset.unassignedFaces || []); - let showingHiddenPeople = $state(false); let latlng = $derived( (() => { const lat = asset.exifInfo?.latitude; @@ -164,108 +149,7 @@ - - {#if !authManager.isSharedLink && isOwner} -
-
- {$t('people')} -
- {#if people.some((person) => person.isHidden)} - (showingHiddenPeople = !showingHiddenPeople)} - /> - {/if} - assetViewerManager.toggleFaceEditMode()} - /> - - {#if people.length > 0 || unassignedFaces.length > 0} - assetViewerManager.openEditFacesPanel()} - /> - {/if} -
-
- - -
- {/if} +
{#if asset.exifInfo} diff --git a/web/src/lib/components/asset-viewer/DetailPanelPeople.svelte b/web/src/lib/components/asset-viewer/DetailPanelPeople.svelte new file mode 100644 index 0000000000..2848e7292a --- /dev/null +++ b/web/src/lib/components/asset-viewer/DetailPanelPeople.svelte @@ -0,0 +1,133 @@ + + +{#if !authManager.isSharedLink && isOwner} +
+
+ {$t('people')} +
+ {#if people.some((person) => person.isHidden)} + assetViewerManager.toggleHiddenPeople()} + /> + {/if} + assetViewerManager.toggleFaceEditMode()} + /> + + {#if people.length > 0 || unassignedFaces.length > 0} + assetViewerManager.openEditFacesPanel()} + /> + {/if} +
+
+ + +
+{/if} diff --git a/web/src/lib/components/asset-viewer/PhotoSphereViewerAdapter.svelte b/web/src/lib/components/asset-viewer/PhotoSphereViewerAdapter.svelte index 12c4b45541..150b6e4f6b 100644 --- a/web/src/lib/components/asset-viewer/PhotoSphereViewerAdapter.svelte +++ b/web/src/lib/components/asset-viewer/PhotoSphereViewerAdapter.svelte @@ -1,9 +1,8 @@ diff --git a/web/src/lib/components/asset-viewer/PhotoViewer.svelte b/web/src/lib/components/asset-viewer/PhotoViewer.svelte index cf8ee876e4..894ee875e3 100644 --- a/web/src/lib/components/asset-viewer/PhotoViewer.svelte +++ b/web/src/lib/components/asset-viewer/PhotoViewer.svelte @@ -6,10 +6,9 @@ import Thumbhash from '$lib/components/Thumbhash.svelte'; import OcrBoundingBox from '$lib/components/asset-viewer/OcrBoundingBox.svelte'; import AssetViewerEvents from '$lib/components/AssetViewerEvents.svelte'; - import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte'; + import { assetViewerManager, type Faces } from '$lib/managers/asset-viewer-manager.svelte'; import { castManager } from '$lib/managers/cast-manager.svelte'; import { ocrManager } from '$lib/stores/ocr.svelte'; - import { boundingBoxesArray, type Faces } from '$lib/stores/people.store'; import { SlideshowLook, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { handlePromiseError } from '$lib/utils'; import { canCopyImageToClipboard, copyImageToClipboard } from '$lib/utils/asset-utils'; @@ -50,12 +49,13 @@ untrack(() => { assetViewerManager.resetZoomState(); visibleImageReady = false; - $boundingBoxesArray = []; + assetViewerManager.clearHighlightedFaces(); }); }); onDestroy(() => { - $boundingBoxesArray = []; + assetViewerManager.clearHighlightedFaces(); + assetViewerManager.hideHiddenPeople(); }); let containerWidth = $state(0); @@ -74,15 +74,13 @@ return scaleToFit(getNaturalSize(assetViewerManager.imgRef), { width: containerWidth, height: containerHeight }); }); - const highlightedBoxes = $derived(getBoundingBox($boundingBoxesArray, overlaySize)); + const highlightedBoxes = $derived(getBoundingBox(assetViewerManager.highlightedFaces, overlaySize)); const isHighlighting = $derived(highlightedBoxes.length > 0); let visibleBoxes = $state([]); - let visibleBoundingBoxes = $state([]); $effect(() => { if (isHighlighting) { visibleBoxes = highlightedBoxes; - visibleBoundingBoxes = $boundingBoxesArray; } }); @@ -160,6 +158,9 @@ // eslint-disable-next-line svelte/prefer-svelte-reactivity const map = new Map(); for (const person of asset.people ?? []) { + if (person.isHidden && !assetViewerManager.isShowingHiddenPeople) { + continue; + } for (const face of person.faces ?? []) { map.set(face, person.name); } @@ -169,35 +170,31 @@ const faces = $derived(Array.from(faceToNameMap.keys())); - const handleImageMouseMove = (event: MouseEvent) => { - $boundingBoxesArray = []; - if (!assetViewerManager.imgRef || !element || assetViewerManager.isFaceEditMode || ocrManager.showOverlay) { - return; + const boundingBoxes = $derived.by(() => { + if (assetViewerManager.isFaceEditMode || ocrManager.showOverlay) { + return []; } - const natural = getNaturalSize(assetViewerManager.imgRef); - const scaled = scaleToFit(natural, container); - const { currentZoom, currentPositionX, currentPositionY } = assetViewerManager.zoomState; + const knownBoxes = getBoundingBox(faces, overlaySize); + const result = knownBoxes.map((box, index) => ({ + ...box, + face: faces[index], + name: faceToNameMap.get(faces[index]), + })); - const contentOffsetX = (container.width - scaled.width) / 2; - const contentOffsetY = (container.height - scaled.height) / 2; - - const containerRect = element.getBoundingClientRect(); - const mouseX = (event.clientX - containerRect.left - contentOffsetX * currentZoom - currentPositionX) / currentZoom; - const mouseY = (event.clientY - containerRect.top - contentOffsetY * currentZoom - currentPositionY) / currentZoom; - - const faceBoxes = getBoundingBox(faces, overlaySize); - - for (const [index, box] of faceBoxes.entries()) { - if (mouseX >= box.left && mouseX <= box.left + box.width && mouseY >= box.top && mouseY <= box.top + box.height) { - $boundingBoxesArray.push(faces[index]); - } + if (assetViewerManager.highlightedFaces.length === 0) { + return result; } - }; - const handleImageMouseLeave = () => { - $boundingBoxesArray = []; - }; + const knownIds = new Set(faces.map((f) => f.id)); + const unassignedFaces = assetViewerManager.highlightedFaces.filter((f) => !knownIds.has(f.id)); + const unassignedBoxes = getBoundingBox(unassignedFaces, overlaySize); + for (let i = 0; i < unassignedBoxes.length; i++) { + result.push({ ...unassignedBoxes[i], face: unassignedFaces[i], name: undefined }); + } + + return result; + }); @@ -218,8 +215,6 @@ bind:clientHeight={containerHeight} role="presentation" ondblclick={onZoom} - onmousemove={handleImageMouseMove} - onmouseleave={handleImageMouseLeave} use:zoomImageAction={{ zoomTarget: adaptiveImage }} {...useSwipe((event) => onSwipe?.(event))} > @@ -261,22 +256,27 @@ - {#each visibleBoxes as boundingbox, index (boundingbox.id)} -
- {#if faceToNameMap.get(visibleBoundingBoxes[index])} +
+ {#each boundingBoxes as boundingbox (boundingbox.id)} + {@const isActive = assetViewerManager.highlightedFaces.some((f) => f.id === boundingbox.id)} + +
assetViewerManager.setHighlightedFaces([boundingbox.face])} + onpointerleave={() => assetViewerManager.clearHighlightedFaces()} + > + {#if isActive && boundingbox.name} {/if} - {/each} -
+ + {/each} {#each ocrBoxes as ocrBox (ocrBox.id)} diff --git a/web/src/lib/components/assets/thumbnail/Thumbnail.svelte b/web/src/lib/components/assets/thumbnail/Thumbnail.svelte index 99608eabcc..63e93169bb 100644 --- a/web/src/lib/components/assets/thumbnail/Thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/Thumbnail.svelte @@ -5,7 +5,6 @@ import { mediaQueryManager } from '$lib/stores/media-query-manager.svelte'; import { locale, playVideoThumbnailOnHover } from '$lib/stores/preferences.store'; import { getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils'; - import { timeToSeconds } from '$lib/utils/date-time'; import { moveFocus } from '$lib/utils/focus-util'; import { currentUrlReplaceAssetId } from '$lib/utils/navigation'; import { getAltText } from '$lib/utils/thumbnail-util'; @@ -274,7 +273,7 @@ url={getAssetPlaybackUrl({ id: asset.id, cacheKey: asset.thumbhash })} enablePlayback={mouseOver && $playVideoThumbnailOnHover} curve={selected} - durationInSeconds={asset.duration ? timeToSeconds(asset.duration) : 0} + durationInSeconds={asset.duration ? asset.duration / 1000 : 0} playbackOnIconHover={!$playVideoThumbnailOnHover} /> diff --git a/web/src/lib/components/faces-page/PersonSidePanel.svelte b/web/src/lib/components/faces-page/PersonSidePanel.svelte index c8f5d1429c..657be179c6 100644 --- a/web/src/lib/components/faces-page/PersonSidePanel.svelte +++ b/web/src/lib/components/faces-page/PersonSidePanel.svelte @@ -4,7 +4,6 @@ import { timeBeforeShowLoadingSpinner } from '$lib/constants'; import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte'; import { eventManager } from '$lib/managers/event-manager.svelte'; - import { boundingBoxesArray } from '$lib/stores/people.store'; import { getPeopleThumbnailUrl, handlePromiseError } from '$lib/utils'; import { handleError } from '$lib/utils/handle-error'; import { zoomImageToBase64 } from '$lib/utils/people-utils'; @@ -239,15 +238,15 @@ {:else} {#each peopleWithFaces as face, index (face.id)} {@const personName = face.person ? face.person?.name : $t('face_unassigned')} - {@const isHighlighted = $boundingBoxesArray.some((b) => b.id === face.id)} + {@const isHighlighted = assetViewerManager.highlightedFaces.some((b) => b.id === face.id)}
($boundingBoxesArray = [peopleWithFaces[index]])} - onmouseover={() => ($boundingBoxesArray = [peopleWithFaces[index]])} - onmouseleave={() => ($boundingBoxesArray = [])} + onfocus={() => assetViewerManager.setHighlightedFaces([peopleWithFaces[index]])} + onpointerenter={() => assetViewerManager.setHighlightedFaces([peopleWithFaces[index]])} + onpointerleave={() => assetViewerManager.clearHighlightedFaces()} >
{#if selectedPersonToCreate[face.id]} diff --git a/web/src/lib/components/timeline/TimelineAssetViewer.svelte b/web/src/lib/components/timeline/TimelineAssetViewer.svelte index 8c2a84298b..1b4e76d9c4 100644 --- a/web/src/lib/components/timeline/TimelineAssetViewer.svelte +++ b/web/src/lib/components/timeline/TimelineAssetViewer.svelte @@ -96,9 +96,9 @@ return { id: randomAsset.id }; }; - const handleClose = async (asset: { id: string }) => { + const handleClose = async (assetId: string) => { invisible = true; - assetViewerManager.gridScrollTarget = { at: asset.id }; + assetViewerManager.gridScrollTarget = { at: assetId }; await navigate({ targetRoute: 'current', assetId: null, @@ -117,7 +117,7 @@ // eslint-disable-next-line @typescript-eslint/no-unused-expressions (await navigateToAsset(assetCursor?.nextAsset)) || (await navigateToAsset(assetCursor?.previousAsset)) || - (await handleClose(assetCursor.current)); + (await handleClose(assetCursor.current.id)); }; const handlePreAction = async (action: Action) => { @@ -136,7 +136,7 @@ // eslint-disable-next-line @typescript-eslint/no-unused-expressions (await navigateToAsset(assetCursor?.nextAsset)) || (await navigateToAsset(assetCursor?.previousAsset)) || - (await handleClose(action.asset)); + (await handleClose(action.asset.id)); break; } diff --git a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte index b1f3ff01bb..ebb671d491 100644 --- a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte +++ b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte @@ -45,10 +45,7 @@ await deleteAssets( force, - (assetIds) => { - timelineManager.removeAssets(assetIds); - eventManager.emit('AssetsDelete', assetIds); - }, + (assetIds) => timelineManager.removeAssets(assetIds), selectedAssets, force ? undefined : (assets) => timelineManager.upsertAssets(assets), ); diff --git a/web/src/lib/managers/asset-viewer-manager.svelte.ts b/web/src/lib/managers/asset-viewer-manager.svelte.ts index 4e8ac0060e..55c8570b78 100644 --- a/web/src/lib/managers/asset-viewer-manager.svelte.ts +++ b/web/src/lib/managers/asset-viewer-manager.svelte.ts @@ -8,6 +8,16 @@ import { BaseEventManager } from '$lib/utils/base-event-manager.svelte'; import type { AssetGridRouteSearchParams } from '$lib/utils/navigation'; import { PersistedLocalStorage } from '$lib/utils/persisted'; +export interface Faces { + id: string; + imageHeight: number; + imageWidth: number; + boundingBoxX1: number; + boundingBoxX2: number; + boundingBoxY1: number; + boundingBoxY2: number; +} + const isShowDetailPanel = new PersistedLocalStorage('asset-viewer-state', false); const isShowAssetPath = new PersistedLocalStorage('asset-viewer-show-path', false); @@ -48,6 +58,8 @@ class AssetViewerManager extends BaseEventManager { #isEditFacesPanelOpen = $state(false); #viewingAssetStoreState = $state(); #viewState = $state(false); + #highlightedFaces = $state([]); + #showingHiddenPeople = $state(false); gridScrollTarget = $state(); get asset() { @@ -209,6 +221,31 @@ class AssetViewerManager extends BaseEventManager { this.closeFaceEditMode(); this.closeEditFacesPanel(); } + + get highlightedFaces() { + return this.#highlightedFaces; + } + + setHighlightedFaces(faces: Faces[]) { + this.#highlightedFaces = faces; + } + + clearHighlightedFaces() { + this.#highlightedFaces = []; + } + + get isShowingHiddenPeople() { + return this.#showingHiddenPeople; + } + + toggleHiddenPeople() { + this.#showingHiddenPeople = !this.#showingHiddenPeople; + } + + hideHiddenPeople() { + this.#showingHiddenPeople = false; + } + setAsset(asset: AssetResponseDto) { this.#viewingAssetStoreState = asset; this.#viewState = true; diff --git a/web/src/lib/managers/memory-manager.svelte.ts b/web/src/lib/managers/memory-manager.svelte.ts index ada5079297..25f998ec82 100644 --- a/web/src/lib/managers/memory-manager.svelte.ts +++ b/web/src/lib/managers/memory-manager.svelte.ts @@ -33,6 +33,8 @@ class MemoryManager { if (authManager.authenticated) { void this.initialize(); } + + this.scheduleHourlyRefresh(); } ready() { @@ -132,6 +134,29 @@ class MemoryManager { const memories = await searchMemories({ $for: asLocalTimeISO(DateTime.now()) }); this.memories = memories.filter((memory) => memory.assets.length > 0); } + + private scheduleHourlyRefresh() { + const now = DateTime.utc(); + let nextEvent = now.set({ minute: 0, second: 5 }); + + if (nextEvent <= now) { + nextEvent = nextEvent.plus({ hours: 1 }); + } + + const initialDelay = nextEvent.diff(now).as('milliseconds'); + + setTimeout(() => { + this.#loading = this.load(); + + // Schedule subsequent events hourly + setInterval( + () => { + this.#loading = this.load(); + }, + 60 * 60 * 1000, + ); + }, initialDelay); + } } export const memoryManager = new MemoryManager(); diff --git a/web/src/lib/managers/timeline-manager/types.ts b/web/src/lib/managers/timeline-manager/types.ts index 1437e5701b..0e85802900 100644 --- a/web/src/lib/managers/timeline-manager/types.ts +++ b/web/src/lib/managers/timeline-manager/types.ts @@ -29,7 +29,7 @@ export type TimelineAsset = { isVideo: boolean; isImage: boolean; stack: AssetStackResponseDto | null; - duration: string | null; + duration: number | null; projectionType: string | null; livePhotoVideoId: string | null; city: string | null; diff --git a/web/src/lib/stores/people.store.ts b/web/src/lib/stores/people.store.ts deleted file mode 100644 index 34e927cf36..0000000000 --- a/web/src/lib/stores/people.store.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { writable } from 'svelte/store'; - -export interface Faces { - id: string; - imageHeight: number; - imageWidth: number; - boundingBoxX1: number; - boundingBoxX2: number; - boundingBoxY1: number; - boundingBoxY2: number; -} - -export const boundingBoxesArray = writable([]); diff --git a/web/src/lib/stores/websocket.ts b/web/src/lib/stores/websocket.ts index 630d9771f9..e5cc25bcf9 100644 --- a/web/src/lib/stores/websocket.ts +++ b/web/src/lib/stores/websocket.ts @@ -80,6 +80,8 @@ websocket .on('on_new_release', (event) => eventManager.emit('ReleaseEvent', event)) .on('on_session_delete', () => eventManager.emit('SessionDelete')) .on('on_user_delete', (id) => eventManager.emit('UserAdminDeleted', { id })) + .on('on_asset_delete', (asset) => eventManager.emit('AssetsDelete', [asset])) + .on('on_asset_trash', (assets) => eventManager.emit('AssetsDelete', assets)) .on('on_asset_update', (asset) => eventManager.emit('AssetUpdate', asset)) .on('on_person_thumbnail', (id) => eventManager.emit('PersonThumbnailReady', { id })) .on('on_notification', () => notificationManager.refresh()) diff --git a/web/src/lib/utils.spec.ts b/web/src/lib/utils.spec.ts index 7b3ae9ecb2..9ecc7f548e 100644 --- a/web/src/lib/utils.spec.ts +++ b/web/src/lib/utils.spec.ts @@ -50,7 +50,7 @@ describe('utils', () => { originalPath: 'image.gif', originalMimeType: 'image/gif', type: AssetTypeEnum.Image, - duration: '2.0', + duration: 2000, }); const url = getAssetUrl({ asset }); @@ -65,7 +65,7 @@ describe('utils', () => { originalPath: 'image.webp', originalMimeType: 'image/webp', type: AssetTypeEnum.Image, - duration: '2.0', + duration: 2000, }); const url = getAssetUrl({ asset }); @@ -119,7 +119,7 @@ describe('utils', () => { originalPath: 'image.gif', originalMimeType: 'image/gif', type: AssetTypeEnum.Image, - duration: '2.0', + duration: 2000, }); const sharedLink = sharedLinkFactory.build({ allowDownload: true, showMetadata: true, assets: [asset] }); @@ -134,7 +134,7 @@ describe('utils', () => { originalPath: 'image.gif', originalMimeType: 'image/gif', type: AssetTypeEnum.Image, - duration: '2.0', + duration: 2000, }); const sharedLink = sharedLinkFactory.build({ allowDownload: false, assets: [asset] }); @@ -150,7 +150,7 @@ describe('utils', () => { originalPath: 'image.gif', originalMimeType: 'image/gif', type: AssetTypeEnum.Image, - duration: '2.0', + duration: 2000, }); const sharedLink = sharedLinkFactory.build({ showMetadata: false, assets: [asset] }); diff --git a/web/src/lib/utils/date-time.spec.ts b/web/src/lib/utils/date-time.spec.ts index 830d96d45c..f91a74c6b9 100644 --- a/web/src/lib/utils/date-time.spec.ts +++ b/web/src/lib/utils/date-time.spec.ts @@ -1,53 +1,5 @@ import { writable } from 'svelte/store'; -import { getAlbumDateRange, getShortDateRange, timeToSeconds } from './date-time'; - -describe('converting time to seconds', () => { - it('parses hh:mm:ss correctly', () => { - expect(timeToSeconds('01:02:03')).toBeCloseTo(3723); - }); - - it('parses hh:mm:ss.SSS correctly', () => { - expect(timeToSeconds('01:02:03.456')).toBeCloseTo(3723.456); - }); - - it('parses h:m:s.S correctly', () => { - expect(timeToSeconds('1:2:3.4')).toBe(0); // Non-standard format, Luxon returns NaN - }); - - it('parses hhh:mm:ss.SSS correctly', () => { - expect(timeToSeconds('100:02:03.456')).toBe(0); // Non-standard format, Luxon returns NaN - }); - - it('ignores ignores double milliseconds hh:mm:ss.SSS.SSSSSS', () => { - expect(timeToSeconds('01:02:03.456.123456')).toBe(0); // Non-standard format, Luxon returns NaN - }); - - // Test edge cases that can cause crashes - it('handles "0" string input', () => { - expect(timeToSeconds('0')).toBe(0); - }); - - it('handles empty string input', () => { - expect(timeToSeconds('')).toBe(0); - }); - - it('parses HH:MM format correctly', () => { - expect(timeToSeconds('01:02')).toBe(3720); // 1 hour 2 minutes = 3720 seconds - }); - - it('handles malformed time strings', () => { - expect(timeToSeconds('invalid')).toBe(0); - }); - - it('parses single hour format correctly', () => { - expect(timeToSeconds('01')).toBe(3600); // Luxon interprets "01" as 1 hour - }); - - it('handles time strings with invalid numbers', () => { - expect(timeToSeconds('aa:bb:cc')).toBe(0); - expect(timeToSeconds('01:bb:03')).toBe(0); - }); -}); +import { getAlbumDateRange, getShortDateRange } from './date-time'; describe('getShortDateRange', () => { beforeEach(() => { diff --git a/web/src/lib/utils/date-time.ts b/web/src/lib/utils/date-time.ts index f39697fa11..d06f92376b 100644 --- a/web/src/lib/utils/date-time.ts +++ b/web/src/lib/utils/date-time.ts @@ -1,20 +1,8 @@ -import { DateTime, Duration } from 'luxon'; +import { DateTime } from 'luxon'; import { get } from 'svelte/store'; import { dateFormats } from '$lib/constants'; import { locale } from '$lib/stores/preferences.store'; -/** - * Convert time like `01:02:03.456` to seconds. - */ -export function timeToSeconds(time: string) { - if (!time || time === '0') { - return 0; - } - - const seconds = Duration.fromISOTime(time).as('seconds'); - - return Number.isNaN(seconds) ? 0 : seconds; -} export function parseUtcDate(date: string) { return DateTime.fromISO(date, { zone: 'UTC' }).toUTC(); } diff --git a/web/src/lib/utils/people-utils.spec.ts b/web/src/lib/utils/people-utils.spec.ts index f27a1855b5..0601f5022a 100644 --- a/web/src/lib/utils/people-utils.spec.ts +++ b/web/src/lib/utils/people-utils.spec.ts @@ -1,4 +1,4 @@ -import type { Faces } from '$lib/stores/people.store'; +import type { Faces } from '$lib/managers/asset-viewer-manager.svelte'; import type { Size } from '$lib/utils/container-utils'; import { getBoundingBox } from '$lib/utils/people-utils'; diff --git a/web/src/lib/utils/people-utils.ts b/web/src/lib/utils/people-utils.ts index 67c4329736..9d7b8be799 100644 --- a/web/src/lib/utils/people-utils.ts +++ b/web/src/lib/utils/people-utils.ts @@ -1,5 +1,5 @@ import { AssetTypeEnum, type AssetFaceResponseDto } from '@immich/sdk'; -import type { Faces } from '$lib/stores/people.store'; +import type { Faces } from '$lib/managers/asset-viewer-manager.svelte'; import { getAssetMediaUrl } from '$lib/utils'; import { mapNormalizedRectToContent, type Rect, type Size } from '$lib/utils/container-utils'; diff --git a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte index 0d356fdb5c..c5a514bd96 100644 --- a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -86,7 +86,7 @@
- {#if assetViewerManager.isViewing} + {#if assetViewerManager.isViewing && !isTimelinePanelVisible} {#await import('$lib/components/asset-viewer/AssetViewer.svelte') then { default: AssetViewer }} { - if (asset.isVideo) { - const timeParts = asset.duration!.split(':').map(Number); - const durationInMilliseconds = (timeParts[0] * 3600 + timeParts[1] * 60 + timeParts[2]) * 1000; - progressBarController = new Tween(0, { - duration: (from: number, to: number) => (to ? durationInMilliseconds * (to - from) : 0), - }); - } else { - progressBarController = new Tween(0, { - duration: (from: number, to: number) => - to ? authManager.preferences.memories.duration * 1000 * (to - from) : 0, - }); - } + progressBarController = new Tween(0, { + duration: (from: number, to: number) => + to ? (asset.isVideo ? asset.duration! : authManager.preferences.memories.duration * 1000) * (to - from) : 0, + }); }; const handleNextAsset = () => handleNavigate(current?.next?.asset); diff --git a/web/src/routes/(user)/utilities/large-files/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/utilities/large-files/[[photos=photos]]/[[assetId=id]]/+page.svelte index 55bd648730..aed7570e9c 100644 --- a/web/src/routes/(user)/utilities/large-files/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/utilities/large-files/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -1,11 +1,12 @@ + +
- {#if assets && data.assets.length > 0} + {#if assets && assets.length > 0} {#each assets as asset (asset.id)} {/each} @@ -75,7 +84,7 @@ cursor={assetCursor} showNavigation={assets.length > 1} {onRandom} - {onAction} + {preAction} onClose={() => { assetViewerManager.showAssetViewer(false); handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));