From 3cb8f54307b386ac546039f9ac463538401fc155 Mon Sep 17 00:00:00 2001 From: martin <74269598+martabal@users.noreply.github.com> Date: Tue, 2 Apr 2024 05:11:11 +0200 Subject: [PATCH 01/25] fix(web): asset description resize (#8442) * fix: asset description resize * use immich-scrollbar class --------- Co-authored-by: Alex Tran --- web/src/lib/components/asset-viewer/detail-panel.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte index 318007280..565bca401 100644 --- a/web/src/lib/components/asset-viewer/detail-panel.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -200,7 +200,7 @@ disabled={!isOwner || isSharedLink()} bind:this={textArea} class="max-h-[500px] - w-full resize-none overflow-hidden border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary" + w-full resize-none border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary immich-scrollbar" placeholder={isOwner ? 'Add a description' : ''} on:focusout={handleFocusOut} on:input={() => autoGrowHeight(textArea)} From 506f9f6fb970bf2954a516d63c8b3637710dba56 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 23:41:23 -0500 Subject: [PATCH 02/25] chore(deps): update prom/prometheus docker digest to dec2018 (#8320) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docker/docker-compose.prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index b8a6b7633..1bcb5e327 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -76,7 +76,7 @@ services: container_name: immich_prometheus ports: - 9090:9090 - image: prom/prometheus@sha256:5ccad477d0057e62a7cd1981ffcc43785ac10c5a35522dc207466ff7e7ec845f + image: prom/prometheus@sha256:dec2018ae55885fed717f25c289b8c9cff0bf5fbb9e619fb49b6161ac493c016 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus From e520c0d1f54824ffbbca57d796b505836baf3bf0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 00:44:57 -0400 Subject: [PATCH 03/25] chore(deps): update dependency black to v24.3.0 [security] (#8109) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- machine-learning/poetry.lock | 46 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/machine-learning/poetry.lock b/machine-learning/poetry.lock index 54dd16292..6b7ebe254 100644 --- a/machine-learning/poetry.lock +++ b/machine-learning/poetry.lock @@ -64,33 +64,33 @@ trio = ["trio (>=0.23)"] [[package]] name = "black" -version = "24.2.0" +version = "24.3.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-24.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29"}, - {file = "black-24.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430"}, - {file = "black-24.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f"}, - {file = "black-24.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a"}, - {file = "black-24.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd"}, - {file = "black-24.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2"}, - {file = "black-24.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92"}, - {file = "black-24.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23"}, - {file = "black-24.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b"}, - {file = "black-24.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9"}, - {file = "black-24.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693"}, - {file = "black-24.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982"}, - {file = "black-24.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4"}, - {file = "black-24.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218"}, - {file = "black-24.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0"}, - {file = "black-24.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"}, - {file = "black-24.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8"}, - {file = "black-24.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8"}, - {file = "black-24.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540"}, - {file = "black-24.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31"}, - {file = "black-24.2.0-py3-none-any.whl", hash = "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6"}, - {file = "black-24.2.0.tar.gz", hash = "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894"}, + {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, + {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, + {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, + {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, + {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, + {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, + {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, + {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, + {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, + {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, + {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, + {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, + {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, + {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, + {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, + {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, + {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, + {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, + {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, + {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, + {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, + {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, ] [package.dependencies] From 8edc2fb46f9b3d72c666ffaa4e9ebffd24f2d3df Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Tue, 2 Apr 2024 00:56:56 -0400 Subject: [PATCH 04/25] refactor(server): decouple generated images from image formats (#8246) * rename thumbnail config update target paths, fix tests rename to image settings replace legacy enum better typing update sql update api remove config option fix * update docs * update other thumbnail configs in migration * keep legacy enum for now * fix jumbled job names * fix jumbled job names in tests * rename thumbhash job * rename dto * fix tests * preserve order * remove unused import * keep old fields in dto, marked deprecated * update sql --------- Co-authored-by: Alex Tran --- docs/docs/guides/database-queries.md | 2 +- docs/docs/install/config-file.md | 8 +- mobile/openapi/.openapi-generator/FILES | 9 +- mobile/openapi/README.md | 3 +- mobile/openapi/doc/AssetApi.md | 8 +- mobile/openapi/doc/ImageFormat.md | 14 ++ mobile/openapi/doc/MetadataSearchDto.md | 2 + mobile/openapi/doc/SystemConfigDto.md | 2 +- ...humbnailDto.md => SystemConfigImageDto.md} | 8 +- mobile/openapi/lib/api.dart | 3 +- mobile/openapi/lib/api/asset_api.dart | 20 ++- mobile/openapi/lib/api_client.dart | 6 +- mobile/openapi/lib/api_helper.dart | 3 + mobile/openapi/lib/model/image_format.dart | 85 +++++++++++ .../lib/model/metadata_search_dto.dart | 36 ++++- mobile/openapi/lib/model/path_type.dart | 12 +- .../openapi/lib/model/system_config_dto.dart | 18 +-- .../lib/model/system_config_image_dto.dart | 138 ++++++++++++++++++ .../model/system_config_thumbnail_dto.dart | 122 ---------------- mobile/openapi/test/asset_api_test.dart | 2 +- mobile/openapi/test/image_format_test.dart | 21 +++ .../test/metadata_search_dto_test.dart | 10 ++ .../openapi/test/system_config_dto_test.dart | 10 +- ...dart => system_config_image_dto_test.dart} | 24 ++- open-api/immich-openapi-specs.json | 99 +++++++++---- open-api/typescript-sdk/src/fetch-client.ts | 40 +++-- server/src/cores/storage.core.ts | 65 ++++----- server/src/cores/system-config.core.ts | 9 +- server/src/dtos/asset-response.dto.ts | 4 +- server/src/dtos/search.dto.ts | 12 ++ server/src/dtos/system-config.dto.ts | 27 ++-- server/src/entities/asset.entity.ts | 4 +- server/src/entities/move.entity.ts | 4 +- server/src/entities/system-config.entity.ts | 13 +- server/src/interfaces/job.interface.ts | 12 +- server/src/interfaces/media.interface.ts | 4 +- server/src/interfaces/search.interface.ts | 4 +- .../1711257900274-RenameWebpJpegPaths.ts | 51 +++++++ server/src/queries/asset.repository.sql | 42 +++--- server/src/queries/person.repository.sql | 12 +- server/src/queries/search.repository.sql | 20 +-- server/src/queries/shared.link.repository.sql | 12 +- .../src/repositories/asset-v1.repository.ts | 4 +- server/src/repositories/asset.repository.ts | 18 +-- server/src/repositories/job.repository.ts | 6 +- server/src/services/asset-v1.service.spec.ts | 4 +- server/src/services/asset-v1.service.ts | 22 +-- server/src/services/asset.service.spec.ts | 12 +- server/src/services/asset.service.ts | 4 +- server/src/services/audit.service.ts | 22 +-- server/src/services/job.service.spec.ts | 20 +-- server/src/services/job.service.ts | 10 +- server/src/services/media.service.spec.ts | 123 +++++++++------- server/src/services/media.service.ts | 53 +++---- server/src/services/microservices.service.ts | 6 +- server/src/services/person.service.spec.ts | 2 +- server/src/services/person.service.ts | 19 +-- server/src/services/search.service.ts | 3 + .../src/services/smart-info.service.spec.ts | 4 +- server/src/services/smart-info.service.ts | 4 +- .../services/system-config.service.spec.ts | 9 +- server/src/utils/database.ts | 2 +- server/test/fixtures/asset.stub.ts | 68 ++++----- server/test/fixtures/shared-link.stub.ts | 4 +- .../image-settings.svelte} | 28 ++-- .../routes/admin/system-settings/+page.svelte | 16 +- 66 files changed, 916 insertions(+), 547 deletions(-) create mode 100644 mobile/openapi/doc/ImageFormat.md rename mobile/openapi/doc/{SystemConfigThumbnailDto.md => SystemConfigImageDto.md} (65%) create mode 100644 mobile/openapi/lib/model/image_format.dart create mode 100644 mobile/openapi/lib/model/system_config_image_dto.dart delete mode 100644 mobile/openapi/lib/model/system_config_thumbnail_dto.dart create mode 100644 mobile/openapi/test/image_format_test.dart rename mobile/openapi/test/{system_config_thumbnail_dto_test.dart => system_config_image_dto_test.dart} (53%) create mode 100644 server/src/migrations/1711257900274-RenameWebpJpegPaths.ts rename web/src/lib/components/admin-page/settings/{thumbnail/thumbnail-settings.svelte => image/image-settings.svelte} (75%) diff --git a/docs/docs/guides/database-queries.md b/docs/docs/guides/database-queries.md index fe369f899..e8252f25d 100644 --- a/docs/docs/guides/database-queries.md +++ b/docs/docs/guides/database-queries.md @@ -45,7 +45,7 @@ SELECT * FROM "assets" JOIN "exif" ON "assets"."id" = "exif"."assetId" WHERE "ex ``` ```sql title="Without thumbnails" -SELECT * FROM "assets" WHERE "assets"."resizePath" IS NULL OR "assets"."webpPath" IS NULL; +SELECT * FROM "assets" WHERE "assets"."previewPath" IS NULL OR "assets"."thumbnailPath" IS NULL; ``` ```sql title="By type" diff --git a/docs/docs/install/config-file.md b/docs/docs/install/config-file.md index 9a1d1acb1..a890d674b 100644 --- a/docs/docs/install/config-file.md +++ b/docs/docs/install/config-file.md @@ -114,9 +114,11 @@ The default configuration looks like this: "hashVerificationEnabled": true, "template": "{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}" }, - "thumbnail": { - "webpSize": 250, - "jpegSize": 1440, + "image": { + "thumbnailFormat": "webp", + "thumbnailSize": 250, + "previewFormat": "jpeg", + "previewSize": 1440, "quality": 80, "colorspace": "p3" }, diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 9ec77670f..795943e29 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -72,6 +72,7 @@ doc/FileChecksumResponseDto.md doc/FileReportDto.md doc/FileReportFixDto.md doc/FileReportItemDto.md +doc/ImageFormat.md doc/JobApi.md doc/JobCommand.md doc/JobCommandDto.md @@ -145,6 +146,7 @@ doc/SmartSearchDto.md doc/SystemConfigApi.md doc/SystemConfigDto.md doc/SystemConfigFFmpegDto.md +doc/SystemConfigImageDto.md doc/SystemConfigJobDto.md doc/SystemConfigLibraryDto.md doc/SystemConfigLibraryScanDto.md @@ -160,7 +162,6 @@ doc/SystemConfigServerDto.md doc/SystemConfigStorageTemplateDto.md doc/SystemConfigTemplateStorageOptionDto.md doc/SystemConfigThemeDto.md -doc/SystemConfigThumbnailDto.md doc/SystemConfigTrashDto.md doc/SystemConfigUserDto.md doc/TagApi.md @@ -284,6 +285,7 @@ lib/model/file_checksum_response_dto.dart lib/model/file_report_dto.dart lib/model/file_report_fix_dto.dart lib/model/file_report_item_dto.dart +lib/model/image_format.dart lib/model/job_command.dart lib/model/job_command_dto.dart lib/model/job_counts_dto.dart @@ -348,6 +350,7 @@ lib/model/smart_info_response_dto.dart lib/model/smart_search_dto.dart lib/model/system_config_dto.dart lib/model/system_config_f_fmpeg_dto.dart +lib/model/system_config_image_dto.dart lib/model/system_config_job_dto.dart lib/model/system_config_library_dto.dart lib/model/system_config_library_scan_dto.dart @@ -363,7 +366,6 @@ lib/model/system_config_server_dto.dart lib/model/system_config_storage_template_dto.dart lib/model/system_config_template_storage_option_dto.dart lib/model/system_config_theme_dto.dart -lib/model/system_config_thumbnail_dto.dart lib/model/system_config_trash_dto.dart lib/model/system_config_user_dto.dart lib/model/tag_response_dto.dart @@ -461,6 +463,7 @@ test/file_checksum_response_dto_test.dart test/file_report_dto_test.dart test/file_report_fix_dto_test.dart test/file_report_item_dto_test.dart +test/image_format_test.dart test/job_api_test.dart test/job_command_dto_test.dart test/job_command_test.dart @@ -534,6 +537,7 @@ test/smart_search_dto_test.dart test/system_config_api_test.dart test/system_config_dto_test.dart test/system_config_f_fmpeg_dto_test.dart +test/system_config_image_dto_test.dart test/system_config_job_dto_test.dart test/system_config_library_dto_test.dart test/system_config_library_scan_dto_test.dart @@ -549,7 +553,6 @@ test/system_config_server_dto_test.dart test/system_config_storage_template_dto_test.dart test/system_config_template_storage_option_dto_test.dart test/system_config_theme_dto_test.dart -test/system_config_thumbnail_dto_test.dart test/system_config_trash_dto_test.dart test/system_config_user_dto_test.dart test/tag_api_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index bfdac06c4..a46f2383e 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -277,6 +277,7 @@ Class | Method | HTTP request | Description - [FileReportDto](doc//FileReportDto.md) - [FileReportFixDto](doc//FileReportFixDto.md) - [FileReportItemDto](doc//FileReportItemDto.md) + - [ImageFormat](doc//ImageFormat.md) - [JobCommand](doc//JobCommand.md) - [JobCommandDto](doc//JobCommandDto.md) - [JobCountsDto](doc//JobCountsDto.md) @@ -341,6 +342,7 @@ Class | Method | HTTP request | Description - [SmartSearchDto](doc//SmartSearchDto.md) - [SystemConfigDto](doc//SystemConfigDto.md) - [SystemConfigFFmpegDto](doc//SystemConfigFFmpegDto.md) + - [SystemConfigImageDto](doc//SystemConfigImageDto.md) - [SystemConfigJobDto](doc//SystemConfigJobDto.md) - [SystemConfigLibraryDto](doc//SystemConfigLibraryDto.md) - [SystemConfigLibraryScanDto](doc//SystemConfigLibraryScanDto.md) @@ -356,7 +358,6 @@ Class | Method | HTTP request | Description - [SystemConfigStorageTemplateDto](doc//SystemConfigStorageTemplateDto.md) - [SystemConfigTemplateStorageOptionDto](doc//SystemConfigTemplateStorageOptionDto.md) - [SystemConfigThemeDto](doc//SystemConfigThemeDto.md) - - [SystemConfigThumbnailDto](doc//SystemConfigThumbnailDto.md) - [SystemConfigTrashDto](doc//SystemConfigTrashDto.md) - [SystemConfigUserDto](doc//SystemConfigUserDto.md) - [TagResponseDto](doc//TagResponseDto.md) diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index 0778485c3..297a4cdba 100644 --- a/mobile/openapi/doc/AssetApi.md +++ b/mobile/openapi/doc/AssetApi.md @@ -886,7 +886,7 @@ void (empty response body) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **searchAssets** -> List searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked) +> List searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, previewPath, resizePath, size, state, takenAfter, takenBefore, thumbnailPath, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked) @@ -936,11 +936,13 @@ final originalFileName = originalFileName_example; // String | final originalPath = originalPath_example; // String | final page = 8.14; // num | final personIds = []; // List | +final previewPath = previewPath_example; // String | final resizePath = resizePath_example; // String | final size = 8.14; // num | final state = state_example; // String | final takenAfter = 2013-10-20T19:20:30+01:00; // DateTime | final takenBefore = 2013-10-20T19:20:30+01:00; // DateTime | +final thumbnailPath = thumbnailPath_example; // String | final trashedAfter = 2013-10-20T19:20:30+01:00; // DateTime | final trashedBefore = 2013-10-20T19:20:30+01:00; // DateTime | final type = ; // AssetTypeEnum | @@ -954,7 +956,7 @@ final withPeople = true; // bool | final withStacked = true; // bool | try { - final result = api_instance.searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked); + final result = api_instance.searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, previewPath, resizePath, size, state, takenAfter, takenBefore, thumbnailPath, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked); print(result); } catch (e) { print('Exception when calling AssetApi->searchAssets: $e\n'); @@ -992,11 +994,13 @@ Name | Type | Description | Notes **originalPath** | **String**| | [optional] **page** | **num**| | [optional] **personIds** | [**List**](String.md)| | [optional] [default to const []] + **previewPath** | **String**| | [optional] **resizePath** | **String**| | [optional] **size** | **num**| | [optional] **state** | **String**| | [optional] **takenAfter** | **DateTime**| | [optional] **takenBefore** | **DateTime**| | [optional] + **thumbnailPath** | **String**| | [optional] **trashedAfter** | **DateTime**| | [optional] **trashedBefore** | **DateTime**| | [optional] **type** | [**AssetTypeEnum**](.md)| | [optional] diff --git a/mobile/openapi/doc/ImageFormat.md b/mobile/openapi/doc/ImageFormat.md new file mode 100644 index 000000000..312e501c1 --- /dev/null +++ b/mobile/openapi/doc/ImageFormat.md @@ -0,0 +1,14 @@ +# openapi.model.ImageFormat + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/doc/MetadataSearchDto.md b/mobile/openapi/doc/MetadataSearchDto.md index d1d098fb0..5dc50c00f 100644 --- a/mobile/openapi/doc/MetadataSearchDto.md +++ b/mobile/openapi/doc/MetadataSearchDto.md @@ -35,11 +35,13 @@ Name | Type | Description | Notes **originalPath** | **String** | | [optional] **page** | **num** | | [optional] **personIds** | **List** | | [optional] [default to const []] +**previewPath** | **String** | | [optional] **resizePath** | **String** | | [optional] **size** | **num** | | [optional] **state** | **String** | | [optional] **takenAfter** | [**DateTime**](DateTime.md) | | [optional] **takenBefore** | [**DateTime**](DateTime.md) | | [optional] +**thumbnailPath** | **String** | | [optional] **trashedAfter** | [**DateTime**](DateTime.md) | | [optional] **trashedBefore** | [**DateTime**](DateTime.md) | | [optional] **type** | [**AssetTypeEnum**](AssetTypeEnum.md) | | [optional] diff --git a/mobile/openapi/doc/SystemConfigDto.md b/mobile/openapi/doc/SystemConfigDto.md index ad1afbe9f..fc3d3dd0b 100644 --- a/mobile/openapi/doc/SystemConfigDto.md +++ b/mobile/openapi/doc/SystemConfigDto.md @@ -9,6 +9,7 @@ import 'package:openapi/api.dart'; Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **ffmpeg** | [**SystemConfigFFmpegDto**](SystemConfigFFmpegDto.md) | | +**image** | [**SystemConfigImageDto**](SystemConfigImageDto.md) | | **job** | [**SystemConfigJobDto**](SystemConfigJobDto.md) | | **library_** | [**SystemConfigLibraryDto**](SystemConfigLibraryDto.md) | | **logging** | [**SystemConfigLoggingDto**](SystemConfigLoggingDto.md) | | @@ -21,7 +22,6 @@ Name | Type | Description | Notes **server** | [**SystemConfigServerDto**](SystemConfigServerDto.md) | | **storageTemplate** | [**SystemConfigStorageTemplateDto**](SystemConfigStorageTemplateDto.md) | | **theme** | [**SystemConfigThemeDto**](SystemConfigThemeDto.md) | | -**thumbnail** | [**SystemConfigThumbnailDto**](SystemConfigThumbnailDto.md) | | **trash** | [**SystemConfigTrashDto**](SystemConfigTrashDto.md) | | **user** | [**SystemConfigUserDto**](SystemConfigUserDto.md) | | diff --git a/mobile/openapi/doc/SystemConfigThumbnailDto.md b/mobile/openapi/doc/SystemConfigImageDto.md similarity index 65% rename from mobile/openapi/doc/SystemConfigThumbnailDto.md rename to mobile/openapi/doc/SystemConfigImageDto.md index 491bf9f12..1b9bbe726 100644 --- a/mobile/openapi/doc/SystemConfigThumbnailDto.md +++ b/mobile/openapi/doc/SystemConfigImageDto.md @@ -1,4 +1,4 @@ -# openapi.model.SystemConfigThumbnailDto +# openapi.model.SystemConfigImageDto ## Load the model package ```dart @@ -9,9 +9,11 @@ import 'package:openapi/api.dart'; Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **colorspace** | [**Colorspace**](Colorspace.md) | | -**jpegSize** | **int** | | +**previewFormat** | [**ImageFormat**](ImageFormat.md) | | +**previewSize** | **int** | | **quality** | **int** | | -**webpSize** | **int** | | +**thumbnailFormat** | [**ImageFormat**](ImageFormat.md) | | +**thumbnailSize** | **int** | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 2abc20fde..1600dfb33 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -112,6 +112,7 @@ part 'model/file_checksum_response_dto.dart'; part 'model/file_report_dto.dart'; part 'model/file_report_fix_dto.dart'; part 'model/file_report_item_dto.dart'; +part 'model/image_format.dart'; part 'model/job_command.dart'; part 'model/job_command_dto.dart'; part 'model/job_counts_dto.dart'; @@ -176,6 +177,7 @@ part 'model/smart_info_response_dto.dart'; part 'model/smart_search_dto.dart'; part 'model/system_config_dto.dart'; part 'model/system_config_f_fmpeg_dto.dart'; +part 'model/system_config_image_dto.dart'; part 'model/system_config_job_dto.dart'; part 'model/system_config_library_dto.dart'; part 'model/system_config_library_scan_dto.dart'; @@ -191,7 +193,6 @@ part 'model/system_config_server_dto.dart'; part 'model/system_config_storage_template_dto.dart'; part 'model/system_config_template_storage_option_dto.dart'; part 'model/system_config_theme_dto.dart'; -part 'model/system_config_thumbnail_dto.dart'; part 'model/system_config_trash_dto.dart'; part 'model/system_config_user_dto.dart'; part 'model/tag_response_dto.dart'; diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index e16ccc73e..10f1c0c10 100644 --- a/mobile/openapi/lib/api/asset_api.dart +++ b/mobile/openapi/lib/api/asset_api.dart @@ -931,6 +931,8 @@ class AssetApi { /// /// * [List] personIds: /// + /// * [String] previewPath: + /// /// * [String] resizePath: /// /// * [num] size: @@ -941,6 +943,8 @@ class AssetApi { /// /// * [DateTime] takenBefore: /// + /// * [String] thumbnailPath: + /// /// * [DateTime] trashedAfter: /// /// * [DateTime] trashedBefore: @@ -962,7 +966,7 @@ class AssetApi { /// * [bool] withPeople: /// /// * [bool] withStacked: - Future searchAssetsWithHttpInfo({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, List? personIds, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async { + Future searchAssetsWithHttpInfo({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, List? personIds, String? previewPath, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, String? thumbnailPath, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async { // ignore: prefer_const_declarations final path = r'/assets'; @@ -1054,6 +1058,9 @@ class AssetApi { if (personIds != null) { queryParams.addAll(_queryParams('multi', 'personIds', personIds)); } + if (previewPath != null) { + queryParams.addAll(_queryParams('', 'previewPath', previewPath)); + } if (resizePath != null) { queryParams.addAll(_queryParams('', 'resizePath', resizePath)); } @@ -1069,6 +1076,9 @@ class AssetApi { if (takenBefore != null) { queryParams.addAll(_queryParams('', 'takenBefore', takenBefore)); } + if (thumbnailPath != null) { + queryParams.addAll(_queryParams('', 'thumbnailPath', thumbnailPath)); + } if (trashedAfter != null) { queryParams.addAll(_queryParams('', 'trashedAfter', trashedAfter)); } @@ -1173,6 +1183,8 @@ class AssetApi { /// /// * [List] personIds: /// + /// * [String] previewPath: + /// /// * [String] resizePath: /// /// * [num] size: @@ -1183,6 +1195,8 @@ class AssetApi { /// /// * [DateTime] takenBefore: /// + /// * [String] thumbnailPath: + /// /// * [DateTime] trashedAfter: /// /// * [DateTime] trashedBefore: @@ -1204,8 +1218,8 @@ class AssetApi { /// * [bool] withPeople: /// /// * [bool] withStacked: - Future?> searchAssets({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, List? personIds, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async { - final response = await searchAssetsWithHttpInfo( checksum: checksum, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceAssetId: deviceAssetId, deviceId: deviceId, encodedVideoPath: encodedVideoPath, id: id, isArchived: isArchived, isEncoded: isEncoded, isExternal: isExternal, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, lensModel: lensModel, libraryId: libraryId, make: make, model: model, order: order, originalFileName: originalFileName, originalPath: originalPath, page: page, personIds: personIds, resizePath: resizePath, size: size, state: state, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, webpPath: webpPath, withArchived: withArchived, withDeleted: withDeleted, withExif: withExif, withPeople: withPeople, withStacked: withStacked, ); + Future?> searchAssets({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, List? personIds, String? previewPath, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, String? thumbnailPath, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async { + final response = await searchAssetsWithHttpInfo( checksum: checksum, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceAssetId: deviceAssetId, deviceId: deviceId, encodedVideoPath: encodedVideoPath, id: id, isArchived: isArchived, isEncoded: isEncoded, isExternal: isExternal, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, lensModel: lensModel, libraryId: libraryId, make: make, model: model, order: order, originalFileName: originalFileName, originalPath: originalPath, page: page, personIds: personIds, previewPath: previewPath, resizePath: resizePath, size: size, state: state, takenAfter: takenAfter, takenBefore: takenBefore, thumbnailPath: thumbnailPath, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, webpPath: webpPath, withArchived: withArchived, withDeleted: withDeleted, withExif: withExif, withPeople: withPeople, withStacked: withStacked, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 312153788..4a145d0c4 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -304,6 +304,8 @@ class ApiClient { return FileReportFixDto.fromJson(value); case 'FileReportItemDto': return FileReportItemDto.fromJson(value); + case 'ImageFormat': + return ImageFormatTypeTransformer().decode(value); case 'JobCommand': return JobCommandTypeTransformer().decode(value); case 'JobCommandDto': @@ -432,6 +434,8 @@ class ApiClient { return SystemConfigDto.fromJson(value); case 'SystemConfigFFmpegDto': return SystemConfigFFmpegDto.fromJson(value); + case 'SystemConfigImageDto': + return SystemConfigImageDto.fromJson(value); case 'SystemConfigJobDto': return SystemConfigJobDto.fromJson(value); case 'SystemConfigLibraryDto': @@ -462,8 +466,6 @@ class ApiClient { return SystemConfigTemplateStorageOptionDto.fromJson(value); case 'SystemConfigThemeDto': return SystemConfigThemeDto.fromJson(value); - case 'SystemConfigThumbnailDto': - return SystemConfigThumbnailDto.fromJson(value); case 'SystemConfigTrashDto': return SystemConfigTrashDto.fromJson(value); case 'SystemConfigUserDto': diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index d186845d9..9d2d86cba 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -79,6 +79,9 @@ String parameterToString(dynamic value) { if (value is EntityType) { return EntityTypeTypeTransformer().encode(value).toString(); } + if (value is ImageFormat) { + return ImageFormatTypeTransformer().encode(value).toString(); + } if (value is JobCommand) { return JobCommandTypeTransformer().encode(value).toString(); } diff --git a/mobile/openapi/lib/model/image_format.dart b/mobile/openapi/lib/model/image_format.dart new file mode 100644 index 000000000..570b6ca6e --- /dev/null +++ b/mobile/openapi/lib/model/image_format.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 ImageFormat { + /// Instantiate a new enum with the provided [value]. + const ImageFormat._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const jpeg = ImageFormat._(r'jpeg'); + static const webp = ImageFormat._(r'webp'); + + /// List of all possible values in this [enum][ImageFormat]. + static const values = [ + jpeg, + webp, + ]; + + static ImageFormat? fromJson(dynamic value) => ImageFormatTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ImageFormat.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [ImageFormat] to String, +/// and [decode] dynamic data back to [ImageFormat]. +class ImageFormatTypeTransformer { + factory ImageFormatTypeTransformer() => _instance ??= const ImageFormatTypeTransformer._(); + + const ImageFormatTypeTransformer._(); + + String encode(ImageFormat data) => data.value; + + /// Decodes a [dynamic value][data] to a ImageFormat. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + ImageFormat? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'jpeg': return ImageFormat.jpeg; + case r'webp': return ImageFormat.webp; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [ImageFormatTypeTransformer] instance. + static ImageFormatTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/metadata_search_dto.dart b/mobile/openapi/lib/model/metadata_search_dto.dart index 86a2856e6..3f770ed09 100644 --- a/mobile/openapi/lib/model/metadata_search_dto.dart +++ b/mobile/openapi/lib/model/metadata_search_dto.dart @@ -40,11 +40,13 @@ class MetadataSearchDto { this.originalPath, this.page, this.personIds = const [], + this.previewPath, this.resizePath, this.size, this.state, this.takenAfter, this.takenBefore, + this.thumbnailPath, this.trashedAfter, this.trashedBefore, this.type, @@ -268,6 +270,14 @@ class MetadataSearchDto { List personIds; + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? previewPath; + /// /// 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 @@ -308,6 +318,14 @@ class MetadataSearchDto { /// DateTime? takenBefore; + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? thumbnailPath; + /// /// 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 @@ -419,11 +437,13 @@ class MetadataSearchDto { other.originalPath == originalPath && other.page == page && _deepEquality.equals(other.personIds, personIds) && + other.previewPath == previewPath && other.resizePath == resizePath && other.size == size && other.state == state && other.takenAfter == takenAfter && other.takenBefore == takenBefore && + other.thumbnailPath == thumbnailPath && other.trashedAfter == trashedAfter && other.trashedBefore == trashedBefore && other.type == type && @@ -466,11 +486,13 @@ class MetadataSearchDto { (originalPath == null ? 0 : originalPath!.hashCode) + (page == null ? 0 : page!.hashCode) + (personIds.hashCode) + + (previewPath == null ? 0 : previewPath!.hashCode) + (resizePath == null ? 0 : resizePath!.hashCode) + (size == null ? 0 : size!.hashCode) + (state == null ? 0 : state!.hashCode) + (takenAfter == null ? 0 : takenAfter!.hashCode) + (takenBefore == null ? 0 : takenBefore!.hashCode) + + (thumbnailPath == null ? 0 : thumbnailPath!.hashCode) + (trashedAfter == null ? 0 : trashedAfter!.hashCode) + (trashedBefore == null ? 0 : trashedBefore!.hashCode) + (type == null ? 0 : type!.hashCode) + @@ -484,7 +506,7 @@ class MetadataSearchDto { (withStacked == null ? 0 : withStacked!.hashCode); @override - String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isArchived=$isArchived, isEncoded=$isEncoded, isExternal=$isExternal, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isReadOnly=$isReadOnly, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, resizePath=$resizePath, size=$size, state=$state, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, webpPath=$webpPath, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; + String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isArchived=$isArchived, isEncoded=$isEncoded, isExternal=$isExternal, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isReadOnly=$isReadOnly, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, resizePath=$resizePath, size=$size, state=$state, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, webpPath=$webpPath, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; Map toJson() { final json = {}; @@ -619,6 +641,11 @@ class MetadataSearchDto { // json[r'page'] = null; } json[r'personIds'] = this.personIds; + if (this.previewPath != null) { + json[r'previewPath'] = this.previewPath; + } else { + // json[r'previewPath'] = null; + } if (this.resizePath != null) { json[r'resizePath'] = this.resizePath; } else { @@ -644,6 +671,11 @@ class MetadataSearchDto { } else { // json[r'takenBefore'] = null; } + if (this.thumbnailPath != null) { + json[r'thumbnailPath'] = this.thumbnailPath; + } else { + // json[r'thumbnailPath'] = null; + } if (this.trashedAfter != null) { json[r'trashedAfter'] = this.trashedAfter!.toUtc().toIso8601String(); } else { @@ -735,11 +767,13 @@ class MetadataSearchDto { personIds: json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) : const [], + previewPath: mapValueOfType(json, r'previewPath'), resizePath: mapValueOfType(json, r'resizePath'), size: num.parse('${json[r'size']}'), state: mapValueOfType(json, r'state'), takenAfter: mapDateTime(json, r'takenAfter', r''), takenBefore: mapDateTime(json, r'takenBefore', r''), + thumbnailPath: mapValueOfType(json, r'thumbnailPath'), trashedAfter: mapDateTime(json, r'trashedAfter', r''), trashedBefore: mapDateTime(json, r'trashedBefore', r''), type: AssetTypeEnum.fromJson(json[r'type']), diff --git a/mobile/openapi/lib/model/path_type.dart b/mobile/openapi/lib/model/path_type.dart index ea722076d..11cdf41ea 100644 --- a/mobile/openapi/lib/model/path_type.dart +++ b/mobile/openapi/lib/model/path_type.dart @@ -24,8 +24,8 @@ class PathType { String toJson() => value; static const original = PathType._(r'original'); - static const jpegThumbnail = PathType._(r'jpeg_thumbnail'); - static const webpThumbnail = PathType._(r'webp_thumbnail'); + static const preview = PathType._(r'preview'); + static const thumbnail = PathType._(r'thumbnail'); static const encodedVideo = PathType._(r'encoded_video'); static const sidecar = PathType._(r'sidecar'); static const face = PathType._(r'face'); @@ -34,8 +34,8 @@ class PathType { /// List of all possible values in this [enum][PathType]. static const values = [ original, - jpegThumbnail, - webpThumbnail, + preview, + thumbnail, encodedVideo, sidecar, face, @@ -79,8 +79,8 @@ class PathTypeTypeTransformer { if (data != null) { switch (data) { case r'original': return PathType.original; - case r'jpeg_thumbnail': return PathType.jpegThumbnail; - case r'webp_thumbnail': return PathType.webpThumbnail; + case r'preview': return PathType.preview; + case r'thumbnail': return PathType.thumbnail; case r'encoded_video': return PathType.encodedVideo; case r'sidecar': return PathType.sidecar; case r'face': return PathType.face; diff --git a/mobile/openapi/lib/model/system_config_dto.dart b/mobile/openapi/lib/model/system_config_dto.dart index 0b5f64fc2..f075d37c8 100644 --- a/mobile/openapi/lib/model/system_config_dto.dart +++ b/mobile/openapi/lib/model/system_config_dto.dart @@ -14,6 +14,7 @@ class SystemConfigDto { /// Returns a new [SystemConfigDto] instance. SystemConfigDto({ required this.ffmpeg, + required this.image, required this.job, required this.library_, required this.logging, @@ -26,13 +27,14 @@ class SystemConfigDto { required this.server, required this.storageTemplate, required this.theme, - required this.thumbnail, required this.trash, required this.user, }); SystemConfigFFmpegDto ffmpeg; + SystemConfigImageDto image; + SystemConfigJobDto job; SystemConfigLibraryDto library_; @@ -57,8 +59,6 @@ class SystemConfigDto { SystemConfigThemeDto theme; - SystemConfigThumbnailDto thumbnail; - SystemConfigTrashDto trash; SystemConfigUserDto user; @@ -66,6 +66,7 @@ class SystemConfigDto { @override bool operator ==(Object other) => identical(this, other) || other is SystemConfigDto && other.ffmpeg == ffmpeg && + other.image == image && other.job == job && other.library_ == library_ && other.logging == logging && @@ -78,7 +79,6 @@ class SystemConfigDto { other.server == server && other.storageTemplate == storageTemplate && other.theme == theme && - other.thumbnail == thumbnail && other.trash == trash && other.user == user; @@ -86,6 +86,7 @@ class SystemConfigDto { int get hashCode => // ignore: unnecessary_parenthesis (ffmpeg.hashCode) + + (image.hashCode) + (job.hashCode) + (library_.hashCode) + (logging.hashCode) + @@ -98,16 +99,16 @@ class SystemConfigDto { (server.hashCode) + (storageTemplate.hashCode) + (theme.hashCode) + - (thumbnail.hashCode) + (trash.hashCode) + (user.hashCode); @override - String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, newVersionCheck=$newVersionCheck, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, theme=$theme, thumbnail=$thumbnail, trash=$trash, user=$user]'; + String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, image=$image, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, newVersionCheck=$newVersionCheck, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, theme=$theme, trash=$trash, user=$user]'; Map toJson() { final json = {}; json[r'ffmpeg'] = this.ffmpeg; + json[r'image'] = this.image; json[r'job'] = this.job; json[r'library'] = this.library_; json[r'logging'] = this.logging; @@ -120,7 +121,6 @@ class SystemConfigDto { json[r'server'] = this.server; json[r'storageTemplate'] = this.storageTemplate; json[r'theme'] = this.theme; - json[r'thumbnail'] = this.thumbnail; json[r'trash'] = this.trash; json[r'user'] = this.user; return json; @@ -135,6 +135,7 @@ class SystemConfigDto { return SystemConfigDto( ffmpeg: SystemConfigFFmpegDto.fromJson(json[r'ffmpeg'])!, + image: SystemConfigImageDto.fromJson(json[r'image'])!, job: SystemConfigJobDto.fromJson(json[r'job'])!, library_: SystemConfigLibraryDto.fromJson(json[r'library'])!, logging: SystemConfigLoggingDto.fromJson(json[r'logging'])!, @@ -147,7 +148,6 @@ class SystemConfigDto { server: SystemConfigServerDto.fromJson(json[r'server'])!, storageTemplate: SystemConfigStorageTemplateDto.fromJson(json[r'storageTemplate'])!, theme: SystemConfigThemeDto.fromJson(json[r'theme'])!, - thumbnail: SystemConfigThumbnailDto.fromJson(json[r'thumbnail'])!, trash: SystemConfigTrashDto.fromJson(json[r'trash'])!, user: SystemConfigUserDto.fromJson(json[r'user'])!, ); @@ -198,6 +198,7 @@ class SystemConfigDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { 'ffmpeg', + 'image', 'job', 'library', 'logging', @@ -210,7 +211,6 @@ class SystemConfigDto { 'server', 'storageTemplate', 'theme', - 'thumbnail', 'trash', 'user', }; diff --git a/mobile/openapi/lib/model/system_config_image_dto.dart b/mobile/openapi/lib/model/system_config_image_dto.dart new file mode 100644 index 000000000..1c830861a --- /dev/null +++ b/mobile/openapi/lib/model/system_config_image_dto.dart @@ -0,0 +1,138 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 SystemConfigImageDto { + /// Returns a new [SystemConfigImageDto] instance. + SystemConfigImageDto({ + required this.colorspace, + required this.previewFormat, + required this.previewSize, + required this.quality, + required this.thumbnailFormat, + required this.thumbnailSize, + }); + + Colorspace colorspace; + + ImageFormat previewFormat; + + int previewSize; + + int quality; + + ImageFormat thumbnailFormat; + + int thumbnailSize; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigImageDto && + other.colorspace == colorspace && + other.previewFormat == previewFormat && + other.previewSize == previewSize && + other.quality == quality && + other.thumbnailFormat == thumbnailFormat && + other.thumbnailSize == thumbnailSize; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (colorspace.hashCode) + + (previewFormat.hashCode) + + (previewSize.hashCode) + + (quality.hashCode) + + (thumbnailFormat.hashCode) + + (thumbnailSize.hashCode); + + @override + String toString() => 'SystemConfigImageDto[colorspace=$colorspace, previewFormat=$previewFormat, previewSize=$previewSize, quality=$quality, thumbnailFormat=$thumbnailFormat, thumbnailSize=$thumbnailSize]'; + + Map toJson() { + final json = {}; + json[r'colorspace'] = this.colorspace; + json[r'previewFormat'] = this.previewFormat; + json[r'previewSize'] = this.previewSize; + json[r'quality'] = this.quality; + json[r'thumbnailFormat'] = this.thumbnailFormat; + json[r'thumbnailSize'] = this.thumbnailSize; + return json; + } + + /// Returns a new [SystemConfigImageDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigImageDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return SystemConfigImageDto( + colorspace: Colorspace.fromJson(json[r'colorspace'])!, + previewFormat: ImageFormat.fromJson(json[r'previewFormat'])!, + previewSize: mapValueOfType(json, r'previewSize')!, + quality: mapValueOfType(json, r'quality')!, + thumbnailFormat: ImageFormat.fromJson(json[r'thumbnailFormat'])!, + thumbnailSize: mapValueOfType(json, r'thumbnailSize')!, + ); + } + 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 = SystemConfigImageDto.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 = SystemConfigImageDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigImageDto-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] = SystemConfigImageDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'colorspace', + 'previewFormat', + 'previewSize', + 'quality', + 'thumbnailFormat', + 'thumbnailSize', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_thumbnail_dto.dart b/mobile/openapi/lib/model/system_config_thumbnail_dto.dart deleted file mode 100644 index f1d55d622..000000000 --- a/mobile/openapi/lib/model/system_config_thumbnail_dto.dart +++ /dev/null @@ -1,122 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.12 - -// 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 SystemConfigThumbnailDto { - /// Returns a new [SystemConfigThumbnailDto] instance. - SystemConfigThumbnailDto({ - required this.colorspace, - required this.jpegSize, - required this.quality, - required this.webpSize, - }); - - Colorspace colorspace; - - int jpegSize; - - int quality; - - int webpSize; - - @override - bool operator ==(Object other) => identical(this, other) || other is SystemConfigThumbnailDto && - other.colorspace == colorspace && - other.jpegSize == jpegSize && - other.quality == quality && - other.webpSize == webpSize; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (colorspace.hashCode) + - (jpegSize.hashCode) + - (quality.hashCode) + - (webpSize.hashCode); - - @override - String toString() => 'SystemConfigThumbnailDto[colorspace=$colorspace, jpegSize=$jpegSize, quality=$quality, webpSize=$webpSize]'; - - Map toJson() { - final json = {}; - json[r'colorspace'] = this.colorspace; - json[r'jpegSize'] = this.jpegSize; - json[r'quality'] = this.quality; - json[r'webpSize'] = this.webpSize; - return json; - } - - /// Returns a new [SystemConfigThumbnailDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static SystemConfigThumbnailDto? fromJson(dynamic value) { - if (value is Map) { - final json = value.cast(); - - return SystemConfigThumbnailDto( - colorspace: Colorspace.fromJson(json[r'colorspace'])!, - jpegSize: mapValueOfType(json, r'jpegSize')!, - quality: mapValueOfType(json, r'quality')!, - webpSize: mapValueOfType(json, r'webpSize')!, - ); - } - 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 = SystemConfigThumbnailDto.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 = SystemConfigThumbnailDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of SystemConfigThumbnailDto-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] = SystemConfigThumbnailDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'colorspace', - 'jpegSize', - 'quality', - 'webpSize', - }; -} - diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index 41d0ac8f5..0c1729e95 100644 --- a/mobile/openapi/test/asset_api_test.dart +++ b/mobile/openapi/test/asset_api_test.dart @@ -100,7 +100,7 @@ void main() { // TODO }); - //Future> searchAssets({ String checksum, String city, String country, DateTime createdAfter, DateTime createdBefore, String deviceAssetId, String deviceId, String encodedVideoPath, String id, bool isArchived, bool isEncoded, bool isExternal, bool isFavorite, bool isMotion, bool isNotInAlbum, bool isOffline, bool isReadOnly, bool isVisible, String lensModel, String libraryId, String make, String model, AssetOrder order, String originalFileName, String originalPath, num page, List personIds, String resizePath, num size, String state, DateTime takenAfter, DateTime takenBefore, DateTime trashedAfter, DateTime trashedBefore, AssetTypeEnum type, DateTime updatedAfter, DateTime updatedBefore, String webpPath, bool withArchived, bool withDeleted, bool withExif, bool withPeople, bool withStacked }) async + //Future> searchAssets({ String checksum, String city, String country, DateTime createdAfter, DateTime createdBefore, String deviceAssetId, String deviceId, String encodedVideoPath, String id, bool isArchived, bool isEncoded, bool isExternal, bool isFavorite, bool isMotion, bool isNotInAlbum, bool isOffline, bool isReadOnly, bool isVisible, String lensModel, String libraryId, String make, String model, AssetOrder order, String originalFileName, String originalPath, num page, List personIds, String previewPath, String resizePath, num size, String state, DateTime takenAfter, DateTime takenBefore, String thumbnailPath, DateTime trashedAfter, DateTime trashedBefore, AssetTypeEnum type, DateTime updatedAfter, DateTime updatedBefore, String webpPath, bool withArchived, bool withDeleted, bool withExif, bool withPeople, bool withStacked }) async test('test searchAssets', () async { // TODO }); diff --git a/mobile/openapi/test/image_format_test.dart b/mobile/openapi/test/image_format_test.dart new file mode 100644 index 000000000..2bb1512a6 --- /dev/null +++ b/mobile/openapi/test/image_format_test.dart @@ -0,0 +1,21 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for ImageFormat +void main() { + + group('test ImageFormat', () { + + }); + +} diff --git a/mobile/openapi/test/metadata_search_dto_test.dart b/mobile/openapi/test/metadata_search_dto_test.dart index f817b7da7..62979da9c 100644 --- a/mobile/openapi/test/metadata_search_dto_test.dart +++ b/mobile/openapi/test/metadata_search_dto_test.dart @@ -151,6 +151,11 @@ void main() { // TODO }); + // String previewPath + test('to test the property `previewPath`', () async { + // TODO + }); + // String resizePath test('to test the property `resizePath`', () async { // TODO @@ -176,6 +181,11 @@ void main() { // TODO }); + // String thumbnailPath + test('to test the property `thumbnailPath`', () async { + // TODO + }); + // DateTime trashedAfter test('to test the property `trashedAfter`', () async { // TODO diff --git a/mobile/openapi/test/system_config_dto_test.dart b/mobile/openapi/test/system_config_dto_test.dart index b41d07e5f..e88ee17c4 100644 --- a/mobile/openapi/test/system_config_dto_test.dart +++ b/mobile/openapi/test/system_config_dto_test.dart @@ -21,6 +21,11 @@ void main() { // TODO }); + // SystemConfigImageDto image + test('to test the property `image`', () async { + // TODO + }); + // SystemConfigJobDto job test('to test the property `job`', () async { // TODO @@ -81,11 +86,6 @@ void main() { // TODO }); - // SystemConfigThumbnailDto thumbnail - test('to test the property `thumbnail`', () async { - // TODO - }); - // SystemConfigTrashDto trash test('to test the property `trash`', () async { // TODO diff --git a/mobile/openapi/test/system_config_thumbnail_dto_test.dart b/mobile/openapi/test/system_config_image_dto_test.dart similarity index 53% rename from mobile/openapi/test/system_config_thumbnail_dto_test.dart rename to mobile/openapi/test/system_config_image_dto_test.dart index 3cc66f467..aef907bbe 100644 --- a/mobile/openapi/test/system_config_thumbnail_dto_test.dart +++ b/mobile/openapi/test/system_config_image_dto_test.dart @@ -11,18 +11,23 @@ import 'package:openapi/api.dart'; import 'package:test/test.dart'; -// tests for SystemConfigThumbnailDto +// tests for SystemConfigImageDto void main() { - // final instance = SystemConfigThumbnailDto(); + // final instance = SystemConfigImageDto(); - group('test SystemConfigThumbnailDto', () { + group('test SystemConfigImageDto', () { // Colorspace colorspace test('to test the property `colorspace`', () async { // TODO }); - // int jpegSize - test('to test the property `jpegSize`', () async { + // ImageFormat previewFormat + test('to test the property `previewFormat`', () async { + // TODO + }); + + // int previewSize + test('to test the property `previewSize`', () async { // TODO }); @@ -31,8 +36,13 @@ void main() { // TODO }); - // int webpSize - test('to test the property `webpSize`', () async { + // ImageFormat thumbnailFormat + test('to test the property `thumbnailFormat`', () async { + // TODO + }); + + // int thumbnailSize + test('to test the property `thumbnailSize`', () async { // TODO }); diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index a2aae92c4..16a9ad63a 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -2101,10 +2101,19 @@ } } }, + { + "name": "previewPath", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, { "name": "resizePath", "required": false, "in": "query", + "deprecated": true, "schema": { "type": "string" } @@ -2143,6 +2152,14 @@ "type": "string" } }, + { + "name": "thumbnailPath", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, { "name": "trashedAfter", "required": false, @@ -2191,6 +2208,7 @@ "name": "webpPath", "required": false, "in": "query", + "deprecated": true, "schema": { "type": "string" } @@ -8114,6 +8132,13 @@ ], "type": "object" }, + "ImageFormat": { + "enum": [ + "jpeg", + "webp" + ], + "type": "string" + }, "JobCommand": { "enum": [ "start", @@ -8555,7 +8580,11 @@ }, "type": "array" }, + "previewPath": { + "type": "string" + }, "resizePath": { + "deprecated": true, "type": "string" }, "size": { @@ -8572,6 +8601,9 @@ "format": "date-time", "type": "string" }, + "thumbnailPath": { + "type": "string" + }, "trashedAfter": { "format": "date-time", "type": "string" @@ -8592,6 +8624,7 @@ "type": "string" }, "webpPath": { + "deprecated": true, "type": "string" }, "withArchived": { @@ -8746,8 +8779,8 @@ "PathType": { "enum": [ "original", - "jpeg_thumbnail", - "webp_thumbnail", + "preview", + "thumbnail", "encoded_video", "sidecar", "face", @@ -9743,6 +9776,9 @@ "ffmpeg": { "$ref": "#/components/schemas/SystemConfigFFmpegDto" }, + "image": { + "$ref": "#/components/schemas/SystemConfigImageDto" + }, "job": { "$ref": "#/components/schemas/SystemConfigJobDto" }, @@ -9779,9 +9815,6 @@ "theme": { "$ref": "#/components/schemas/SystemConfigThemeDto" }, - "thumbnail": { - "$ref": "#/components/schemas/SystemConfigThumbnailDto" - }, "trash": { "$ref": "#/components/schemas/SystemConfigTrashDto" }, @@ -9791,6 +9824,7 @@ }, "required": [ "ffmpeg", + "image", "job", "library", "logging", @@ -9803,7 +9837,6 @@ "server", "storageTemplate", "theme", - "thumbnail", "trash", "user" ], @@ -9902,6 +9935,37 @@ ], "type": "object" }, + "SystemConfigImageDto": { + "properties": { + "colorspace": { + "$ref": "#/components/schemas/Colorspace" + }, + "previewFormat": { + "$ref": "#/components/schemas/ImageFormat" + }, + "previewSize": { + "type": "integer" + }, + "quality": { + "type": "integer" + }, + "thumbnailFormat": { + "$ref": "#/components/schemas/ImageFormat" + }, + "thumbnailSize": { + "type": "integer" + } + }, + "required": [ + "colorspace", + "previewFormat", + "previewSize", + "quality", + "thumbnailFormat", + "thumbnailSize" + ], + "type": "object" + }, "SystemConfigJobDto": { "properties": { "backgroundTask": { @@ -10251,29 +10315,6 @@ ], "type": "object" }, - "SystemConfigThumbnailDto": { - "properties": { - "colorspace": { - "$ref": "#/components/schemas/Colorspace" - }, - "jpegSize": { - "type": "integer" - }, - "quality": { - "type": "integer" - }, - "webpSize": { - "type": "integer" - } - }, - "required": [ - "colorspace", - "jpegSize", - "quality", - "webpSize" - ], - "type": "object" - }, "SystemConfigTrashDto": { "properties": { "days": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index b9b4978a9..e63ccb4d6 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -640,11 +640,13 @@ export type MetadataSearchDto = { originalPath?: string; page?: number; personIds?: string[]; + previewPath?: string; resizePath?: string; size?: number; state?: string; takenAfter?: string; takenBefore?: string; + thumbnailPath?: string; trashedAfter?: string; trashedBefore?: string; "type"?: AssetTypeEnum; @@ -827,6 +829,14 @@ export type SystemConfigFFmpegDto = { transcode: TranscodePolicy; twoPass: boolean; }; +export type SystemConfigImageDto = { + colorspace: Colorspace; + previewFormat: ImageFormat; + previewSize: number; + quality: number; + thumbnailFormat: ImageFormat; + thumbnailSize: number; +}; export type JobSettingsDto = { concurrency: number; }; @@ -919,12 +929,6 @@ export type SystemConfigStorageTemplateDto = { export type SystemConfigThemeDto = { customCss: string; }; -export type SystemConfigThumbnailDto = { - colorspace: Colorspace; - jpegSize: number; - quality: number; - webpSize: number; -}; export type SystemConfigTrashDto = { days: number; enabled: boolean; @@ -934,6 +938,7 @@ export type SystemConfigUserDto = { }; export type SystemConfigDto = { ffmpeg: SystemConfigFFmpegDto; + image: SystemConfigImageDto; job: SystemConfigJobDto; library: SystemConfigLibraryDto; logging: SystemConfigLoggingDto; @@ -946,7 +951,6 @@ export type SystemConfigDto = { server: SystemConfigServerDto; storageTemplate: SystemConfigStorageTemplateDto; theme: SystemConfigThemeDto; - thumbnail: SystemConfigThumbnailDto; trash: SystemConfigTrashDto; user: SystemConfigUserDto; }; @@ -1497,7 +1501,7 @@ export function updateAsset({ id, updateAssetDto }: { body: updateAssetDto }))); } -export function searchAssets({ checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked }: { +export function searchAssets({ checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, previewPath, resizePath, size, state, takenAfter, takenBefore, thumbnailPath, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked }: { checksum?: string; city?: string; country?: string; @@ -1525,11 +1529,13 @@ export function searchAssets({ checksum, city, country, createdAfter, createdBef originalPath?: string; page?: number; personIds?: string[]; + previewPath?: string; resizePath?: string; size?: number; state?: string; takenAfter?: string; takenBefore?: string; + thumbnailPath?: string; trashedAfter?: string; trashedBefore?: string; $type?: AssetTypeEnum; @@ -1573,11 +1579,13 @@ export function searchAssets({ checksum, city, country, createdAfter, createdBef originalPath, page, personIds, + previewPath, resizePath, size, state, takenAfter, takenBefore, + thumbnailPath, trashedAfter, trashedBefore, "type": $type, @@ -2802,8 +2810,8 @@ export enum PathEntityType { } export enum PathType { Original = "original", - JpegThumbnail = "jpeg_thumbnail", - WebpThumbnail = "webp_thumbnail", + Preview = "preview", + Thumbnail = "thumbnail", EncodedVideo = "encoded_video", Sidecar = "sidecar", Face = "face", @@ -2885,6 +2893,14 @@ export enum TranscodePolicy { Required = "required", Disabled = "disabled" } +export enum Colorspace { + Srgb = "srgb", + P3 = "p3" +} +export enum ImageFormat { + Jpeg = "jpeg", + Webp = "webp" +} export enum LogLevel { Verbose = "verbose", Debug = "debug", @@ -2901,10 +2917,6 @@ export enum ModelType { FacialRecognition = "facial-recognition", Clip = "clip" } -export enum Colorspace { - Srgb = "srgb", - P3 = "p3" -} export enum MapTheme { Light = "light", Dark = "dark" diff --git a/server/src/cores/storage.core.ts b/server/src/cores/storage.core.ts index ee9f12e51..035f90c91 100644 --- a/server/src/cores/storage.core.ts +++ b/server/src/cores/storage.core.ts @@ -4,6 +4,7 @@ import { SystemConfigCore } from 'src/cores/system-config.core'; import { AssetEntity } from 'src/entities/asset.entity'; import { AssetPathType, PathType, PersonPathType } from 'src/entities/move.entity'; import { PersonEntity } from 'src/entities/person.entity'; +import { ImageFormat } from 'src/entities/system-config.entity'; import { IAssetRepository } from 'src/interfaces/asset.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; @@ -34,7 +35,8 @@ export interface MoveRequest { }; } -type GeneratedAssetPath = AssetPathType.JPEG_THUMBNAIL | AssetPathType.WEBP_THUMBNAIL | AssetPathType.ENCODED_VIDEO; +export type GeneratedImageType = AssetPathType.PREVIEW | AssetPathType.THUMBNAIL; +export type GeneratedAssetType = GeneratedImageType | AssetPathType.ENCODED_VIDEO; let instance: StorageCore | null; @@ -94,12 +96,8 @@ export class StorageCore { return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, person.ownerId, `${person.id}.jpeg`); } - static getLargeThumbnailPath(asset: AssetEntity) { - return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}.jpeg`); - } - - static getSmallThumbnailPath(asset: AssetEntity) { - return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}.webp`); + static getImagePath(asset: AssetEntity, type: GeneratedImageType, format: ImageFormat) { + return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}-${type}.${format}`); } static getEncodedVideoPath(asset: AssetEntity) { @@ -128,34 +126,23 @@ export class StorageCore { return path.startsWith(THUMBNAIL_DIR) || path.startsWith(ENCODED_VIDEO_DIR); } - async moveAssetFile(asset: AssetEntity, pathType: GeneratedAssetPath) { - const { id: entityId, resizePath, webpPath, encodedVideoPath } = asset; - switch (pathType) { - case AssetPathType.JPEG_THUMBNAIL: { - return this.moveFile({ - entityId, - pathType, - oldPath: resizePath, - newPath: StorageCore.getLargeThumbnailPath(asset), - }); - } - case AssetPathType.WEBP_THUMBNAIL: { - return this.moveFile({ - entityId, - pathType, - oldPath: webpPath, - newPath: StorageCore.getSmallThumbnailPath(asset), - }); - } - case AssetPathType.ENCODED_VIDEO: { - return this.moveFile({ - entityId, - pathType, - oldPath: encodedVideoPath, - newPath: StorageCore.getEncodedVideoPath(asset), - }); - } - } + async moveAssetImage(asset: AssetEntity, pathType: GeneratedImageType, format: ImageFormat) { + const { id: entityId, previewPath, thumbnailPath } = asset; + return this.moveFile({ + entityId, + pathType, + oldPath: pathType === AssetPathType.PREVIEW ? previewPath : thumbnailPath, + newPath: StorageCore.getImagePath(asset, AssetPathType.THUMBNAIL, format), + }); + } + + async moveAssetVideo(asset: AssetEntity) { + return this.moveFile({ + entityId: asset.id, + pathType: AssetPathType.ENCODED_VIDEO, + oldPath: asset.encodedVideoPath, + newPath: StorageCore.getEncodedVideoPath(asset), + }); } async movePersonFile(person: PersonEntity, pathType: PersonPathType) { @@ -294,11 +281,11 @@ export class StorageCore { case AssetPathType.ORIGINAL: { return this.assetRepository.update({ id, originalPath: newPath }); } - case AssetPathType.JPEG_THUMBNAIL: { - return this.assetRepository.update({ id, resizePath: newPath }); + case AssetPathType.PREVIEW: { + return this.assetRepository.update({ id, previewPath: newPath }); } - case AssetPathType.WEBP_THUMBNAIL: { - return this.assetRepository.update({ id, webpPath: newPath }); + case AssetPathType.THUMBNAIL: { + return this.assetRepository.update({ id, thumbnailPath: newPath }); } case AssetPathType.ENCODED_VIDEO: { return this.assetRepository.update({ id, encodedVideoPath: newPath }); diff --git a/server/src/cores/system-config.core.ts b/server/src/cores/system-config.core.ts index 01bfacc9b..3a1ea47bb 100644 --- a/server/src/cores/system-config.core.ts +++ b/server/src/cores/system-config.core.ts @@ -10,6 +10,7 @@ import { AudioCodec, CQMode, Colorspace, + ImageFormat, LogLevel, SystemConfig, SystemConfigEntity, @@ -112,9 +113,11 @@ export const defaults = Object.freeze({ hashVerificationEnabled: true, template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', }, - thumbnail: { - webpSize: 250, - jpegSize: 1440, + image: { + thumbnailFormat: ImageFormat.WEBP, + thumbnailSize: 250, + previewFormat: ImageFormat.JPEG, + previewSize: 1440, quality: 80, colorspace: Colorspace.P3, }, diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index cf16a99a2..bdda36d15 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -82,7 +82,7 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As type: entity.type, thumbhash: entity.thumbhash?.toString('base64') ?? null, localDateTime: entity.localDateTime, - resized: !!entity.resizePath, + resized: !!entity.previewPath, duration: entity.duration ?? '0:00:00.00000', livePhotoVideoId: entity.livePhotoVideoId, hasMetadata: false, @@ -100,7 +100,7 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As type: entity.type, originalPath: entity.originalPath, originalFileName: entity.originalFileName, - resized: !!entity.resizePath, + resized: !!entity.previewPath, thumbhash: entity.thumbhash?.toString('base64') ?? null, fileCreatedAt: entity.fileCreatedAt, fileModifiedAt: entity.fileModifiedAt, diff --git a/server/src/dtos/search.dto.ts b/server/src/dtos/search.dto.ts index 799baddee..d96ce0d98 100644 --- a/server/src/dtos/search.dto.ts +++ b/server/src/dtos/search.dto.ts @@ -163,13 +163,25 @@ export class MetadataSearchDto extends BaseSearchDto { @IsString() @IsNotEmpty() @Optional() + @ApiProperty({ deprecated: true }) resizePath?: string; @IsString() @IsNotEmpty() @Optional() + @ApiProperty({ deprecated: true }) webpPath?: string; + @IsString() + @IsNotEmpty() + @Optional() + previewPath?: string; + + @IsString() + @IsNotEmpty() + @Optional() + thumbnailPath?: string; + @IsString() @IsNotEmpty() @Optional() diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts index 740f1672e..9f80e8d6a 100644 --- a/server/src/dtos/system-config.dto.ts +++ b/server/src/dtos/system-config.dto.ts @@ -22,6 +22,7 @@ import { AudioCodec, CQMode, Colorspace, + ImageFormat, LogLevel, SystemConfig, ToneMapping, @@ -385,18 +386,26 @@ export class SystemConfigThemeDto { customCss!: string; } -class SystemConfigThumbnailDto { - @IsInt() - @Min(1) - @Type(() => Number) - @ApiProperty({ type: 'integer' }) - webpSize!: number; +class SystemConfigImageDto { + @IsEnum(ImageFormat) + @ApiProperty({ enumName: 'ImageFormat', enum: ImageFormat }) + thumbnailFormat!: ImageFormat; @IsInt() @Min(1) @Type(() => Number) @ApiProperty({ type: 'integer' }) - jpegSize!: number; + thumbnailSize!: number; + + @IsEnum(ImageFormat) + @ApiProperty({ enumName: 'ImageFormat', enum: ImageFormat }) + previewFormat!: ImageFormat; + + @IsInt() + @Min(1) + @Type(() => Number) + @ApiProperty({ type: 'integer' }) + previewSize!: number; @IsInt() @Min(1) @@ -480,10 +489,10 @@ export class SystemConfigDto implements SystemConfig { @IsObject() job!: SystemConfigJobDto; - @Type(() => SystemConfigThumbnailDto) + @Type(() => SystemConfigImageDto) @ValidateNested() @IsObject() - thumbnail!: SystemConfigThumbnailDto; + image!: SystemConfigImageDto; @Type(() => SystemConfigTrashDto) @ValidateNested() diff --git a/server/src/entities/asset.entity.ts b/server/src/entities/asset.entity.ts index 916636115..c977560be 100644 --- a/server/src/entities/asset.entity.ts +++ b/server/src/entities/asset.entity.ts @@ -67,10 +67,10 @@ export class AssetEntity { originalPath!: string; @Column({ type: 'varchar', nullable: true }) - resizePath!: string | null; + previewPath!: string | null; @Column({ type: 'varchar', nullable: true, default: '' }) - webpPath!: string | null; + thumbnailPath!: string | null; @Column({ type: 'bytea', nullable: true }) thumbhash!: Buffer | null; diff --git a/server/src/entities/move.entity.ts b/server/src/entities/move.entity.ts index de20cb973..f3dad6b28 100644 --- a/server/src/entities/move.entity.ts +++ b/server/src/entities/move.entity.ts @@ -24,8 +24,8 @@ export class MoveEntity { export enum AssetPathType { ORIGINAL = 'original', - JPEG_THUMBNAIL = 'jpeg_thumbnail', - WEBP_THUMBNAIL = 'webp_thumbnail', + PREVIEW = 'preview', + THUMBNAIL = 'thumbnail', ENCODED_VIDEO = 'encoded_video', SIDECAR = 'sidecar', } diff --git a/server/src/entities/system-config.entity.ts b/server/src/entities/system-config.entity.ts index 98b882a36..e07b6d4a2 100644 --- a/server/src/entities/system-config.entity.ts +++ b/server/src/entities/system-config.entity.ts @@ -165,6 +165,11 @@ export enum Colorspace { P3 = 'p3', } +export enum ImageFormat { + JPEG = 'jpeg', + WEBP = 'webp', +} + export enum LogLevel { VERBOSE = 'verbose', DEBUG = 'debug', @@ -249,9 +254,11 @@ export interface SystemConfig { hashVerificationEnabled: boolean; template: string; }; - thumbnail: { - webpSize: number; - jpegSize: number; + image: { + thumbnailFormat: ImageFormat; + thumbnailSize: number; + previewFormat: ImageFormat; + previewSize: number; quality: number; colorspace: Colorspace; }; diff --git a/server/src/interfaces/job.interface.ts b/server/src/interfaces/job.interface.ts index 6f07fc752..eddaefcf3 100644 --- a/server/src/interfaces/job.interface.ts +++ b/server/src/interfaces/job.interface.ts @@ -33,9 +33,9 @@ export enum JobName { // thumbnails QUEUE_GENERATE_THUMBNAILS = 'queue-generate-thumbnails', - GENERATE_JPEG_THUMBNAIL = 'generate-jpeg-thumbnail', - GENERATE_WEBP_THUMBNAIL = 'generate-webp-thumbnail', - GENERATE_THUMBHASH_THUMBNAIL = 'generate-thumbhash-thumbnail', + GENERATE_PREVIEW = 'generate-preview', + GENERATE_THUMBNAIL = 'generate-thumbnail', + GENERATE_THUMBHASH = 'generate-thumbhash', GENERATE_PERSON_THUMBNAIL = 'generate-person-thumbnail', // metadata @@ -160,9 +160,9 @@ export type JobItem = // Thumbnails | { name: JobName.QUEUE_GENERATE_THUMBNAILS; data: IBaseJob } - | { name: JobName.GENERATE_JPEG_THUMBNAIL; data: IEntityJob } - | { name: JobName.GENERATE_WEBP_THUMBNAIL; data: IEntityJob } - | { name: JobName.GENERATE_THUMBHASH_THUMBNAIL; data: IEntityJob } + | { name: JobName.GENERATE_PREVIEW; data: IEntityJob } + | { name: JobName.GENERATE_THUMBNAIL; data: IEntityJob } + | { name: JobName.GENERATE_THUMBHASH; data: IEntityJob } // User | { name: JobName.USER_DELETE_CHECK; data?: IBaseJob } diff --git a/server/src/interfaces/media.interface.ts b/server/src/interfaces/media.interface.ts index 0e34227d3..5e51e94a5 100644 --- a/server/src/interfaces/media.interface.ts +++ b/server/src/interfaces/media.interface.ts @@ -1,11 +1,11 @@ import { Writable } from 'node:stream'; -import { TranscodeTarget, VideoCodec } from 'src/entities/system-config.entity'; +import { ImageFormat, TranscodeTarget, VideoCodec } from 'src/entities/system-config.entity'; export const IMediaRepository = 'IMediaRepository'; export interface ResizeOptions { size: number; - format: 'webp' | 'jpeg'; + format: ImageFormat; colorspace: string; quality: number; } diff --git a/server/src/interfaces/search.interface.ts b/server/src/interfaces/search.interface.ts index 1287202ad..771b23e9c 100644 --- a/server/src/interfaces/search.interface.ts +++ b/server/src/interfaces/search.interface.ts @@ -117,8 +117,8 @@ export interface SearchPathOptions { encodedVideoPath?: string; originalFileName?: string; originalPath?: string; - resizePath?: string; - webpPath?: string; + previewPath?: string; + thumbnailPath?: string; } export interface SearchExifOptions { diff --git a/server/src/migrations/1711257900274-RenameWebpJpegPaths.ts b/server/src/migrations/1711257900274-RenameWebpJpegPaths.ts new file mode 100644 index 000000000..ab6f2a4e9 --- /dev/null +++ b/server/src/migrations/1711257900274-RenameWebpJpegPaths.ts @@ -0,0 +1,51 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenameWebpJpegPaths1711257900274 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.renameColumn('assets', 'webpPath', 'thumbnailPath'); + await queryRunner.renameColumn('assets', 'resizePath', 'previewPath'); + await queryRunner.query(` + UPDATE system_config + SET key = 'image.previewSize' + WHERE key = 'thumbnail.jpegSize'`); + await queryRunner.query( + `UPDATE system_config + SET key = 'image.thumbnailSize' + WHERE key = 'thumbnail.webpSize'`, + ); + await queryRunner.query( + `UPDATE system_config + SET key = 'image.quality' + WHERE key = 'thumbnail.quality'`, + ); + await queryRunner.query( + `UPDATE system_config + SET key = 'image.colorspace' + WHERE key = 'thumbnail.colorspace'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.renameColumn('assets', 'thumbnailPath', 'webpPath'); + await queryRunner.renameColumn('assets', 'previewPath', 'resizePath'); + await queryRunner.query(` + UPDATE system_config + SET key = 'thumbnail.jpegSize' + WHERE key = 'image.previewSize'`); + await queryRunner.query( + `UPDATE system_config + SET key = 'thumbnail.webpSize' + WHERE key = 'image.thumbnailSize'`, + ); + await queryRunner.query( + `UPDATE system_config + SET key = 'thumbnail.quality' + WHERE key = 'image.quality'`, + ); + await queryRunner.query( + `UPDATE system_config + SET key = 'thumbnail.colorspace' + WHERE key = 'image.colorspace'`, + ); + } +} diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index fab0f5376..6f0e8cd5e 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -9,8 +9,8 @@ SELECT "entity"."deviceId" AS "entity_deviceId", "entity"."type" AS "entity_type", "entity"."originalPath" AS "entity_originalPath", - "entity"."resizePath" AS "entity_resizePath", - "entity"."webpPath" AS "entity_webpPath", + "entity"."previewPath" AS "entity_previewPath", + "entity"."thumbnailPath" AS "entity_thumbnailPath", "entity"."thumbhash" AS "entity_thumbhash", "entity"."encodedVideoPath" AS "entity_encodedVideoPath", "entity"."createdAt" AS "entity_createdAt", @@ -67,7 +67,7 @@ WHERE "entity"."ownerId" IN ($1) AND "entity"."isVisible" = true AND "entity"."isArchived" = false - AND "entity"."resizePath" IS NOT NULL + AND "entity"."previewPath" IS NOT NULL AND EXTRACT( DAY FROM @@ -92,8 +92,8 @@ SELECT "AssetEntity"."deviceId" AS "AssetEntity_deviceId", "AssetEntity"."type" AS "AssetEntity_type", "AssetEntity"."originalPath" AS "AssetEntity_originalPath", - "AssetEntity"."resizePath" AS "AssetEntity_resizePath", - "AssetEntity"."webpPath" AS "AssetEntity_webpPath", + "AssetEntity"."previewPath" AS "AssetEntity_previewPath", + "AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath", "AssetEntity"."thumbhash" AS "AssetEntity_thumbhash", "AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath", "AssetEntity"."createdAt" AS "AssetEntity_createdAt", @@ -128,8 +128,8 @@ SELECT "AssetEntity"."deviceId" AS "AssetEntity_deviceId", "AssetEntity"."type" AS "AssetEntity_type", "AssetEntity"."originalPath" AS "AssetEntity_originalPath", - "AssetEntity"."resizePath" AS "AssetEntity_resizePath", - "AssetEntity"."webpPath" AS "AssetEntity_webpPath", + "AssetEntity"."previewPath" AS "AssetEntity_previewPath", + "AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath", "AssetEntity"."thumbhash" AS "AssetEntity_thumbhash", "AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath", "AssetEntity"."createdAt" AS "AssetEntity_createdAt", @@ -213,8 +213,8 @@ SELECT "bd93d5747511a4dad4923546c51365bf1a803774"."deviceId" AS "bd93d5747511a4dad4923546c51365bf1a803774_deviceId", "bd93d5747511a4dad4923546c51365bf1a803774"."type" AS "bd93d5747511a4dad4923546c51365bf1a803774_type", "bd93d5747511a4dad4923546c51365bf1a803774"."originalPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_originalPath", - "bd93d5747511a4dad4923546c51365bf1a803774"."resizePath" AS "bd93d5747511a4dad4923546c51365bf1a803774_resizePath", - "bd93d5747511a4dad4923546c51365bf1a803774"."webpPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_webpPath", + "bd93d5747511a4dad4923546c51365bf1a803774"."previewPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_previewPath", + "bd93d5747511a4dad4923546c51365bf1a803774"."thumbnailPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_thumbnailPath", "bd93d5747511a4dad4923546c51365bf1a803774"."thumbhash" AS "bd93d5747511a4dad4923546c51365bf1a803774_thumbhash", "bd93d5747511a4dad4923546c51365bf1a803774"."encodedVideoPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_encodedVideoPath", "bd93d5747511a4dad4923546c51365bf1a803774"."createdAt" AS "bd93d5747511a4dad4923546c51365bf1a803774_createdAt", @@ -294,8 +294,8 @@ FROM "AssetEntity"."deviceId" AS "AssetEntity_deviceId", "AssetEntity"."type" AS "AssetEntity_type", "AssetEntity"."originalPath" AS "AssetEntity_originalPath", - "AssetEntity"."resizePath" AS "AssetEntity_resizePath", - "AssetEntity"."webpPath" AS "AssetEntity_webpPath", + "AssetEntity"."previewPath" AS "AssetEntity_previewPath", + "AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath", "AssetEntity"."thumbhash" AS "AssetEntity_thumbhash", "AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath", "AssetEntity"."createdAt" AS "AssetEntity_createdAt", @@ -391,8 +391,8 @@ SELECT "AssetEntity"."deviceId" AS "AssetEntity_deviceId", "AssetEntity"."type" AS "AssetEntity_type", "AssetEntity"."originalPath" AS "AssetEntity_originalPath", - "AssetEntity"."resizePath" AS "AssetEntity_resizePath", - "AssetEntity"."webpPath" AS "AssetEntity_webpPath", + "AssetEntity"."previewPath" AS "AssetEntity_previewPath", + "AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath", "AssetEntity"."thumbhash" AS "AssetEntity_thumbhash", "AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath", "AssetEntity"."createdAt" AS "AssetEntity_createdAt", @@ -437,8 +437,8 @@ SELECT "AssetEntity"."deviceId" AS "AssetEntity_deviceId", "AssetEntity"."type" AS "AssetEntity_type", "AssetEntity"."originalPath" AS "AssetEntity_originalPath", - "AssetEntity"."resizePath" AS "AssetEntity_resizePath", - "AssetEntity"."webpPath" AS "AssetEntity_webpPath", + "AssetEntity"."previewPath" AS "AssetEntity_previewPath", + "AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath", "AssetEntity"."thumbhash" AS "AssetEntity_thumbhash", "AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath", "AssetEntity"."createdAt" AS "AssetEntity_createdAt", @@ -481,8 +481,8 @@ SELECT "AssetEntity"."deviceId" AS "AssetEntity_deviceId", "AssetEntity"."type" AS "AssetEntity_type", "AssetEntity"."originalPath" AS "AssetEntity_originalPath", - "AssetEntity"."resizePath" AS "AssetEntity_resizePath", - "AssetEntity"."webpPath" AS "AssetEntity_webpPath", + "AssetEntity"."previewPath" AS "AssetEntity_previewPath", + "AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath", "AssetEntity"."thumbhash" AS "AssetEntity_thumbhash", "AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath", "AssetEntity"."createdAt" AS "AssetEntity_createdAt", @@ -570,8 +570,8 @@ SELECT "asset"."deviceId" AS "asset_deviceId", "asset"."type" AS "asset_type", "asset"."originalPath" AS "asset_originalPath", - "asset"."resizePath" AS "asset_resizePath", - "asset"."webpPath" AS "asset_webpPath", + "asset"."previewPath" AS "asset_previewPath", + "asset"."thumbnailPath" AS "asset_thumbnailPath", "asset"."thumbhash" AS "asset_thumbhash", "asset"."encodedVideoPath" AS "asset_encodedVideoPath", "asset"."createdAt" AS "asset_createdAt", @@ -629,8 +629,8 @@ SELECT "stackedAssets"."deviceId" AS "stackedAssets_deviceId", "stackedAssets"."type" AS "stackedAssets_type", "stackedAssets"."originalPath" AS "stackedAssets_originalPath", - "stackedAssets"."resizePath" AS "stackedAssets_resizePath", - "stackedAssets"."webpPath" AS "stackedAssets_webpPath", + "stackedAssets"."previewPath" AS "stackedAssets_previewPath", + "stackedAssets"."thumbnailPath" AS "stackedAssets_thumbnailPath", "stackedAssets"."thumbhash" AS "stackedAssets_thumbhash", "stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath", "stackedAssets"."createdAt" AS "stackedAssets_createdAt", diff --git a/server/src/queries/person.repository.sql b/server/src/queries/person.repository.sql index b6a513ff9..1cde746d8 100644 --- a/server/src/queries/person.repository.sql +++ b/server/src/queries/person.repository.sql @@ -152,8 +152,8 @@ FROM "AssetFaceEntity__AssetFaceEntity_asset"."deviceId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceId", "AssetFaceEntity__AssetFaceEntity_asset"."type" AS "AssetFaceEntity__AssetFaceEntity_asset_type", "AssetFaceEntity__AssetFaceEntity_asset"."originalPath" AS "AssetFaceEntity__AssetFaceEntity_asset_originalPath", - "AssetFaceEntity__AssetFaceEntity_asset"."resizePath" AS "AssetFaceEntity__AssetFaceEntity_asset_resizePath", - "AssetFaceEntity__AssetFaceEntity_asset"."webpPath" AS "AssetFaceEntity__AssetFaceEntity_asset_webpPath", + "AssetFaceEntity__AssetFaceEntity_asset"."previewPath" AS "AssetFaceEntity__AssetFaceEntity_asset_previewPath", + "AssetFaceEntity__AssetFaceEntity_asset"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbnailPath", "AssetFaceEntity__AssetFaceEntity_asset"."thumbhash" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbhash", "AssetFaceEntity__AssetFaceEntity_asset"."encodedVideoPath" AS "AssetFaceEntity__AssetFaceEntity_asset_encodedVideoPath", "AssetFaceEntity__AssetFaceEntity_asset"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_asset_createdAt", @@ -250,8 +250,8 @@ FROM "AssetEntity"."deviceId" AS "AssetEntity_deviceId", "AssetEntity"."type" AS "AssetEntity_type", "AssetEntity"."originalPath" AS "AssetEntity_originalPath", - "AssetEntity"."resizePath" AS "AssetEntity_resizePath", - "AssetEntity"."webpPath" AS "AssetEntity_webpPath", + "AssetEntity"."previewPath" AS "AssetEntity_previewPath", + "AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath", "AssetEntity"."thumbhash" AS "AssetEntity_thumbhash", "AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath", "AssetEntity"."createdAt" AS "AssetEntity_createdAt", @@ -380,8 +380,8 @@ SELECT "AssetFaceEntity__AssetFaceEntity_asset"."deviceId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceId", "AssetFaceEntity__AssetFaceEntity_asset"."type" AS "AssetFaceEntity__AssetFaceEntity_asset_type", "AssetFaceEntity__AssetFaceEntity_asset"."originalPath" AS "AssetFaceEntity__AssetFaceEntity_asset_originalPath", - "AssetFaceEntity__AssetFaceEntity_asset"."resizePath" AS "AssetFaceEntity__AssetFaceEntity_asset_resizePath", - "AssetFaceEntity__AssetFaceEntity_asset"."webpPath" AS "AssetFaceEntity__AssetFaceEntity_asset_webpPath", + "AssetFaceEntity__AssetFaceEntity_asset"."previewPath" AS "AssetFaceEntity__AssetFaceEntity_asset_previewPath", + "AssetFaceEntity__AssetFaceEntity_asset"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbnailPath", "AssetFaceEntity__AssetFaceEntity_asset"."thumbhash" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbhash", "AssetFaceEntity__AssetFaceEntity_asset"."encodedVideoPath" AS "AssetFaceEntity__AssetFaceEntity_asset_encodedVideoPath", "AssetFaceEntity__AssetFaceEntity_asset"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_asset_createdAt", diff --git a/server/src/queries/search.repository.sql b/server/src/queries/search.repository.sql index e985a1a6d..3e83d7238 100644 --- a/server/src/queries/search.repository.sql +++ b/server/src/queries/search.repository.sql @@ -14,8 +14,8 @@ FROM "asset"."deviceId" AS "asset_deviceId", "asset"."type" AS "asset_type", "asset"."originalPath" AS "asset_originalPath", - "asset"."resizePath" AS "asset_resizePath", - "asset"."webpPath" AS "asset_webpPath", + "asset"."previewPath" AS "asset_previewPath", + "asset"."thumbnailPath" AS "asset_thumbnailPath", "asset"."thumbhash" AS "asset_thumbhash", "asset"."encodedVideoPath" AS "asset_encodedVideoPath", "asset"."createdAt" AS "asset_createdAt", @@ -45,8 +45,8 @@ FROM "stackedAssets"."deviceId" AS "stackedAssets_deviceId", "stackedAssets"."type" AS "stackedAssets_type", "stackedAssets"."originalPath" AS "stackedAssets_originalPath", - "stackedAssets"."resizePath" AS "stackedAssets_resizePath", - "stackedAssets"."webpPath" AS "stackedAssets_webpPath", + "stackedAssets"."previewPath" AS "stackedAssets_previewPath", + "stackedAssets"."thumbnailPath" AS "stackedAssets_thumbnailPath", "stackedAssets"."thumbhash" AS "stackedAssets_thumbhash", "stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath", "stackedAssets"."createdAt" AS "stackedAssets_createdAt", @@ -110,8 +110,8 @@ SELECT "asset"."deviceId" AS "asset_deviceId", "asset"."type" AS "asset_type", "asset"."originalPath" AS "asset_originalPath", - "asset"."resizePath" AS "asset_resizePath", - "asset"."webpPath" AS "asset_webpPath", + "asset"."previewPath" AS "asset_previewPath", + "asset"."thumbnailPath" AS "asset_thumbnailPath", "asset"."thumbhash" AS "asset_thumbhash", "asset"."encodedVideoPath" AS "asset_encodedVideoPath", "asset"."createdAt" AS "asset_createdAt", @@ -141,8 +141,8 @@ SELECT "stackedAssets"."deviceId" AS "stackedAssets_deviceId", "stackedAssets"."type" AS "stackedAssets_type", "stackedAssets"."originalPath" AS "stackedAssets_originalPath", - "stackedAssets"."resizePath" AS "stackedAssets_resizePath", - "stackedAssets"."webpPath" AS "stackedAssets_webpPath", + "stackedAssets"."previewPath" AS "stackedAssets_previewPath", + "stackedAssets"."thumbnailPath" AS "stackedAssets_thumbnailPath", "stackedAssets"."thumbhash" AS "stackedAssets_thumbhash", "stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath", "stackedAssets"."createdAt" AS "stackedAssets_createdAt", @@ -320,8 +320,8 @@ SELECT "asset"."deviceId" AS "asset_deviceId", "asset"."type" AS "asset_type", "asset"."originalPath" AS "asset_originalPath", - "asset"."resizePath" AS "asset_resizePath", - "asset"."webpPath" AS "asset_webpPath", + "asset"."previewPath" AS "asset_previewPath", + "asset"."thumbnailPath" AS "asset_thumbnailPath", "asset"."thumbhash" AS "asset_thumbhash", "asset"."encodedVideoPath" AS "asset_encodedVideoPath", "asset"."createdAt" AS "asset_createdAt", diff --git a/server/src/queries/shared.link.repository.sql b/server/src/queries/shared.link.repository.sql index 27531cfc9..78581b8ba 100644 --- a/server/src/queries/shared.link.repository.sql +++ b/server/src/queries/shared.link.repository.sql @@ -28,8 +28,8 @@ FROM "SharedLinkEntity__SharedLinkEntity_assets"."deviceId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceId", "SharedLinkEntity__SharedLinkEntity_assets"."type" AS "SharedLinkEntity__SharedLinkEntity_assets_type", "SharedLinkEntity__SharedLinkEntity_assets"."originalPath" AS "SharedLinkEntity__SharedLinkEntity_assets_originalPath", - "SharedLinkEntity__SharedLinkEntity_assets"."resizePath" AS "SharedLinkEntity__SharedLinkEntity_assets_resizePath", - "SharedLinkEntity__SharedLinkEntity_assets"."webpPath" AS "SharedLinkEntity__SharedLinkEntity_assets_webpPath", + "SharedLinkEntity__SharedLinkEntity_assets"."previewPath" AS "SharedLinkEntity__SharedLinkEntity_assets_previewPath", + "SharedLinkEntity__SharedLinkEntity_assets"."thumbnailPath" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbnailPath", "SharedLinkEntity__SharedLinkEntity_assets"."thumbhash" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbhash", "SharedLinkEntity__SharedLinkEntity_assets"."encodedVideoPath" AS "SharedLinkEntity__SharedLinkEntity_assets_encodedVideoPath", "SharedLinkEntity__SharedLinkEntity_assets"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_assets_createdAt", @@ -95,8 +95,8 @@ FROM "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deviceId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_deviceId", "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."type" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_type", "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."originalPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_originalPath", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."resizePath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_resizePath", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."webpPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_webpPath", + "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."previewPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_previewPath", + "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."thumbnailPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_thumbnailPath", "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."thumbhash" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_thumbhash", "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."encodedVideoPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_encodedVideoPath", "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."createdAt" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_createdAt", @@ -218,8 +218,8 @@ SELECT "SharedLinkEntity__SharedLinkEntity_assets"."deviceId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceId", "SharedLinkEntity__SharedLinkEntity_assets"."type" AS "SharedLinkEntity__SharedLinkEntity_assets_type", "SharedLinkEntity__SharedLinkEntity_assets"."originalPath" AS "SharedLinkEntity__SharedLinkEntity_assets_originalPath", - "SharedLinkEntity__SharedLinkEntity_assets"."resizePath" AS "SharedLinkEntity__SharedLinkEntity_assets_resizePath", - "SharedLinkEntity__SharedLinkEntity_assets"."webpPath" AS "SharedLinkEntity__SharedLinkEntity_assets_webpPath", + "SharedLinkEntity__SharedLinkEntity_assets"."previewPath" AS "SharedLinkEntity__SharedLinkEntity_assets_previewPath", + "SharedLinkEntity__SharedLinkEntity_assets"."thumbnailPath" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbnailPath", "SharedLinkEntity__SharedLinkEntity_assets"."thumbhash" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbhash", "SharedLinkEntity__SharedLinkEntity_assets"."encodedVideoPath" AS "SharedLinkEntity__SharedLinkEntity_assets_encodedVideoPath", "SharedLinkEntity__SharedLinkEntity_assets"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_assets_createdAt", diff --git a/server/src/repositories/asset-v1.repository.ts b/server/src/repositories/asset-v1.repository.ts index 6f53d820c..229e700fd 100644 --- a/server/src/repositories/asset-v1.repository.ts +++ b/server/src/repositories/asset-v1.repository.ts @@ -66,7 +66,7 @@ export class AssetRepositoryV1 implements IAssetRepositoryV1 { getDetectedObjectsByUserId(userId: string): Promise { return this.assetRepository.query( ` - SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."resizePath", a."deviceAssetId", a."deviceId" + SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."previewPath" AS "resizePath", a."deviceAssetId", a."deviceId" FROM assets a LEFT JOIN smart_info si ON a.id = si."assetId" WHERE a."ownerId" = $1 @@ -80,7 +80,7 @@ export class AssetRepositoryV1 implements IAssetRepositoryV1 { getLocationsByUserId(userId: string): Promise { return this.assetRepository.query( ` - SELECT DISTINCT ON (e.city) a.id, e.city, a."resizePath", a."deviceAssetId", a."deviceId" + SELECT DISTINCT ON (e.city) a.id, e.city, a."previewPath" AS "resizePath", a."deviceAssetId", a."deviceId" FROM assets a LEFT JOIN exif e ON a.id = e."assetId" WHERE a."ownerId" = $1 diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index 907cddb89..e6389c2e5 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -83,7 +83,7 @@ export class AssetRepository implements IAssetRepository { `entity.ownerId IN (:...ownerIds) AND entity.isVisible = true AND entity.isArchived = false - AND entity.resizePath IS NOT NULL + AND entity.previewPath IS NOT NULL AND EXTRACT(DAY FROM entity.localDateTime AT TIME ZONE 'UTC') = :day AND EXTRACT(MONTH FROM entity.localDateTime AT TIME ZONE 'UTC') = :month`, { @@ -302,10 +302,10 @@ export class AssetRepository implements IAssetRepository { switch (property) { case WithoutProperty.THUMBNAIL: { where = [ - { resizePath: IsNull(), isVisible: true }, - { resizePath: '', isVisible: true }, - { webpPath: IsNull(), isVisible: true }, - { webpPath: '', isVisible: true }, + { previewPath: IsNull(), isVisible: true }, + { previewPath: '', isVisible: true }, + { thumbnailPath: IsNull(), isVisible: true }, + { thumbnailPath: '', isVisible: true }, { thumbhash: IsNull(), isVisible: true }, ]; break; @@ -339,7 +339,7 @@ export class AssetRepository implements IAssetRepository { }; where = { isVisible: true, - resizePath: Not(IsNull()), + previewPath: Not(IsNull()), smartSearch: { embedding: IsNull(), }, @@ -352,7 +352,7 @@ export class AssetRepository implements IAssetRepository { smartInfo: true, }; where = { - resizePath: Not(IsNull()), + previewPath: Not(IsNull()), isVisible: true, smartInfo: { tags: IsNull(), @@ -367,7 +367,7 @@ export class AssetRepository implements IAssetRepository { jobStatus: true, }; where = { - resizePath: Not(IsNull()), + previewPath: Not(IsNull()), isVisible: true, faces: { assetId: IsNull(), @@ -385,7 +385,7 @@ export class AssetRepository implements IAssetRepository { faces: true, }; where = { - resizePath: Not(IsNull()), + previewPath: Not(IsNull()), isVisible: true, faces: { assetId: Not(IsNull()), diff --git a/server/src/repositories/job.repository.ts b/server/src/repositories/job.repository.ts index b55996ed0..a7c99f93c 100644 --- a/server/src/repositories/job.repository.ts +++ b/server/src/repositories/job.repository.ts @@ -35,9 +35,9 @@ export const JOBS_TO_QUEUE: Record = { // thumbnails [JobName.QUEUE_GENERATE_THUMBNAILS]: QueueName.THUMBNAIL_GENERATION, - [JobName.GENERATE_JPEG_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, - [JobName.GENERATE_WEBP_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, - [JobName.GENERATE_THUMBHASH_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, + [JobName.GENERATE_PREVIEW]: QueueName.THUMBNAIL_GENERATION, + [JobName.GENERATE_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, + [JobName.GENERATE_THUMBHASH]: QueueName.THUMBNAIL_GENERATION, [JobName.GENERATE_PERSON_THUMBNAIL]: QueueName.THUMBNAIL_GENERATION, // metadata diff --git a/server/src/services/asset-v1.service.spec.ts b/server/src/services/asset-v1.service.spec.ts index 898fb5a99..735ac8322 100644 --- a/server/src/services/asset-v1.service.spec.ts +++ b/server/src/services/asset-v1.service.spec.ts @@ -44,13 +44,13 @@ const _getAsset_1 = () => { asset_1.deviceId = 'device_id_1'; asset_1.type = AssetType.VIDEO; asset_1.originalPath = 'fake_path/asset_1.jpeg'; - asset_1.resizePath = ''; + asset_1.previewPath = ''; asset_1.fileModifiedAt = new Date('2022-06-19T23:41:36.910Z'); asset_1.fileCreatedAt = new Date('2022-06-19T23:41:36.910Z'); asset_1.updatedAt = new Date('2022-06-19T23:41:36.910Z'); asset_1.isFavorite = false; asset_1.isArchived = false; - asset_1.webpPath = ''; + asset_1.thumbnailPath = ''; asset_1.encodedVideoPath = ''; asset_1.duration = '0:00:00.000000'; asset_1.exifInfo = new ExifEntity(); diff --git a/server/src/services/asset-v1.service.ts b/server/src/services/asset-v1.service.ts index a24ddbd69..97aa99d91 100644 --- a/server/src/services/asset-v1.service.ts +++ b/server/src/services/asset-v1.service.ts @@ -247,16 +247,16 @@ export class AssetServiceV1 { private getThumbnailPath(asset: AssetEntity, format: GetAssetThumbnailFormatEnum) { switch (format) { case GetAssetThumbnailFormatEnum.WEBP: { - if (asset.webpPath) { - return asset.webpPath; + if (asset.thumbnailPath) { + return asset.thumbnailPath; } this.logger.warn(`WebP thumbnail requested but not found for asset ${asset.id}, falling back to JPEG`); } case GetAssetThumbnailFormatEnum.JPEG: { - if (!asset.resizePath) { + if (!asset.previewPath) { throw new NotFoundException(`No thumbnail found for asset ${asset.id}`); } - return asset.resizePath; + return asset.previewPath; } } } @@ -268,12 +268,12 @@ export class AssetServiceV1 { * Serve file viewer on the web */ if (dto.isWeb && mimeType != 'image/gif') { - if (!asset.resizePath) { + if (!asset.previewPath) { this.logger.error('Error serving IMAGE asset for web'); throw new InternalServerErrorException(`Failed to serve image asset for web`, 'ServeFile'); } - return asset.resizePath; + return asset.previewPath; } /** @@ -283,15 +283,15 @@ export class AssetServiceV1 { return asset.originalPath; } - if (asset.webpPath && asset.webpPath.length > 0) { - return asset.webpPath; + if (asset.thumbnailPath && asset.thumbnailPath.length > 0) { + return asset.thumbnailPath; } - if (!asset.resizePath) { - throw new Error('resizePath not set'); + if (!asset.previewPath) { + throw new Error('previewPath not set'); } - return asset.resizePath; + return asset.previewPath; } private async getLibraryId(auth: AuthDto, libraryId?: string) { diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index 683e2f5ae..b90c194aa 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -661,8 +661,8 @@ describe(AssetService.name, () => { name: JobName.DELETE_FILES, data: { files: [ - assetWithFace.webpPath, - assetWithFace.resizePath, + assetWithFace.thumbnailPath, + assetWithFace.previewPath, assetWithFace.encodedVideoPath, assetWithFace.sidecarPath, assetWithFace.originalPath, @@ -745,8 +745,8 @@ describe(AssetService.name, () => { name: JobName.DELETE_FILES, data: { files: [ - assetStub.external.webpPath, - assetStub.external.resizePath, + assetStub.external.thumbnailPath, + assetStub.external.previewPath, assetStub.external.encodedVideoPath, assetStub.external.sidecarPath, ], @@ -828,9 +828,7 @@ describe(AssetService.name, () => { it('should run the refresh thumbnails job', async () => { accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REGENERATE_THUMBNAIL }), - expect(jobMock.queueAll).toHaveBeenCalledWith([ - { name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-1' } }, - ]); + expect(jobMock.queueAll).toHaveBeenCalledWith([{ name: JobName.GENERATE_PREVIEW, data: { id: 'asset-1' } }]); }); it('should run the transcode video', async () => { diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index 556883afd..135377e0b 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -399,7 +399,7 @@ export class AssetService { await this.jobRepository.queue({ name: JobName.ASSET_DELETION, data: { id: asset.livePhotoVideoId } }); } - const files = [asset.webpPath, asset.resizePath, asset.encodedVideoPath, asset.sidecarPath]; + const files = [asset.thumbnailPath, asset.previewPath, asset.encodedVideoPath, asset.sidecarPath]; if (!fromExternal) { files.push(asset.originalPath); } @@ -472,7 +472,7 @@ export class AssetService { } case AssetJobName.REGENERATE_THUMBNAIL: { - jobs.push({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id } }); + jobs.push({ name: JobName.GENERATE_PREVIEW, data: { id } }); break; } diff --git a/server/src/services/audit.service.ts b/server/src/services/audit.service.ts index ff5e0d9c7..d40167429 100644 --- a/server/src/services/audit.service.ts +++ b/server/src/services/audit.service.ts @@ -95,13 +95,13 @@ export class AuditService { break; } - case AssetPathType.JPEG_THUMBNAIL: { - await this.assetRepository.update({ id, resizePath: pathValue }); + case AssetPathType.PREVIEW: { + await this.assetRepository.update({ id, previewPath: pathValue }); break; } - case AssetPathType.WEBP_THUMBNAIL: { - await this.assetRepository.update({ id, webpPath: pathValue }); + case AssetPathType.THUMBNAIL: { + await this.assetRepository.update({ id, thumbnailPath: pathValue }); break; } @@ -174,8 +174,8 @@ export class AuditService { const orphans: FileReportItemDto[] = []; for await (const assets of pagination) { assetCount += assets.length; - for (const { id, originalPath, resizePath, encodedVideoPath, webpPath, isExternal, checksum } of assets) { - for (const file of [originalPath, resizePath, encodedVideoPath, webpPath]) { + for (const { id, originalPath, previewPath, encodedVideoPath, thumbnailPath, isExternal, checksum } of assets) { + for (const file of [originalPath, previewPath, encodedVideoPath, thumbnailPath]) { track(file); } @@ -191,14 +191,14 @@ export class AuditService { ) { orphans.push({ ...entity, pathType: AssetPathType.ORIGINAL, pathValue: originalPath }); } - if (resizePath && !hasFile(thumbFiles, resizePath)) { - orphans.push({ ...entity, pathType: AssetPathType.JPEG_THUMBNAIL, pathValue: resizePath }); + if (previewPath && !hasFile(thumbFiles, previewPath)) { + orphans.push({ ...entity, pathType: AssetPathType.PREVIEW, pathValue: previewPath }); } - if (webpPath && !hasFile(thumbFiles, webpPath)) { - orphans.push({ ...entity, pathType: AssetPathType.WEBP_THUMBNAIL, pathValue: webpPath }); + if (thumbnailPath && !hasFile(thumbFiles, thumbnailPath)) { + orphans.push({ ...entity, pathType: AssetPathType.THUMBNAIL, pathValue: thumbnailPath }); } if (encodedVideoPath && !hasFile(videoFiles, encodedVideoPath)) { - orphans.push({ ...entity, pathType: AssetPathType.WEBP_THUMBNAIL, pathValue: encodedVideoPath }); + orphans.push({ ...entity, pathType: AssetPathType.THUMBNAIL, pathValue: encodedVideoPath }); } } } diff --git a/server/src/services/job.service.spec.ts b/server/src/services/job.service.spec.ts index aa5739878..ac0e502ae 100644 --- a/server/src/services/job.service.spec.ts +++ b/server/src/services/job.service.spec.ts @@ -279,7 +279,7 @@ describe(JobService.name, () => { }, { item: { name: JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE, data: { id: 'asset-1', source: 'upload' } }, - jobs: [JobName.GENERATE_JPEG_THUMBNAIL], + jobs: [JobName.GENERATE_PREVIEW], }, { item: { name: JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE, data: { id: 'asset-1' } }, @@ -290,24 +290,24 @@ describe(JobService.name, () => { jobs: [], }, { - item: { name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-1' } }, - jobs: [JobName.GENERATE_WEBP_THUMBNAIL, JobName.GENERATE_THUMBHASH_THUMBNAIL], + item: { name: JobName.GENERATE_PREVIEW, data: { id: 'asset-1' } }, + jobs: [JobName.GENERATE_THUMBNAIL, JobName.GENERATE_THUMBHASH], }, { - item: { name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-1', source: 'upload' } }, + item: { name: JobName.GENERATE_PREVIEW, data: { id: 'asset-1', source: 'upload' } }, jobs: [ - JobName.GENERATE_WEBP_THUMBNAIL, - JobName.GENERATE_THUMBHASH_THUMBNAIL, + JobName.GENERATE_THUMBNAIL, + JobName.GENERATE_THUMBHASH, JobName.SMART_SEARCH, JobName.FACE_DETECTION, JobName.VIDEO_CONVERSION, ], }, { - item: { name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-live-image', source: 'upload' } }, + item: { name: JobName.GENERATE_PREVIEW, data: { id: 'asset-live-image', source: 'upload' } }, jobs: [ - JobName.GENERATE_WEBP_THUMBNAIL, - JobName.GENERATE_THUMBHASH_THUMBNAIL, + JobName.GENERATE_THUMBNAIL, + JobName.GENERATE_THUMBHASH, JobName.SMART_SEARCH, JobName.FACE_DETECTION, JobName.VIDEO_CONVERSION, @@ -329,7 +329,7 @@ describe(JobService.name, () => { for (const { item, jobs } of tests) { it(`should queue ${jobs.length} jobs when a ${item.name} job finishes successfully`, async () => { - if (item.name === JobName.GENERATE_JPEG_THUMBNAIL && item.data.source === 'upload') { + if (item.name === JobName.GENERATE_PREVIEW && item.data.source === 'upload') { if (item.data.id === 'asset-live-image') { assetMock.getByIds.mockResolvedValue([assetStub.livePhotoStillAsset]); } else { diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index c03b7c7bc..3f9cd8a22 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -245,7 +245,7 @@ export class JobService { case JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE: { if (item.data.source === 'upload') { - await this.jobRepository.queue({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: item.data }); + await this.jobRepository.queue({ name: JobName.GENERATE_PREVIEW, data: item.data }); } break; } @@ -259,10 +259,10 @@ export class JobService { break; } - case JobName.GENERATE_JPEG_THUMBNAIL: { + case JobName.GENERATE_PREVIEW: { const jobs: JobItem[] = [ - { name: JobName.GENERATE_WEBP_THUMBNAIL, data: item.data }, - { name: JobName.GENERATE_THUMBHASH_THUMBNAIL, data: item.data }, + { name: JobName.GENERATE_THUMBNAIL, data: item.data }, + { name: JobName.GENERATE_THUMBHASH, data: item.data }, ]; if (item.data.source === 'upload') { @@ -282,7 +282,7 @@ export class JobService { break; } - case JobName.GENERATE_WEBP_THUMBNAIL: { + case JobName.GENERATE_THUMBNAIL: { if (item.data.source !== 'upload') { break; } diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index 4397730ab..3a650430e 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -4,6 +4,7 @@ import { ExifEntity } from 'src/entities/exif.entity'; import { AudioCodec, Colorspace, + ImageFormat, SystemConfigKey, ToneMapping, TranscodeHWAccel, @@ -78,7 +79,7 @@ describe(MediaService.name, () => { expect(assetMock.getWithout).not.toHaveBeenCalled(); expect(jobMock.queueAll).toHaveBeenCalledWith([ { - name: JobName.GENERATE_JPEG_THUMBNAIL, + name: JobName.GENERATE_PREVIEW, data: { id: assetStub.image.id }, }, ]); @@ -136,7 +137,7 @@ describe(MediaService.name, () => { expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL); expect(jobMock.queueAll).toHaveBeenCalledWith([ { - name: JobName.GENERATE_JPEG_THUMBNAIL, + name: JobName.GENERATE_PREVIEW, data: { id: assetStub.image.id }, }, ]); @@ -160,7 +161,7 @@ describe(MediaService.name, () => { expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL); expect(jobMock.queueAll).toHaveBeenCalledWith([ { - name: JobName.GENERATE_WEBP_THUMBNAIL, + name: JobName.GENERATE_THUMBNAIL, data: { id: assetStub.image.id }, }, ]); @@ -184,7 +185,7 @@ describe(MediaService.name, () => { expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL); expect(jobMock.queueAll).toHaveBeenCalledWith([ { - name: JobName.GENERATE_THUMBHASH_THUMBNAIL, + name: JobName.GENERATE_THUMBHASH, data: { id: assetStub.image.id }, }, ]); @@ -193,10 +194,10 @@ describe(MediaService.name, () => { }); }); - describe('handleGenerateJpegThumbnail', () => { + describe('handleGeneratePreview', () => { it('should skip thumbnail generation if asset not found', async () => { assetMock.getByIds.mockResolvedValue([]); - await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id }); + await sut.handleGeneratePreview({ id: assetStub.image.id }); expect(mediaMock.resize).not.toHaveBeenCalled(); expect(assetMock.update).not.toHaveBeenCalledWith(); }); @@ -204,25 +205,29 @@ describe(MediaService.name, () => { it('should skip video thumbnail generation if no video stream', async () => { mediaMock.probe.mockResolvedValue(probeStub.noVideoStreams); assetMock.getByIds.mockResolvedValue([assetStub.video]); - await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id }); + await sut.handleGeneratePreview({ id: assetStub.image.id }); expect(mediaMock.resize).not.toHaveBeenCalled(); expect(assetMock.update).not.toHaveBeenCalledWith(); }); it('should generate a thumbnail for an image', async () => { assetMock.getByIds.mockResolvedValue([assetStub.image]); - await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id }); + await sut.handleGeneratePreview({ id: assetStub.image.id }); expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id/as/se'); - expect(mediaMock.resize).toHaveBeenCalledWith('/original/path.jpg', 'upload/thumbs/user-id/as/se/asset-id.jpeg', { - size: 1440, - format: 'jpeg', - quality: 80, - colorspace: Colorspace.SRGB, - }); + expect(mediaMock.resize).toHaveBeenCalledWith( + '/original/path.jpg', + 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg', + { + size: 1440, + format: ImageFormat.JPEG, + quality: 80, + colorspace: Colorspace.SRGB, + }, + ); expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-id', - resizePath: 'upload/thumbs/user-id/as/se/asset-id.jpeg', + previewPath: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg', }); }); @@ -230,30 +235,34 @@ describe(MediaService.name, () => { assetMock.getByIds.mockResolvedValue([ { ...assetStub.image, exifInfo: { profileDescription: 'Adobe RGB', bitsPerSample: 14 } as ExifEntity }, ]); - await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id }); + await sut.handleGeneratePreview({ id: assetStub.image.id }); expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id/as/se'); - expect(mediaMock.resize).toHaveBeenCalledWith('/original/path.jpg', 'upload/thumbs/user-id/as/se/asset-id.jpeg', { - size: 1440, - format: 'jpeg', - quality: 80, - colorspace: Colorspace.P3, - }); + expect(mediaMock.resize).toHaveBeenCalledWith( + '/original/path.jpg', + 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg', + { + size: 1440, + format: ImageFormat.JPEG, + quality: 80, + colorspace: Colorspace.P3, + }, + ); expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-id', - resizePath: 'upload/thumbs/user-id/as/se/asset-id.jpeg', + previewPath: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg', }); }); it('should generate a thumbnail for a video', async () => { mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p); assetMock.getByIds.mockResolvedValue([assetStub.video]); - await sut.handleGenerateJpegThumbnail({ id: assetStub.video.id }); + await sut.handleGeneratePreview({ id: assetStub.video.id }); expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id/as/se'); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', - 'upload/thumbs/user-id/as/se/asset-id.jpeg', + 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg', { inputOptions: ['-ss 00:00:00', '-sws_flags accurate_rnd+bitexact+full_chroma_int'], outputOptions: [ @@ -266,19 +275,19 @@ describe(MediaService.name, () => { ); expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-id', - resizePath: 'upload/thumbs/user-id/as/se/asset-id.jpeg', + previewPath: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg', }); }); it('should tonemap thumbnail for hdr video', async () => { mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR); assetMock.getByIds.mockResolvedValue([assetStub.video]); - await sut.handleGenerateJpegThumbnail({ id: assetStub.video.id }); + await sut.handleGeneratePreview({ id: assetStub.video.id }); expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id/as/se'); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', - 'upload/thumbs/user-id/as/se/asset-id.jpeg', + 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg', { inputOptions: ['-ss 00:00:00', '-sws_flags accurate_rnd+bitexact+full_chroma_int'], outputOptions: [ @@ -291,7 +300,7 @@ describe(MediaService.name, () => { ); expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-id', - resizePath: 'upload/thumbs/user-id/as/se/asset-id.jpeg', + previewPath: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg', }); }); @@ -302,11 +311,11 @@ describe(MediaService.name, () => { { key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '5000k' }, ]); assetMock.getByIds.mockResolvedValue([assetStub.video]); - await sut.handleGenerateJpegThumbnail({ id: assetStub.video.id }); + await sut.handleGeneratePreview({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', - 'upload/thumbs/user-id/as/se/asset-id.jpeg', + 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg', { inputOptions: ['-ss 00:00:00', '-sws_flags accurate_rnd+bitexact+full_chroma_int'], outputOptions: [ @@ -321,31 +330,35 @@ describe(MediaService.name, () => { it('should run successfully', async () => { assetMock.getByIds.mockResolvedValue([assetStub.image]); - await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id }); + await sut.handleGeneratePreview({ id: assetStub.image.id }); }); }); - describe('handleGenerateWebpThumbnail', () => { + describe('handleGenerateThumbnail', () => { it('should skip thumbnail generation if asset not found', async () => { assetMock.getByIds.mockResolvedValue([]); - await sut.handleGenerateWebpThumbnail({ id: assetStub.image.id }); + await sut.handleGenerateThumbnail({ id: assetStub.image.id }); expect(mediaMock.resize).not.toHaveBeenCalled(); expect(assetMock.update).not.toHaveBeenCalledWith(); }); it('should generate a thumbnail', async () => { assetMock.getByIds.mockResolvedValue([assetStub.image]); - await sut.handleGenerateWebpThumbnail({ id: assetStub.image.id }); + await sut.handleGenerateThumbnail({ id: assetStub.image.id }); - expect(mediaMock.resize).toHaveBeenCalledWith('/original/path.jpg', 'upload/thumbs/user-id/as/se/asset-id.webp', { - format: 'webp', - size: 250, - quality: 80, - colorspace: Colorspace.SRGB, - }); + expect(mediaMock.resize).toHaveBeenCalledWith( + '/original/path.jpg', + 'upload/thumbs/user-id/as/se/asset-id-thumbnail.webp', + { + format: ImageFormat.WEBP, + size: 250, + quality: 80, + colorspace: Colorspace.SRGB, + }, + ); expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-id', - webpPath: 'upload/thumbs/user-id/as/se/asset-id.webp', + thumbnailPath: 'upload/thumbs/user-id/as/se/asset-id-thumbnail.webp', }); }); }); @@ -354,31 +367,35 @@ describe(MediaService.name, () => { assetMock.getByIds.mockResolvedValue([ { ...assetStub.image, exifInfo: { profileDescription: 'Adobe RGB', bitsPerSample: 14 } as ExifEntity }, ]); - await sut.handleGenerateWebpThumbnail({ id: assetStub.image.id }); + await sut.handleGenerateThumbnail({ id: assetStub.image.id }); expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id/as/se'); - expect(mediaMock.resize).toHaveBeenCalledWith('/original/path.jpg', 'upload/thumbs/user-id/as/se/asset-id.webp', { - format: 'webp', - size: 250, - quality: 80, - colorspace: Colorspace.P3, - }); + expect(mediaMock.resize).toHaveBeenCalledWith( + '/original/path.jpg', + 'upload/thumbs/user-id/as/se/asset-id-thumbnail.webp', + { + format: ImageFormat.WEBP, + size: 250, + quality: 80, + colorspace: Colorspace.P3, + }, + ); expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-id', - webpPath: 'upload/thumbs/user-id/as/se/asset-id.webp', + thumbnailPath: 'upload/thumbs/user-id/as/se/asset-id-thumbnail.webp', }); }); describe('handleGenerateThumbhashThumbnail', () => { it('should skip thumbhash generation if asset not found', async () => { assetMock.getByIds.mockResolvedValue([]); - await sut.handleGenerateThumbhashThumbnail({ id: assetStub.image.id }); + await sut.handleGenerateThumbhash({ id: assetStub.image.id }); expect(mediaMock.generateThumbhash).not.toHaveBeenCalled(); }); it('should skip thumbhash generation if resize path is missing', async () => { assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]); - await sut.handleGenerateThumbhashThumbnail({ id: assetStub.noResizePath.id }); + await sut.handleGenerateThumbhash({ id: assetStub.noResizePath.id }); expect(mediaMock.generateThumbhash).not.toHaveBeenCalled(); }); @@ -387,7 +404,7 @@ describe(MediaService.name, () => { assetMock.getByIds.mockResolvedValue([assetStub.image]); mediaMock.generateThumbhash.mockResolvedValue(thumbhashBuffer); - await sut.handleGenerateThumbhashThumbnail({ id: assetStub.image.id }); + await sut.handleGenerateThumbhash({ id: assetStub.image.id }); expect(mediaMock.generateThumbhash).toHaveBeenCalledWith('/uploads/user-id/thumbs/path.jpg'); expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-id', thumbhash: thumbhashBuffer }); diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts index 26aa2dce9..c56fd26e6 100644 --- a/server/src/services/media.service.ts +++ b/server/src/services/media.service.ts @@ -1,5 +1,5 @@ import { Inject, Injectable, UnsupportedMediaTypeException } from '@nestjs/common'; -import { StorageCore, StorageFolder } from 'src/cores/storage.core'; +import { GeneratedImageType, StorageCore, StorageFolder } from 'src/cores/storage.core'; import { SystemConfigCore } from 'src/cores/system-config.core'; import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto'; import { AssetEntity, AssetType } from 'src/entities/asset.entity'; @@ -7,6 +7,7 @@ import { AssetPathType } from 'src/entities/move.entity'; import { AudioCodec, Colorspace, + ImageFormat, TranscodeHWAccel, TranscodePolicy, TranscodeTarget, @@ -81,15 +82,15 @@ export class MediaService { const jobs: JobItem[] = []; for (const asset of assets) { - if (!asset.resizePath || force) { - jobs.push({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: asset.id } }); + if (!asset.previewPath || force) { + jobs.push({ name: JobName.GENERATE_PREVIEW, data: { id: asset.id } }); continue; } - if (!asset.webpPath) { - jobs.push({ name: JobName.GENERATE_WEBP_THUMBNAIL, data: { id: asset.id } }); + if (!asset.thumbnailPath) { + jobs.push({ name: JobName.GENERATE_THUMBNAIL, data: { id: asset.id } }); } if (!asset.thumbhash) { - jobs.push({ name: JobName.GENERATE_THUMBHASH_THUMBNAIL, data: { id: asset.id } }); + jobs.push({ name: JobName.GENERATE_THUMBHASH, data: { id: asset.id } }); } } @@ -152,41 +153,41 @@ export class MediaService { } async handleAssetMigration({ id }: IEntityJob): Promise { + const { image } = await this.configCore.getConfig(); const [asset] = await this.assetRepository.getByIds([id]); if (!asset) { return JobStatus.FAILED; } - await this.storageCore.moveAssetFile(asset, AssetPathType.JPEG_THUMBNAIL); - await this.storageCore.moveAssetFile(asset, AssetPathType.WEBP_THUMBNAIL); - await this.storageCore.moveAssetFile(asset, AssetPathType.ENCODED_VIDEO); + await this.storageCore.moveAssetImage(asset, AssetPathType.PREVIEW, image.previewFormat); + await this.storageCore.moveAssetImage(asset, AssetPathType.THUMBNAIL, image.thumbnailFormat); + await this.storageCore.moveAssetVideo(asset); return JobStatus.SUCCESS; } - async handleGenerateJpegThumbnail({ id }: IEntityJob): Promise { + async handleGeneratePreview({ id }: IEntityJob): Promise { const [asset] = await this.assetRepository.getByIds([id], { exifInfo: true }); if (!asset) { return JobStatus.FAILED; } - const resizePath = await this.generateThumbnail(asset, 'jpeg'); - await this.assetRepository.update({ id: asset.id, resizePath }); + const previewPath = await this.generateThumbnail(asset, AssetPathType.PREVIEW, ImageFormat.JPEG); + await this.assetRepository.update({ id: asset.id, previewPath }); return JobStatus.SUCCESS; } - private async generateThumbnail(asset: AssetEntity, format: 'jpeg' | 'webp') { - const { thumbnail, ffmpeg } = await this.configCore.getConfig(); - const size = format === 'jpeg' ? thumbnail.jpegSize : thumbnail.webpSize; - const path = - format === 'jpeg' ? StorageCore.getLargeThumbnailPath(asset) : StorageCore.getSmallThumbnailPath(asset); + private async generateThumbnail(asset: AssetEntity, type: GeneratedImageType, format: ImageFormat) { + const { image, ffmpeg } = await this.configCore.getConfig(); + const size = type === AssetPathType.PREVIEW ? image.previewSize : image.thumbnailSize; + const path = StorageCore.getImagePath(asset, type, format); this.storageCore.ensureFolders(path); switch (asset.type) { case AssetType.IMAGE: { - const colorspace = this.isSRGB(asset) ? Colorspace.SRGB : thumbnail.colorspace; - const thumbnailOptions = { format, size, colorspace, quality: thumbnail.quality }; - await this.mediaRepository.resize(asset.originalPath, path, thumbnailOptions); + const colorspace = this.isSRGB(asset) ? Colorspace.SRGB : image.colorspace; + const imageOptions = { format, size, colorspace, quality: image.quality }; + await this.mediaRepository.resize(asset.originalPath, path, imageOptions); break; } @@ -214,24 +215,24 @@ export class MediaService { return path; } - async handleGenerateWebpThumbnail({ id }: IEntityJob): Promise { + async handleGenerateThumbnail({ id }: IEntityJob): Promise { const [asset] = await this.assetRepository.getByIds([id], { exifInfo: true }); if (!asset) { return JobStatus.FAILED; } - const webpPath = await this.generateThumbnail(asset, 'webp'); - await this.assetRepository.update({ id: asset.id, webpPath }); + const thumbnailPath = await this.generateThumbnail(asset, AssetPathType.THUMBNAIL, ImageFormat.WEBP); + await this.assetRepository.update({ id: asset.id, thumbnailPath }); return JobStatus.SUCCESS; } - async handleGenerateThumbhashThumbnail({ id }: IEntityJob): Promise { + async handleGenerateThumbhash({ id }: IEntityJob): Promise { const [asset] = await this.assetRepository.getByIds([id]); - if (!asset?.resizePath) { + if (!asset?.previewPath) { return JobStatus.FAILED; } - const thumbhash = await this.mediaRepository.generateThumbhash(asset.resizePath); + const thumbhash = await this.mediaRepository.generateThumbhash(asset.previewPath); await this.assetRepository.update({ id: asset.id, thumbhash }); return JobStatus.SUCCESS; diff --git a/server/src/services/microservices.service.ts b/server/src/services/microservices.service.ts index d5cae818e..7bea8c377 100644 --- a/server/src/services/microservices.service.ts +++ b/server/src/services/microservices.service.ts @@ -53,9 +53,9 @@ export class MicroservicesService { [JobName.MIGRATE_ASSET]: (data) => this.mediaService.handleAssetMigration(data), [JobName.MIGRATE_PERSON]: (data) => this.personService.handlePersonMigration(data), [JobName.QUEUE_GENERATE_THUMBNAILS]: (data) => this.mediaService.handleQueueGenerateThumbnails(data), - [JobName.GENERATE_JPEG_THUMBNAIL]: (data) => this.mediaService.handleGenerateJpegThumbnail(data), - [JobName.GENERATE_WEBP_THUMBNAIL]: (data) => this.mediaService.handleGenerateWebpThumbnail(data), - [JobName.GENERATE_THUMBHASH_THUMBNAIL]: (data) => this.mediaService.handleGenerateThumbhashThumbnail(data), + [JobName.GENERATE_PREVIEW]: (data) => this.mediaService.handleGeneratePreview(data), + [JobName.GENERATE_THUMBNAIL]: (data) => this.mediaService.handleGenerateThumbnail(data), + [JobName.GENERATE_THUMBHASH]: (data) => this.mediaService.handleGenerateThumbhash(data), [JobName.QUEUE_VIDEO_CONVERSION]: (data) => this.mediaService.handleQueueVideoConversion(data), [JobName.VIDEO_CONVERSION]: (data) => this.mediaService.handleVideoConversion(data), [JobName.QUEUE_METADATA_EXTRACTION]: (data) => this.metadataService.handleQueueMetadataExtraction(data), diff --git a/server/src/services/person.service.spec.ts b/server/src/services/person.service.spec.ts index 10e42e1b6..501154c1d 100644 --- a/server/src/services/person.service.spec.ts +++ b/server/src/services/person.service.spec.ts @@ -645,7 +645,7 @@ describe(PersonService.name, () => { expect(machineLearningMock.detectFaces).toHaveBeenCalledWith( 'http://immich-machine-learning:3003', { - imagePath: assetStub.image.resizePath, + imagePath: assetStub.image.previewPath, }, { enabled: true, diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index 504716a55..d2bc81b0e 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -23,6 +23,7 @@ import { } from 'src/dtos/person.dto'; import { PersonPathType } from 'src/entities/move.entity'; import { PersonEntity } from 'src/entities/person.entity'; +import { ImageFormat } from 'src/entities/system-config.entity'; import { IAccessRepository } from 'src/interfaces/access.interface'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; @@ -315,17 +316,17 @@ export class PersonService { }, }; const [asset] = await this.assetRepository.getByIds([id], relations); - if (!asset || !asset.resizePath || asset.faces?.length > 0) { + if (!asset || !asset.previewPath || asset.faces?.length > 0) { return JobStatus.FAILED; } const faces = await this.machineLearningRepository.detectFaces( machineLearning.url, - { imagePath: asset.resizePath }, + { imagePath: asset.previewPath }, machineLearning.facialRecognition, ); - this.logger.debug(`${faces.length} faces detected in ${asset.resizePath}`); + this.logger.debug(`${faces.length} faces detected in ${asset.previewPath}`); this.logger.verbose(faces.map((face) => ({ ...face, embedding: `vector(${face.embedding.length})` }))); if (faces.length > 0) { @@ -470,7 +471,7 @@ export class PersonService { } async handleGeneratePersonThumbnail(data: IEntityJob): Promise { - const { machineLearning, thumbnail } = await this.configCore.getConfig(); + const { machineLearning, image } = await this.configCore.getConfig(); if (!machineLearning.enabled || !machineLearning.facialRecognition.enabled) { return JobStatus.SKIPPED; } @@ -496,7 +497,7 @@ export class PersonService { } = face; const [asset] = await this.assetRepository.getByIds([assetId]); - if (!asset?.resizePath) { + if (!asset?.previewPath) { return JobStatus.FAILED; } this.logger.verbose(`Cropping face for person: ${person.id}`); @@ -527,12 +528,12 @@ export class PersonService { height: newHalfSize * 2, }; - const croppedOutput = await this.mediaRepository.crop(asset.resizePath, cropOptions); + const croppedOutput = await this.mediaRepository.crop(asset.previewPath, cropOptions); const thumbnailOptions = { - format: 'jpeg', + format: ImageFormat.JPEG, size: FACE_THUMBNAIL_SIZE, - colorspace: thumbnail.colorspace, - quality: thumbnail.quality, + colorspace: image.colorspace, + quality: image.quality, } as const; await this.mediaRepository.resize(croppedOutput, thumbnailPath, thumbnailOptions); diff --git a/server/src/services/search.service.ts b/server/src/services/search.service.ts index 03fa154a3..9422dac86 100644 --- a/server/src/services/search.service.ts +++ b/server/src/services/search.service.ts @@ -76,6 +76,9 @@ export class SearchService { checksum = Buffer.from(dto.checksum, encoding); } + dto.previewPath ??= dto.resizePath; + dto.thumbnailPath ??= dto.webpPath; + const page = dto.page ?? 1; const size = dto.size || 250; const enumToOrder = { [AssetOrder.ASC]: 'ASC', [AssetOrder.DESC]: 'DESC' } as const; diff --git a/server/src/services/smart-info.service.spec.ts b/server/src/services/smart-info.service.spec.ts index 968aeac5f..2e1dfbafc 100644 --- a/server/src/services/smart-info.service.spec.ts +++ b/server/src/services/smart-info.service.spec.ts @@ -18,7 +18,7 @@ import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.r const asset = { id: 'asset-1', - resizePath: 'path/to/resize.ext', + previewPath: 'path/to/resize.ext', } as AssetEntity; describe(SmartInfoService.name, () => { @@ -94,7 +94,7 @@ describe(SmartInfoService.name, () => { }); it('should skip assets without a resize path', async () => { - const asset = { resizePath: '' } as AssetEntity; + const asset = { previewPath: '' } as AssetEntity; assetMock.getByIds.mockResolvedValue([asset]); await sut.handleEncodeClip({ id: asset.id }); diff --git a/server/src/services/smart-info.service.ts b/server/src/services/smart-info.service.ts index 183c45b80..f9d36c238 100644 --- a/server/src/services/smart-info.service.ts +++ b/server/src/services/smart-info.service.ts @@ -83,13 +83,13 @@ export class SmartInfoService { return JobStatus.FAILED; } - if (!asset.resizePath) { + if (!asset.previewPath) { return JobStatus.FAILED; } const clipEmbedding = await this.machineLearning.encodeImage( machineLearning.url, - { imagePath: asset.resizePath }, + { imagePath: asset.previewPath }, machineLearning.clip, ); diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts index b2079f606..4bb5dd0a1 100644 --- a/server/src/services/system-config.service.spec.ts +++ b/server/src/services/system-config.service.spec.ts @@ -4,6 +4,7 @@ import { AudioCodec, CQMode, Colorspace, + ImageFormat, LogLevel, SystemConfig, SystemConfigEntity, @@ -119,9 +120,11 @@ const updatedConfig = Object.freeze({ hashVerificationEnabled: true, template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', }, - thumbnail: { - webpSize: 250, - jpegSize: 1440, + image: { + thumbnailFormat: ImageFormat.WEBP, + thumbnailSize: 250, + previewFormat: ImageFormat.JPEG, + previewSize: 1440, quality: 80, colorspace: Colorspace.P3, }, diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts index 7ec677756..be0eb8fa6 100644 --- a/server/src/utils/database.ts +++ b/server/src/utils/database.ts @@ -58,7 +58,7 @@ export function searchAssetBuilder( builder.andWhere(`${builder.alias}.ownerId IN (:...userIds)`, { userIds: options.userIds }); } - const path = _.pick(options, ['encodedVideoPath', 'originalPath', 'resizePath', 'webpPath']); + const path = _.pick(options, ['encodedVideoPath', 'originalPath', 'previewPath', 'thumbnailPath']); builder.andWhere(_.omitBy(path, _.isUndefined)); if (options.originalFileName) { diff --git a/server/test/fixtures/asset.stub.ts b/server/test/fixtures/asset.stub.ts index fc94a363a..0b2ff82a3 100644 --- a/server/test/fixtures/asset.stub.ts +++ b/server/test/fixtures/asset.stub.ts @@ -26,10 +26,10 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: 'upload/library/IMG_123.jpg', - resizePath: null, + previewPath: null, checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: '/uploads/user-id/webp/path.ext', + thumbnailPath: '/uploads/user-id/webp/path.ext', thumbhash: Buffer.from('blablabla', 'base64'), encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), @@ -62,10 +62,10 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: 'upload/library/IMG_456.jpg', - resizePath: '/uploads/user-id/thumbs/path.ext', + previewPath: '/uploads/user-id/thumbs/path.ext', checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: null, + thumbnailPath: null, thumbhash: Buffer.from('blablabla', 'base64'), encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), @@ -102,10 +102,10 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', + previewPath: '/uploads/user-id/thumbs/path.ext', checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: '/uploads/user-id/webp/path.ext', + thumbnailPath: '/uploads/user-id/webp/path.ext', thumbhash: null, encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), @@ -139,10 +139,10 @@ export const assetStub = { ownerId: 'admin-id', deviceId: 'device-id', originalPath: '/original/path.jpg', - resizePath: '/uploads/admin-id/thumbs/path.jpg', + previewPath: '/uploads/admin-id/thumbs/path.jpg', checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: '/uploads/admin-id/webp/path.ext', + thumbnailPath: '/uploads/admin-id/webp/path.ext', thumbhash: Buffer.from('blablabla', 'base64'), encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), @@ -184,10 +184,10 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: '/original/path.jpg', - resizePath: '/uploads/user-id/thumbs/path.jpg', + previewPath: '/uploads/user-id/thumbs/path.jpg', checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: '/uploads/user-id/webp/path.ext', + thumbnailPath: '/uploads/user-id/webp/path.ext', thumbhash: Buffer.from('blablabla', 'base64'), encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), @@ -224,10 +224,10 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: '/data/user1/photo.jpg', - resizePath: '/uploads/user-id/thumbs/path.jpg', + previewPath: '/uploads/user-id/thumbs/path.jpg', checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: '/uploads/user-id/webp/path.ext', + thumbnailPath: '/uploads/user-id/webp/path.ext', thumbhash: Buffer.from('blablabla', 'base64'), encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), @@ -264,10 +264,10 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: '/original/path.jpg', - resizePath: '/uploads/user-id/thumbs/path.jpg', + previewPath: '/uploads/user-id/thumbs/path.jpg', checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: '/uploads/user-id/webp/path.ext', + thumbnailPath: '/uploads/user-id/webp/path.ext', thumbhash: Buffer.from('blablabla', 'base64'), encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), @@ -304,10 +304,10 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', + previewPath: '/uploads/user-id/thumbs/path.ext', checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: '/uploads/user-id/webp/path.ext', + thumbnailPath: '/uploads/user-id/webp/path.ext', thumbhash: Buffer.from('blablabla', 'base64'), encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), @@ -344,10 +344,10 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', + previewPath: '/uploads/user-id/thumbs/path.ext', checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: '/uploads/user-id/webp/path.ext', + thumbnailPath: '/uploads/user-id/webp/path.ext', thumbhash: Buffer.from('blablabla', 'base64'), encodedVideoPath: null, createdAt: new Date('2015-02-23T05:06:29.716Z'), @@ -385,10 +385,10 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', + previewPath: '/uploads/user-id/thumbs/path.ext', checksum: Buffer.from('file hash', 'utf8'), type: AssetType.VIDEO, - webpPath: null, + thumbnailPath: null, thumbhash: null, encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), @@ -456,10 +456,10 @@ export const assetStub = { deviceId: 'device-id', checksum: Buffer.from('file hash', 'utf8'), originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', + previewPath: '/uploads/user-id/thumbs/path.ext', sidecarPath: null, type: AssetType.IMAGE, - webpPath: null, + thumbnailPath: null, thumbhash: null, encodedVideoPath: null, createdAt: new Date('2023-02-22T05:06:29.716Z'), @@ -499,11 +499,11 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', + previewPath: '/uploads/user-id/thumbs/path.ext', thumbhash: null, checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: null, + thumbnailPath: null, encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), @@ -535,11 +535,11 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', + previewPath: '/uploads/user-id/thumbs/path.ext', thumbhash: null, checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: null, + thumbnailPath: null, encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), @@ -572,11 +572,11 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', + previewPath: '/uploads/user-id/thumbs/path.ext', thumbhash: null, checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: null, + thumbnailPath: null, encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), @@ -610,10 +610,10 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', + previewPath: '/uploads/user-id/thumbs/path.ext', checksum: Buffer.from('file hash', 'utf8'), type: AssetType.VIDEO, - webpPath: null, + thumbnailPath: null, thumbhash: null, encodedVideoPath: '/encoded/video/path.mp4', createdAt: new Date('2023-02-23T05:06:29.716Z'), @@ -648,10 +648,10 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: '/data/user1/photo.jpg', - resizePath: '/uploads/user-id/thumbs/path.jpg', + previewPath: '/uploads/user-id/thumbs/path.jpg', checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: '/uploads/user-id/webp/path.ext', + thumbnailPath: '/uploads/user-id/webp/path.ext', thumbhash: Buffer.from('blablabla', 'base64'), encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), @@ -687,10 +687,10 @@ export const assetStub = { ownerId: 'user-id', deviceId: 'device-id', originalPath: '/data/user1/photo.jpg', - resizePath: '/uploads/user-id/thumbs/path.jpg', + previewPath: '/uploads/user-id/thumbs/path.jpg', checksum: Buffer.from('file hash', 'utf8'), type: AssetType.IMAGE, - webpPath: '/uploads/user-id/webp/path.ext', + thumbnailPath: '/uploads/user-id/webp/path.ext', thumbhash: Buffer.from('blablabla', 'base64'), encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), diff --git a/server/test/fixtures/shared-link.stub.ts b/server/test/fixtures/shared-link.stub.ts index 34e3da515..ccd76c328 100644 --- a/server/test/fixtures/shared-link.stub.ts +++ b/server/test/fixtures/shared-link.stub.ts @@ -199,7 +199,7 @@ export const sharedLinkStub = { deviceId: 'device_id_1', type: AssetType.VIDEO, originalPath: 'fake_path/jpeg', - resizePath: '', + previewPath: '', checksum: Buffer.from('file hash', 'utf8'), fileModifiedAt: today, fileCreatedAt: today, @@ -219,7 +219,7 @@ export const sharedLinkStub = { objects: ['a', 'b', 'c'], asset: null as any, }, - webpPath: '', + thumbnailPath: '', thumbhash: null, encodedVideoPath: '', duration: null, diff --git a/web/src/lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte b/web/src/lib/components/admin-page/settings/image/image-settings.svelte similarity index 75% rename from web/src/lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte rename to web/src/lib/components/admin-page/settings/image/image-settings.svelte index 8e2936b55..dcf59936d 100644 --- a/web/src/lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte +++ b/web/src/lib/components/admin-page/settings/image/image-settings.svelte @@ -25,10 +25,10 @@
(config.thumbnail.colorspace = e.detail ? Colorspace.P3 : Colorspace.Srgb)} - isEdited={config.thumbnail.colorspace !== savedConfig.thumbnail.colorspace} + checked={config.image.colorspace === Colorspace.P3} + on:toggle={(e) => (config.image.colorspace = e.detail ? Colorspace.P3 : Colorspace.Srgb)} + isEdited={config.image.colorspace !== savedConfig.image.colorspace} />
dispatch('reset', { ...detail, configKeys: ['thumbnail'] })} - on:save={() => dispatch('save', { thumbnail: config.thumbnail })} + on:reset={({ detail }) => dispatch('reset', { ...detail, configKeys: ['image'] })} + on:save={() => dispatch('save', { image: config.image })} showResetToDefault={!isEqual(savedConfig, defaultConfig)} {disabled} /> diff --git a/web/src/routes/admin/system-settings/+page.svelte b/web/src/routes/admin/system-settings/+page.svelte index d63f9544a..cb80a9e0d 100644 --- a/web/src/routes/admin/system-settings/+page.svelte +++ b/web/src/routes/admin/system-settings/+page.svelte @@ -13,7 +13,7 @@ import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte'; import StorageTemplateSettings from '$lib/components/admin-page/settings/storage-template/storage-template-settings.svelte'; import ThemeSettings from '$lib/components/admin-page/settings/theme/theme-settings.svelte'; - import ThumbnailSettings from '$lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte'; + import ImageSettings from '$lib/components/admin-page/settings/image/image-settings.svelte'; import TrashSettings from '$lib/components/admin-page/settings/trash-settings/trash-settings.svelte'; import UserSettings from '$lib/components/admin-page/settings/user-settings/user-settings.svelte'; import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; @@ -43,7 +43,7 @@ | typeof ServerSettings | typeof StorageTemplateSettings | typeof ThemeSettings - | typeof ThumbnailSettings + | typeof ImageSettings | typeof TrashSettings | typeof NewVersionCheckSettings | typeof FFmpegSettings @@ -64,6 +64,12 @@ subtitle: string; key: string; }> = [ + { + item: ImageSettings, + title: 'Image Settings', + subtitle: 'Manage the quality and resolution of generated images', + key: 'image', + }, { item: JobSettings, title: 'Job Settings', @@ -124,12 +130,6 @@ subtitle: 'Manage customization of the Immich web interface', key: 'theme', }, - { - item: ThumbnailSettings, - title: 'Thumbnail Settings', - subtitle: 'Manage the resolution of thumbnail sizes', - key: 'thumbnail', - }, { item: TrashSettings, title: 'Trash Settings', From e5d9372708a7ef6a078a68ddf0b8cad3df03b56d Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 2 Apr 2024 00:13:45 -0500 Subject: [PATCH 05/25] fix(web): weird Overpass font height (#8458) --- web/src/app.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/src/app.css b/web/src/app.css index c361d890c..9db440e46 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -29,6 +29,7 @@ src: url('$lib/assets/fonts/overpass/Overpass.ttf') format('truetype-variations'); font-weight: 1 999; font-style: normal; + ascent-override: 100%; } @font-face { @@ -36,6 +37,7 @@ src: url('$lib/assets/fonts/overpass/OverpassMono.ttf') format('truetype-variations'); font-weight: 1 999; font-style: monospace; + ascent-override: 100%; } @font-face { From 4ab4a35eba09709372669da0c5c0665f50d95051 Mon Sep 17 00:00:00 2001 From: Fynn Petersen-Frey <10599762+fyfrey@users.noreply.github.com> Date: Tue, 2 Apr 2024 07:22:15 +0200 Subject: [PATCH 06/25] fix(mobile): sync all album properties (#8332) --- mobile/lib/shared/models/album.dart | 18 +++++++++--------- mobile/lib/shared/services/sync.service.dart | 20 ++++++++++++-------- mobile/lib/utils/datetime_comparison.dart | 3 +++ 3 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 mobile/lib/utils/datetime_comparison.dart diff --git a/mobile/lib/shared/models/album.dart b/mobile/lib/shared/models/album.dart index 3ed8f69ea..55c105c74 100644 --- a/mobile/lib/shared/models/album.dart +++ b/mobile/lib/shared/models/album.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/models/user.dart'; +import 'package:immich_mobile/utils/datetime_comparison.dart'; import 'package:isar/isar.dart'; import 'package:openapi/api.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -72,21 +73,18 @@ class Album { @override bool operator ==(other) { if (other is! Album) return false; - - final lastModifiedAssetTimestampIsSetAndEqual = - lastModifiedAssetTimestamp != null && - other.lastModifiedAssetTimestamp != null - ? lastModifiedAssetTimestamp! - .isAtSameMomentAs(other.lastModifiedAssetTimestamp!) - : true; - return id == other.id && remoteId == other.remoteId && localId == other.localId && name == other.name && createdAt.isAtSameMomentAs(other.createdAt) && modifiedAt.isAtSameMomentAs(other.modifiedAt) && - lastModifiedAssetTimestampIsSetAndEqual && + isAtSameMomentAs(startDate, other.startDate) && + isAtSameMomentAs(endDate, other.endDate) && + isAtSameMomentAs( + lastModifiedAssetTimestamp, + other.lastModifiedAssetTimestamp, + ) && shared == other.shared && activityEnabled == other.activityEnabled && owner.value == other.owner.value && @@ -104,6 +102,8 @@ class Album { name.hashCode ^ createdAt.hashCode ^ modifiedAt.hashCode ^ + startDate.hashCode ^ + endDate.hashCode ^ lastModifiedAssetTimestamp.hashCode ^ shared.hashCode ^ activityEnabled.hashCode ^ diff --git a/mobile/lib/shared/services/sync.service.dart b/mobile/lib/shared/services/sync.service.dart index a441091d3..e547eb012 100644 --- a/mobile/lib/shared/services/sync.service.dart +++ b/mobile/lib/shared/services/sync.service.dart @@ -12,6 +12,7 @@ import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:immich_mobile/shared/services/hash.service.dart'; import 'package:immich_mobile/utils/async_mutex.dart'; import 'package:immich_mobile/extensions/collection_extensions.dart'; +import 'package:immich_mobile/utils/datetime_comparison.dart'; import 'package:immich_mobile/utils/diff.dart'; import 'package:isar/isar.dart'; import 'package:logging/logging.dart'; @@ -343,8 +344,13 @@ class SyncService { album.name = dto.albumName; album.shared = dto.shared; + album.createdAt = dto.createdAt; album.modifiedAt = dto.updatedAt; + album.startDate = dto.startDate; + album.endDate = dto.endDate; album.lastModifiedAssetTimestamp = originalDto.lastModifiedAssetTimestamp; + album.shared = dto.shared; + album.activityEnabled = dto.isActivityEnabled; if (album.thumbnail.value?.remoteId != dto.albumThumbnailAssetId) { album.thumbnail.value = await _db.assets .where() @@ -863,12 +869,10 @@ bool _hasAlbumResponseDtoChanged(AlbumResponseDto dto, Album a) { dto.shared != a.shared || dto.sharedUsers.length != a.sharedUsers.length || !dto.updatedAt.isAtSameMomentAs(a.modifiedAt) || - (dto.lastModifiedAssetTimestamp == null && - a.lastModifiedAssetTimestamp != null) || - (dto.lastModifiedAssetTimestamp != null && - a.lastModifiedAssetTimestamp == null) || - (dto.lastModifiedAssetTimestamp != null && - a.lastModifiedAssetTimestamp != null && - !dto.lastModifiedAssetTimestamp! - .isAtSameMomentAs(a.lastModifiedAssetTimestamp!)); + !isAtSameMomentAs(dto.startDate, a.startDate) || + !isAtSameMomentAs(dto.endDate, a.endDate) || + !isAtSameMomentAs( + dto.lastModifiedAssetTimestamp, + a.lastModifiedAssetTimestamp, + ); } diff --git a/mobile/lib/utils/datetime_comparison.dart b/mobile/lib/utils/datetime_comparison.dart new file mode 100644 index 000000000..8c53ea45b --- /dev/null +++ b/mobile/lib/utils/datetime_comparison.dart @@ -0,0 +1,3 @@ +bool isAtSameMomentAs(DateTime? a, DateTime? b) => + (a == null && b == null) || + ((a != null && b != null) && a.isAtSameMomentAs(b)); From 0849dbd1afe957cdd4e8cd9a69aac682a80b40c4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 02:20:52 -0400 Subject: [PATCH 07/25] fix(deps): update typescript-projects (#8451) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 90 ++--- docs/package-lock.json | 14 +- e2e/package-lock.json | 84 ++--- server/package-lock.json | 744 +++++++++++++++++++-------------------- server/package.json | 2 +- web/package-lock.json | 136 +++---- 6 files changed, 529 insertions(+), 541 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index dd9c2cc00..6edc44424 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1251,16 +1251,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz", - "integrity": "sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz", + "integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/type-utils": "7.3.1", - "@typescript-eslint/utils": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/type-utils": "7.4.0", + "@typescript-eslint/utils": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1286,15 +1286,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.3.1.tgz", - "integrity": "sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz", + "integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/typescript-estree": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4" }, "engines": { @@ -1314,13 +1314,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz", - "integrity": "sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz", + "integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1" + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1331,13 +1331,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz", - "integrity": "sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz", + "integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.3.1", - "@typescript-eslint/utils": "7.3.1", + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/utils": "7.4.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1358,9 +1358,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.3.1.tgz", - "integrity": "sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz", + "integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1371,13 +1371,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz", - "integrity": "sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz", + "integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1399,17 +1399,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.3.1.tgz", - "integrity": "sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz", + "integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/typescript-estree": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", "semver": "^7.5.4" }, "engines": { @@ -1424,12 +1424,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz", - "integrity": "sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz", + "integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/types": "7.4.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -4456,9 +4456,9 @@ } }, "node_modules/vite": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.3.tgz", - "integrity": "sha512-+i1oagbvkVIhEy9TnEV+fgXsng13nZM90JQbrcPrf6DvW2mXARlz+DK7DLiDP+qeKoD1FCVx/1SpFL1CLq9Mhw==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", + "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==", "dev": true, "dependencies": { "esbuild": "^0.20.1", diff --git a/docs/package-lock.json b/docs/package-lock.json index f173cef2c..d86f69ac9 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -3429,9 +3429,9 @@ } }, "node_modules/@tsconfig/docusaurus": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/docusaurus/-/docusaurus-2.0.2.tgz", - "integrity": "sha512-12HWfYmgUl4M2o76/TFufGtI68wl2k/b8qPrIrG7ci9YJLrpAtadpy897Bz5v29Mlkr7a1Hq4KHdQTKtU+2rhQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/docusaurus/-/docusaurus-2.0.3.tgz", + "integrity": "sha512-3l1L5PzWVa7l0691TjnsZ0yOIEwG9DziSqu5IPZPlI5Dowi7z42cEym8Y35GHbgHvPcBfNxfrbxm7Cncn4nByQ==", "dev": true }, "node_modules/@types/acorn": { @@ -15781,9 +15781,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", - "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -15793,7 +15793,7 @@ "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.19.1", + "jiti": "^1.21.0", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 127ef5767..894029102 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -1277,16 +1277,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz", - "integrity": "sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz", + "integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/type-utils": "7.3.1", - "@typescript-eslint/utils": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/type-utils": "7.4.0", + "@typescript-eslint/utils": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1312,15 +1312,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.3.1.tgz", - "integrity": "sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz", + "integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/typescript-estree": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4" }, "engines": { @@ -1340,13 +1340,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz", - "integrity": "sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz", + "integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1" + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1357,13 +1357,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz", - "integrity": "sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz", + "integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.3.1", - "@typescript-eslint/utils": "7.3.1", + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/utils": "7.4.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1384,9 +1384,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.3.1.tgz", - "integrity": "sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz", + "integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1397,13 +1397,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz", - "integrity": "sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz", + "integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1449,17 +1449,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.3.1.tgz", - "integrity": "sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz", + "integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/typescript-estree": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", "semver": "^7.5.4" }, "engines": { @@ -1474,12 +1474,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz", - "integrity": "sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz", + "integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/types": "7.4.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { diff --git a/server/package-lock.json b/server/package-lock.json index 5d3686c9f..cad273e1e 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -996,9 +996,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", - "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.1.1.tgz", + "integrity": "sha512-3bfqkzuR1KLx57nZfjr2NLnFOobvyS0aTszaEGCGqmYMVDRaGvgIZbjGSV/MHSSmLgQ/b9JFHQ5xm5WRZYd+XQ==", "optional": true, "dependencies": { "tslib": "^2.4.0" @@ -1287,9 +1287,9 @@ "dev": true }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz", - "integrity": "sha512-itHBs1rPmsmGF9p4qRe++CzCgd+kFYktnsoR1sbIAfsRMrJZau0Tt1AH9KVnufc2/tU02Gf6Ibujx+15qRE03w==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.3.tgz", + "integrity": "sha512-FaNiGX1MrOuJ3hxuNzWgsT/mg5OHG/Izh59WW2mk1UwYHUwtfbhk5QNKYZgxf0pLOhx9ctGiGa2OykD71vOnSw==", "cpu": [ "arm64" ], @@ -1308,13 +1308,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.1" + "@img/sharp-libvips-darwin-arm64": "1.0.2" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.2.tgz", - "integrity": "sha512-/rK/69Rrp9x5kaWBjVN07KixZanRr+W1OiyKdXcbjQD6KbW+obaTeBBtLUAtbBsnlTTmWthw99xqoOS7SsySDg==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.3.tgz", + "integrity": "sha512-2QeSl7QDK9ru//YBT4sQkoq7L0EAJZA3rtV+v9p8xTKl4U1bUqTIaCnoC7Ctx2kCjQgwFXDasOtPTCT8eCTXvw==", "cpu": [ "x64" ], @@ -1333,13 +1333,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.1" + "@img/sharp-libvips-darwin-x64": "1.0.2" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.1.tgz", - "integrity": "sha512-kQyrSNd6lmBV7O0BUiyu/OEw9yeNGFbQhbxswS1i6rMDwBBSX+e+rPzu3S+MwAiGU3HdLze3PanQ4Xkfemgzcw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==", "cpu": [ "arm64" ], @@ -1358,9 +1358,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.1.tgz", - "integrity": "sha512-eVU/JYLPVjhhrd8Tk6gosl5pVlvsqiFlt50wotCvdkFGf+mDNBJxMh+bvav+Wt3EBnNZWq8Sp2I7XfSjm8siog==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz", + "integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==", "cpu": [ "x64" ], @@ -1379,9 +1379,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.1.tgz", - "integrity": "sha512-FtdMvR4R99FTsD53IA3LxYGghQ82t3yt0ZQ93WMZ2xV3dqrb0E8zq4VHaTOuLEAuA83oDawHV3fd+BsAPadHIQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz", + "integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==", "cpu": [ "arm" ], @@ -1400,9 +1400,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.1.tgz", - "integrity": "sha512-bnGG+MJjdX70mAQcSLxgeJco11G+MxTz+ebxlz8Y3dxyeb3Nkl7LgLI0mXupoO+u1wRNx/iRj5yHtzA4sde1yA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz", + "integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==", "cpu": [ "arm64" ], @@ -1421,9 +1421,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.1.tgz", - "integrity": "sha512-3+rzfAR1YpMOeA2zZNp+aYEzGNWK4zF3+sdMxuCS3ey9HhDbJ66w6hDSHDMoap32DueFwhhs3vwooAB2MaK4XQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz", + "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==", "cpu": [ "s390x" ], @@ -1442,9 +1442,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.1.tgz", - "integrity": "sha512-3NR1mxFsaSgMMzz1bAnnKbSAI+lHXVTqAHgc1bgzjHuXjo4hlscpUxc0vFSAPKI3yuzdzcZOkq7nDPrP2F8Jgw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz", + "integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==", "cpu": [ "x64" ], @@ -1463,9 +1463,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.1.tgz", - "integrity": "sha512-5aBRcjHDG/T6jwC3Edl3lP8nl9U2Yo8+oTl5drd1dh9Z1EBfzUKAJFUDTDisDjUwc7N4AjnPGfCA3jl3hY8uDg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz", + "integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==", "cpu": [ "arm64" ], @@ -1484,9 +1484,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.1.tgz", - "integrity": "sha512-dcT7inI9DBFK6ovfeWRe3hG30h51cBAP5JXlZfx6pzc/Mnf9HFCQDLtYf4MCBjxaaTfjCCjkBxcy3XzOAo5txw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz", + "integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==", "cpu": [ "x64" ], @@ -1505,9 +1505,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.2.tgz", - "integrity": "sha512-Fndk/4Zq3vAc4G/qyfXASbS3HBZbKrlnKZLEJzPLrXoJuipFNNwTes71+Ki1hwYW5lch26niRYoZFAtZVf3EGA==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.3.tgz", + "integrity": "sha512-Q7Ee3fFSC9P7vUSqVEF0zccJsZ8GiiCJYGWDdhEjdlOeS9/jdkyJ6sUSPj+bL8VuOYFSbofrW0t/86ceVhx32w==", "cpu": [ "arm" ], @@ -1526,13 +1526,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.1" + "@img/sharp-libvips-linux-arm": "1.0.2" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.2.tgz", - "integrity": "sha512-pz0NNo882vVfqJ0yNInuG9YH71smP4gRSdeL09ukC2YLE6ZyZePAlWKEHgAzJGTiOh8Qkaov6mMIMlEhmLdKew==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.3.tgz", + "integrity": "sha512-Zf+sF1jHZJKA6Gor9hoYG2ljr4wo9cY4twaxgFDvlG0Xz9V7sinsPp8pFd1XtlhTzYo0IhDbl3rK7P6MzHpnYA==", "cpu": [ "arm64" ], @@ -1551,13 +1551,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.1" + "@img/sharp-libvips-linux-arm64": "1.0.2" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.2.tgz", - "integrity": "sha512-MBoInDXDppMfhSzbMmOQtGfloVAflS2rP1qPcUIiITMi36Mm5YR7r0ASND99razjQUpHTzjrU1flO76hKvP5RA==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.3.tgz", + "integrity": "sha512-vFk441DKRFepjhTEH20oBlFrHcLjPfI8B0pMIxGm3+yilKyYeHEVvrZhYFdqIseSclIqbQ3SnZMwEMWonY5XFA==", "cpu": [ "s390x" ], @@ -1576,13 +1576,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.1" + "@img/sharp-libvips-linux-s390x": "1.0.2" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.2.tgz", - "integrity": "sha512-xUT82H5IbXewKkeF5aiooajoO1tQV4PnKfS/OZtb5DDdxS/FCI/uXTVZ35GQ97RZXsycojz/AJ0asoz6p2/H/A==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.3.tgz", + "integrity": "sha512-Q4I++herIJxJi+qmbySd072oDPRkCg/SClLEIDh5IL9h1zjhqjv82H0Seupd+q2m0yOfD+/fJnjSoDFtKiHu2g==", "cpu": [ "x64" ], @@ -1601,13 +1601,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.1" + "@img/sharp-libvips-linux-x64": "1.0.2" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.2.tgz", - "integrity": "sha512-F+0z8JCu/UnMzg8IYW1TMeiViIWBVg7IWP6nE0p5S5EPQxlLd76c8jYemG21X99UzFwgkRo5yz2DS+zbrnxZeA==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.3.tgz", + "integrity": "sha512-qnDccehRDXadhM9PM5hLvcPRYqyFCBN31kq+ErBSZtZlsAc1U4Z85xf/RXv1qolkdu+ibw64fUDaRdktxTNP9A==", "cpu": [ "arm64" ], @@ -1626,13 +1626,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.1" + "@img/sharp-libvips-linuxmusl-arm64": "1.0.2" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.2.tgz", - "integrity": "sha512-+ZLE3SQmSL+Fn1gmSaM8uFusW5Y3J9VOf+wMGNnTtJUMUxFhv+P4UPaYEYT8tqnyYVaOVGgMN/zsOxn9pSsO2A==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.3.tgz", + "integrity": "sha512-Jhchim8kHWIU/GZ+9poHMWRcefeaxFIs9EBqf9KtcC14Ojk6qua7ghKiPs0sbeLbLj/2IGBtDcxHyjCdYWkk2w==", "cpu": [ "x64" ], @@ -1651,19 +1651,19 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.1" + "@img/sharp-libvips-linuxmusl-x64": "1.0.2" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.2.tgz", - "integrity": "sha512-fLbTaESVKuQcpm8ffgBD7jLb/CQLcATju/jxtTXR1XCLwbOQt+OL5zPHSDMmp2JZIeq82e18yE0Vv7zh6+6BfQ==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.3.tgz", + "integrity": "sha512-68zivsdJ0koE96stdUfM+gmyaK/NcoSZK5dV5CAjES0FUXS9lchYt8LAB5rTbM7nlWtxaU/2GON0HVN6/ZYJAQ==", "cpu": [ "wasm32" ], "optional": true, "dependencies": { - "@emnapi/runtime": "^0.45.0" + "@emnapi/runtime": "^1.1.0" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0", @@ -1676,9 +1676,9 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.2.tgz", - "integrity": "sha512-okBpql96hIGuZ4lN3+nsAjGeggxKm7hIRu9zyec0lnfB8E7Z6p95BuRZzDDXZOl2e8UmR4RhYt631i7mfmKU8g==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.3.tgz", + "integrity": "sha512-CyimAduT2whQD8ER4Ux7exKrtfoaUiVr7HG0zZvO0XTFn2idUWljjxv58GxNTkFb8/J9Ub9AqITGkJD6ZginxQ==", "cpu": [ "ia32" ], @@ -1697,9 +1697,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.2.tgz", - "integrity": "sha512-E4magOks77DK47FwHUIGH0RYWSgRBfGdK56kIHSVeB9uIS4pPFr4N2kIVsXdQQo4LzOsENKV5KAhRlRL7eMAdg==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.3.tgz", + "integrity": "sha512-viT4fUIDKnli3IfOephGnolMzhz5VaTvDRkYqtZxOMIoMQ4MrAziO7pT1nVnOt2FAm7qW5aa+CCc13aEY6Le0g==", "cpu": [ "x64" ], @@ -2382,9 +2382,9 @@ ] }, "node_modules/@nestjs/bull-shared": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-10.1.0.tgz", - "integrity": "sha512-E1lAvVTCwbtBXySElkVrleXzr1bNuTCOLaQ1GmLSQGGlzXIvrXFXEIS1Dh1JCULICC25b7rGOfD3yL7uKRaMzw==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-10.1.1.tgz", + "integrity": "sha512-su7eThDrSz1oQagEi8l+1CyZ7N6nMgmyAX0DuZoXqT1KEVEDqGX7x80RlPVF60m/8SYOskckGMjJROSfNQcErw==", "dependencies": { "tslib": "2.6.2" }, @@ -2394,11 +2394,11 @@ } }, "node_modules/@nestjs/bullmq": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-10.1.0.tgz", - "integrity": "sha512-e4QD3JilyOZAddyQ4ABj0rX7T2Rr0OVx4KwJKWTpaEqNQhBP4yVLZbSdEY+GFu1HEE8NkV92Q8BJJdCxlVphSw==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-10.1.1.tgz", + "integrity": "sha512-afYx1wYCKtXEu1p0S1+qw2o7QaZWr/EQgF7Wkt3YL8RBIECy5S4C450gv/cRGd8EZjlt6bw8hGCLqR2Q5VjHpQ==", "dependencies": { - "@nestjs/bull-shared": "^10.1.0", + "@nestjs/bull-shared": "^10.1.1", "tslib": "2.6.2" }, "peerDependencies": { @@ -2538,9 +2538,9 @@ } }, "node_modules/@nestjs/common": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.5.tgz", - "integrity": "sha512-XWxbDf2ey/jAyEa3/XpckgfzJZ9j3I05ZkEFx7cAlebFuVKeq5UDDb5Sq9O7hMmbH9xdQj3pYT19SSj01hKeug==", + "version": "10.3.6", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.6.tgz", + "integrity": "sha512-ExWGwjKs8L3sAm6SMRIV/N2HzVol4VFO3EbDhKL/HV1FQXAqj7otjXv/SzGBlI+/ax1nQfEdGgZbSMeZeeR6VA==", "dependencies": { "iterare": "1.2.1", "tslib": "2.6.2", @@ -2566,11 +2566,11 @@ } }, "node_modules/@nestjs/config": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.0.tgz", - "integrity": "sha512-BpYRn57shg7CH35KGT6h+hT7ZucB6Qn2B3NBNdvhD4ApU8huS5pX/Wc2e/aO5trIha606Bz2a9t9/vbiuTBTww==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.1.tgz", + "integrity": "sha512-tFZyLJKanSAu51ygQ6ZBSpx95pRcwS6qSpJDW6FFgRQzkOaOUXpL8qD8yMNoYoYxuJCxph+waiBaWKgFWxn3sw==", "dependencies": { - "dotenv": "16.4.1", + "dotenv": "16.4.5", "dotenv-expand": "10.0.0", "lodash": "4.17.21", "uuid": "9.0.1" @@ -2580,21 +2580,10 @@ "rxjs": "^7.1.0" } }, - "node_modules/@nestjs/config/node_modules/dotenv": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz", - "integrity": "sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" - } - }, "node_modules/@nestjs/core": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.5.tgz", - "integrity": "sha512-U7SrGD9/Mu4eUtxfZYiGdY38FcksEyJegs4dQZ8B19nnusw0aTocPEy4HVsmx0LLO4sG+fBLLYzCDDr9kFwXAQ==", + "version": "10.3.6", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.6.tgz", + "integrity": "sha512-06RFGSFGwDiMRdWFm1Rofp9A1G+1+W7dH2U07S6mE2XtABx5UBwA5kGBZL8z9osdNnBg+6WMpSpkNvQ35f60EA==", "hasInstallScript": true, "dependencies": { "@nuxtjs/opencollective": "0.3.2", @@ -2660,13 +2649,13 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.5.tgz", - "integrity": "sha512-IhVomwLvdLlv4zCdQK2ROT/nInk1i8m4K48lAUHJV5UVktgVmg0WbQga2/9KywaTjNbx+eWhZXXFii+vtFRAOw==", + "version": "10.3.6", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.6.tgz", + "integrity": "sha512-yNHuBn/ktjssl+hJd3RXNyMjqL7jXOCDHhnc0JvAmRf3L54/RBgKyDcDLZ6Mv/KYOrmfId5PNeAAjd9IPmcyBw==", "dependencies": { "body-parser": "1.20.2", "cors": "2.8.5", - "express": "4.18.3", + "express": "4.19.2", "multer": "1.4.4-lts.1", "tslib": "2.6.2" }, @@ -2680,9 +2669,9 @@ } }, "node_modules/@nestjs/platform-socket.io": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.5.tgz", - "integrity": "sha512-G2N3sTd9tZ7XQQ7RlrpaQdt1/IBztVHuKg686QmBTLVlRHZ1AMOmXouBk+q5SINT1XURiABa8tQh1Ydx0OEh9w==", + "version": "10.3.6", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.6.tgz", + "integrity": "sha512-DHFFmwWKgiHdqkLOG44RsaVolcuiLpnzQLOHA/2cDOpHfO7bYNnN4uc8WwWskYOHzzhIya3oJSHeSiLbHPjIkQ==", "dependencies": { "socket.io": "4.7.5", "tslib": "2.6.2" @@ -2765,9 +2754,9 @@ } }, "node_modules/@nestjs/testing": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.5.tgz", - "integrity": "sha512-j30/lxH0BayeDTigapYtQn/XhMRR7CzlFsm3dHoWViWQv0qT1r2ffe3927BbBLX3N/ZzglE10OAqW06ADZV8dw==", + "version": "10.3.6", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.6.tgz", + "integrity": "sha512-9atg+aGqhg3ZhwTz2oHMa3hg3JJ6XQfQcRhkiIsOegCIiV/esqBH7R71kAJC6ppOlKtT3XHngFvlIxo4tODKfw==", "dev": true, "dependencies": { "tslib": "2.6.2" @@ -2807,9 +2796,9 @@ } }, "node_modules/@nestjs/websockets": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.5.tgz", - "integrity": "sha512-6w383LUBFHoZ0eFODqEHN2NoIRUwbTd37Hc1KqtZZihhFUzscC/0LMAV20o9LdfS/Xjog5ShNTxvOHuzNBnE4A==", + "version": "10.3.6", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.6.tgz", + "integrity": "sha512-XdHF4E+O2TLqqdP211OmlkcXIJ+GMGqfm7/i+Zlg1KwwGu7vG/HOX+m/H9x8UMD41rnWO3vXBqvCLugZpTCiGw==", "dependencies": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -4920,16 +4909,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz", - "integrity": "sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz", + "integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/type-utils": "7.3.1", - "@typescript-eslint/utils": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/type-utils": "7.4.0", + "@typescript-eslint/utils": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -4955,15 +4944,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.3.1.tgz", - "integrity": "sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz", + "integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/typescript-estree": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4" }, "engines": { @@ -4983,13 +4972,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz", - "integrity": "sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz", + "integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1" + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5000,13 +4989,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz", - "integrity": "sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz", + "integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.3.1", - "@typescript-eslint/utils": "7.3.1", + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/utils": "7.4.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -5027,9 +5016,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.3.1.tgz", - "integrity": "sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz", + "integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5040,13 +5029,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz", - "integrity": "sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz", + "integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -5092,17 +5081,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.3.1.tgz", - "integrity": "sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz", + "integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/typescript-estree": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", "semver": "^7.5.4" }, "engines": { @@ -5117,12 +5106,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz", - "integrity": "sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz", + "integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/types": "7.4.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -7050,9 +7039,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "engines": { "node": ">=8" } @@ -7774,16 +7763,16 @@ } }, "node_modules/express": { - "version": "4.18.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", - "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -7815,9 +7804,9 @@ } }, "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -11245,9 +11234,9 @@ "dev": true }, "node_modules/picomatch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", - "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "engines": { "node": ">=12" }, @@ -12236,9 +12225,9 @@ "dev": true }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -12390,42 +12379,42 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/sharp": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.2.tgz", - "integrity": "sha512-WlYOPyyPDiiM07j/UO+E720ju6gtNtHjEGg5vovUk1Lgxyjm2LFO+37Nt/UI3MMh2l6hxTWQWi7qk3cXJTutcQ==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.3.tgz", + "integrity": "sha512-vHUeXJU1UvlO/BNwTpT0x/r53WkLUVxrmb5JTgW92fdFCFk0ispLMAeu/jPO2vjkXM1fYUi3K7/qcLF47pwM1A==", "hasInstallScript": true, "dependencies": { "color": "^4.2.3", - "detect-libc": "^2.0.2", - "semver": "^7.5.4" + "detect-libc": "^2.0.3", + "semver": "^7.6.0" }, "engines": { - "libvips": ">=8.15.1", + "libvips": ">=8.15.2", "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.2", - "@img/sharp-darwin-x64": "0.33.2", - "@img/sharp-libvips-darwin-arm64": "1.0.1", - "@img/sharp-libvips-darwin-x64": "1.0.1", - "@img/sharp-libvips-linux-arm": "1.0.1", - "@img/sharp-libvips-linux-arm64": "1.0.1", - "@img/sharp-libvips-linux-s390x": "1.0.1", - "@img/sharp-libvips-linux-x64": "1.0.1", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.1", - "@img/sharp-libvips-linuxmusl-x64": "1.0.1", - "@img/sharp-linux-arm": "0.33.2", - "@img/sharp-linux-arm64": "0.33.2", - "@img/sharp-linux-s390x": "0.33.2", - "@img/sharp-linux-x64": "0.33.2", - "@img/sharp-linuxmusl-arm64": "0.33.2", - "@img/sharp-linuxmusl-x64": "0.33.2", - "@img/sharp-wasm32": "0.33.2", - "@img/sharp-win32-ia32": "0.33.2", - "@img/sharp-win32-x64": "0.33.2" + "@img/sharp-darwin-arm64": "0.33.3", + "@img/sharp-darwin-x64": "0.33.3", + "@img/sharp-libvips-darwin-arm64": "1.0.2", + "@img/sharp-libvips-darwin-x64": "1.0.2", + "@img/sharp-libvips-linux-arm": "1.0.2", + "@img/sharp-libvips-linux-arm64": "1.0.2", + "@img/sharp-libvips-linux-s390x": "1.0.2", + "@img/sharp-libvips-linux-x64": "1.0.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.2", + "@img/sharp-libvips-linuxmusl-x64": "1.0.2", + "@img/sharp-linux-arm": "0.33.3", + "@img/sharp-linux-arm64": "0.33.3", + "@img/sharp-linux-s390x": "0.33.3", + "@img/sharp-linux-x64": "0.33.3", + "@img/sharp-linuxmusl-arm64": "0.33.3", + "@img/sharp-linuxmusl-x64": "0.33.3", + "@img/sharp-wasm32": "0.33.3", + "@img/sharp-win32-ia32": "0.33.3", + "@img/sharp-win32-x64": "0.33.3" } }, "node_modules/shebang-command": { @@ -15175,9 +15164,9 @@ } }, "@emnapi/runtime": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", - "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.1.1.tgz", + "integrity": "sha512-3bfqkzuR1KLx57nZfjr2NLnFOobvyS0aTszaEGCGqmYMVDRaGvgIZbjGSV/MHSSmLgQ/b9JFHQ5xm5WRZYd+XQ==", "optional": true, "requires": { "tslib": "^2.4.0" @@ -15407,144 +15396,144 @@ "dev": true }, "@img/sharp-darwin-arm64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz", - "integrity": "sha512-itHBs1rPmsmGF9p4qRe++CzCgd+kFYktnsoR1sbIAfsRMrJZau0Tt1AH9KVnufc2/tU02Gf6Ibujx+15qRE03w==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.3.tgz", + "integrity": "sha512-FaNiGX1MrOuJ3hxuNzWgsT/mg5OHG/Izh59WW2mk1UwYHUwtfbhk5QNKYZgxf0pLOhx9ctGiGa2OykD71vOnSw==", "optional": true, "requires": { - "@img/sharp-libvips-darwin-arm64": "1.0.1" + "@img/sharp-libvips-darwin-arm64": "1.0.2" } }, "@img/sharp-darwin-x64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.2.tgz", - "integrity": "sha512-/rK/69Rrp9x5kaWBjVN07KixZanRr+W1OiyKdXcbjQD6KbW+obaTeBBtLUAtbBsnlTTmWthw99xqoOS7SsySDg==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.3.tgz", + "integrity": "sha512-2QeSl7QDK9ru//YBT4sQkoq7L0EAJZA3rtV+v9p8xTKl4U1bUqTIaCnoC7Ctx2kCjQgwFXDasOtPTCT8eCTXvw==", "optional": true, "requires": { - "@img/sharp-libvips-darwin-x64": "1.0.1" + "@img/sharp-libvips-darwin-x64": "1.0.2" } }, "@img/sharp-libvips-darwin-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.1.tgz", - "integrity": "sha512-kQyrSNd6lmBV7O0BUiyu/OEw9yeNGFbQhbxswS1i6rMDwBBSX+e+rPzu3S+MwAiGU3HdLze3PanQ4Xkfemgzcw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==", "optional": true }, "@img/sharp-libvips-darwin-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.1.tgz", - "integrity": "sha512-eVU/JYLPVjhhrd8Tk6gosl5pVlvsqiFlt50wotCvdkFGf+mDNBJxMh+bvav+Wt3EBnNZWq8Sp2I7XfSjm8siog==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz", + "integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==", "optional": true }, "@img/sharp-libvips-linux-arm": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.1.tgz", - "integrity": "sha512-FtdMvR4R99FTsD53IA3LxYGghQ82t3yt0ZQ93WMZ2xV3dqrb0E8zq4VHaTOuLEAuA83oDawHV3fd+BsAPadHIQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz", + "integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==", "optional": true }, "@img/sharp-libvips-linux-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.1.tgz", - "integrity": "sha512-bnGG+MJjdX70mAQcSLxgeJco11G+MxTz+ebxlz8Y3dxyeb3Nkl7LgLI0mXupoO+u1wRNx/iRj5yHtzA4sde1yA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz", + "integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==", "optional": true }, "@img/sharp-libvips-linux-s390x": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.1.tgz", - "integrity": "sha512-3+rzfAR1YpMOeA2zZNp+aYEzGNWK4zF3+sdMxuCS3ey9HhDbJ66w6hDSHDMoap32DueFwhhs3vwooAB2MaK4XQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz", + "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==", "optional": true }, "@img/sharp-libvips-linux-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.1.tgz", - "integrity": "sha512-3NR1mxFsaSgMMzz1bAnnKbSAI+lHXVTqAHgc1bgzjHuXjo4hlscpUxc0vFSAPKI3yuzdzcZOkq7nDPrP2F8Jgw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz", + "integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==", "optional": true }, "@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.1.tgz", - "integrity": "sha512-5aBRcjHDG/T6jwC3Edl3lP8nl9U2Yo8+oTl5drd1dh9Z1EBfzUKAJFUDTDisDjUwc7N4AjnPGfCA3jl3hY8uDg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz", + "integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==", "optional": true }, "@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.1.tgz", - "integrity": "sha512-dcT7inI9DBFK6ovfeWRe3hG30h51cBAP5JXlZfx6pzc/Mnf9HFCQDLtYf4MCBjxaaTfjCCjkBxcy3XzOAo5txw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz", + "integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==", "optional": true }, "@img/sharp-linux-arm": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.2.tgz", - "integrity": "sha512-Fndk/4Zq3vAc4G/qyfXASbS3HBZbKrlnKZLEJzPLrXoJuipFNNwTes71+Ki1hwYW5lch26niRYoZFAtZVf3EGA==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.3.tgz", + "integrity": "sha512-Q7Ee3fFSC9P7vUSqVEF0zccJsZ8GiiCJYGWDdhEjdlOeS9/jdkyJ6sUSPj+bL8VuOYFSbofrW0t/86ceVhx32w==", "optional": true, "requires": { - "@img/sharp-libvips-linux-arm": "1.0.1" + "@img/sharp-libvips-linux-arm": "1.0.2" } }, "@img/sharp-linux-arm64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.2.tgz", - "integrity": "sha512-pz0NNo882vVfqJ0yNInuG9YH71smP4gRSdeL09ukC2YLE6ZyZePAlWKEHgAzJGTiOh8Qkaov6mMIMlEhmLdKew==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.3.tgz", + "integrity": "sha512-Zf+sF1jHZJKA6Gor9hoYG2ljr4wo9cY4twaxgFDvlG0Xz9V7sinsPp8pFd1XtlhTzYo0IhDbl3rK7P6MzHpnYA==", "optional": true, "requires": { - "@img/sharp-libvips-linux-arm64": "1.0.1" + "@img/sharp-libvips-linux-arm64": "1.0.2" } }, "@img/sharp-linux-s390x": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.2.tgz", - "integrity": "sha512-MBoInDXDppMfhSzbMmOQtGfloVAflS2rP1qPcUIiITMi36Mm5YR7r0ASND99razjQUpHTzjrU1flO76hKvP5RA==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.3.tgz", + "integrity": "sha512-vFk441DKRFepjhTEH20oBlFrHcLjPfI8B0pMIxGm3+yilKyYeHEVvrZhYFdqIseSclIqbQ3SnZMwEMWonY5XFA==", "optional": true, "requires": { - "@img/sharp-libvips-linux-s390x": "1.0.1" + "@img/sharp-libvips-linux-s390x": "1.0.2" } }, "@img/sharp-linux-x64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.2.tgz", - "integrity": "sha512-xUT82H5IbXewKkeF5aiooajoO1tQV4PnKfS/OZtb5DDdxS/FCI/uXTVZ35GQ97RZXsycojz/AJ0asoz6p2/H/A==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.3.tgz", + "integrity": "sha512-Q4I++herIJxJi+qmbySd072oDPRkCg/SClLEIDh5IL9h1zjhqjv82H0Seupd+q2m0yOfD+/fJnjSoDFtKiHu2g==", "optional": true, "requires": { - "@img/sharp-libvips-linux-x64": "1.0.1" + "@img/sharp-libvips-linux-x64": "1.0.2" } }, "@img/sharp-linuxmusl-arm64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.2.tgz", - "integrity": "sha512-F+0z8JCu/UnMzg8IYW1TMeiViIWBVg7IWP6nE0p5S5EPQxlLd76c8jYemG21X99UzFwgkRo5yz2DS+zbrnxZeA==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.3.tgz", + "integrity": "sha512-qnDccehRDXadhM9PM5hLvcPRYqyFCBN31kq+ErBSZtZlsAc1U4Z85xf/RXv1qolkdu+ibw64fUDaRdktxTNP9A==", "optional": true, "requires": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.1" + "@img/sharp-libvips-linuxmusl-arm64": "1.0.2" } }, "@img/sharp-linuxmusl-x64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.2.tgz", - "integrity": "sha512-+ZLE3SQmSL+Fn1gmSaM8uFusW5Y3J9VOf+wMGNnTtJUMUxFhv+P4UPaYEYT8tqnyYVaOVGgMN/zsOxn9pSsO2A==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.3.tgz", + "integrity": "sha512-Jhchim8kHWIU/GZ+9poHMWRcefeaxFIs9EBqf9KtcC14Ojk6qua7ghKiPs0sbeLbLj/2IGBtDcxHyjCdYWkk2w==", "optional": true, "requires": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.1" + "@img/sharp-libvips-linuxmusl-x64": "1.0.2" } }, "@img/sharp-wasm32": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.2.tgz", - "integrity": "sha512-fLbTaESVKuQcpm8ffgBD7jLb/CQLcATju/jxtTXR1XCLwbOQt+OL5zPHSDMmp2JZIeq82e18yE0Vv7zh6+6BfQ==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.3.tgz", + "integrity": "sha512-68zivsdJ0koE96stdUfM+gmyaK/NcoSZK5dV5CAjES0FUXS9lchYt8LAB5rTbM7nlWtxaU/2GON0HVN6/ZYJAQ==", "optional": true, "requires": { - "@emnapi/runtime": "^0.45.0" + "@emnapi/runtime": "^1.1.0" } }, "@img/sharp-win32-ia32": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.2.tgz", - "integrity": "sha512-okBpql96hIGuZ4lN3+nsAjGeggxKm7hIRu9zyec0lnfB8E7Z6p95BuRZzDDXZOl2e8UmR4RhYt631i7mfmKU8g==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.3.tgz", + "integrity": "sha512-CyimAduT2whQD8ER4Ux7exKrtfoaUiVr7HG0zZvO0XTFn2idUWljjxv58GxNTkFb8/J9Ub9AqITGkJD6ZginxQ==", "optional": true }, "@img/sharp-win32-x64": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.2.tgz", - "integrity": "sha512-E4magOks77DK47FwHUIGH0RYWSgRBfGdK56kIHSVeB9uIS4pPFr4N2kIVsXdQQo4LzOsENKV5KAhRlRL7eMAdg==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.3.tgz", + "integrity": "sha512-viT4fUIDKnli3IfOephGnolMzhz5VaTvDRkYqtZxOMIoMQ4MrAziO7pT1nVnOt2FAm7qW5aa+CCc13aEY6Le0g==", "optional": true }, "@ioredis/commands": { @@ -16053,19 +16042,19 @@ "optional": true }, "@nestjs/bull-shared": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-10.1.0.tgz", - "integrity": "sha512-E1lAvVTCwbtBXySElkVrleXzr1bNuTCOLaQ1GmLSQGGlzXIvrXFXEIS1Dh1JCULICC25b7rGOfD3yL7uKRaMzw==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-10.1.1.tgz", + "integrity": "sha512-su7eThDrSz1oQagEi8l+1CyZ7N6nMgmyAX0DuZoXqT1KEVEDqGX7x80RlPVF60m/8SYOskckGMjJROSfNQcErw==", "requires": { "tslib": "2.6.2" } }, "@nestjs/bullmq": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-10.1.0.tgz", - "integrity": "sha512-e4QD3JilyOZAddyQ4ABj0rX7T2Rr0OVx4KwJKWTpaEqNQhBP4yVLZbSdEY+GFu1HEE8NkV92Q8BJJdCxlVphSw==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-10.1.1.tgz", + "integrity": "sha512-afYx1wYCKtXEu1p0S1+qw2o7QaZWr/EQgF7Wkt3YL8RBIECy5S4C450gv/cRGd8EZjlt6bw8hGCLqR2Q5VjHpQ==", "requires": { - "@nestjs/bull-shared": "^10.1.0", + "@nestjs/bull-shared": "^10.1.1", "tslib": "2.6.2" } }, @@ -16155,9 +16144,9 @@ } }, "@nestjs/common": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.5.tgz", - "integrity": "sha512-XWxbDf2ey/jAyEa3/XpckgfzJZ9j3I05ZkEFx7cAlebFuVKeq5UDDb5Sq9O7hMmbH9xdQj3pYT19SSj01hKeug==", + "version": "10.3.6", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.6.tgz", + "integrity": "sha512-ExWGwjKs8L3sAm6SMRIV/N2HzVol4VFO3EbDhKL/HV1FQXAqj7otjXv/SzGBlI+/ax1nQfEdGgZbSMeZeeR6VA==", "requires": { "iterare": "1.2.1", "tslib": "2.6.2", @@ -16165,27 +16154,20 @@ } }, "@nestjs/config": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.0.tgz", - "integrity": "sha512-BpYRn57shg7CH35KGT6h+hT7ZucB6Qn2B3NBNdvhD4ApU8huS5pX/Wc2e/aO5trIha606Bz2a9t9/vbiuTBTww==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.1.tgz", + "integrity": "sha512-tFZyLJKanSAu51ygQ6ZBSpx95pRcwS6qSpJDW6FFgRQzkOaOUXpL8qD8yMNoYoYxuJCxph+waiBaWKgFWxn3sw==", "requires": { - "dotenv": "16.4.1", + "dotenv": "16.4.5", "dotenv-expand": "10.0.0", "lodash": "4.17.21", "uuid": "9.0.1" - }, - "dependencies": { - "dotenv": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz", - "integrity": "sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==" - } } }, "@nestjs/core": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.5.tgz", - "integrity": "sha512-U7SrGD9/Mu4eUtxfZYiGdY38FcksEyJegs4dQZ8B19nnusw0aTocPEy4HVsmx0LLO4sG+fBLLYzCDDr9kFwXAQ==", + "version": "10.3.6", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.6.tgz", + "integrity": "sha512-06RFGSFGwDiMRdWFm1Rofp9A1G+1+W7dH2U07S6mE2XtABx5UBwA5kGBZL8z9osdNnBg+6WMpSpkNvQ35f60EA==", "requires": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", @@ -16210,21 +16192,21 @@ "requires": {} }, "@nestjs/platform-express": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.5.tgz", - "integrity": "sha512-IhVomwLvdLlv4zCdQK2ROT/nInk1i8m4K48lAUHJV5UVktgVmg0WbQga2/9KywaTjNbx+eWhZXXFii+vtFRAOw==", + "version": "10.3.6", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.6.tgz", + "integrity": "sha512-yNHuBn/ktjssl+hJd3RXNyMjqL7jXOCDHhnc0JvAmRf3L54/RBgKyDcDLZ6Mv/KYOrmfId5PNeAAjd9IPmcyBw==", "requires": { "body-parser": "1.20.2", "cors": "2.8.5", - "express": "4.18.3", + "express": "4.19.2", "multer": "1.4.4-lts.1", "tslib": "2.6.2" } }, "@nestjs/platform-socket.io": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.5.tgz", - "integrity": "sha512-G2N3sTd9tZ7XQQ7RlrpaQdt1/IBztVHuKg686QmBTLVlRHZ1AMOmXouBk+q5SINT1XURiABa8tQh1Ydx0OEh9w==", + "version": "10.3.6", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.6.tgz", + "integrity": "sha512-DHFFmwWKgiHdqkLOG44RsaVolcuiLpnzQLOHA/2cDOpHfO7bYNnN4uc8WwWskYOHzzhIya3oJSHeSiLbHPjIkQ==", "requires": { "socket.io": "4.7.5", "tslib": "2.6.2" @@ -16274,9 +16256,9 @@ } }, "@nestjs/testing": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.5.tgz", - "integrity": "sha512-j30/lxH0BayeDTigapYtQn/XhMRR7CzlFsm3dHoWViWQv0qT1r2ffe3927BbBLX3N/ZzglE10OAqW06ADZV8dw==", + "version": "10.3.6", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.6.tgz", + "integrity": "sha512-9atg+aGqhg3ZhwTz2oHMa3hg3JJ6XQfQcRhkiIsOegCIiV/esqBH7R71kAJC6ppOlKtT3XHngFvlIxo4tODKfw==", "dev": true, "requires": { "tslib": "2.6.2" @@ -16291,9 +16273,9 @@ } }, "@nestjs/websockets": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.5.tgz", - "integrity": "sha512-6w383LUBFHoZ0eFODqEHN2NoIRUwbTd37Hc1KqtZZihhFUzscC/0LMAV20o9LdfS/Xjog5ShNTxvOHuzNBnE4A==", + "version": "10.3.6", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.6.tgz", + "integrity": "sha512-XdHF4E+O2TLqqdP211OmlkcXIJ+GMGqfm7/i+Zlg1KwwGu7vG/HOX+m/H9x8UMD41rnWO3vXBqvCLugZpTCiGw==", "requires": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -17933,16 +17915,16 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz", - "integrity": "sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz", + "integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/type-utils": "7.3.1", - "@typescript-eslint/utils": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/type-utils": "7.4.0", + "@typescript-eslint/utils": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -17952,54 +17934,54 @@ } }, "@typescript-eslint/parser": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.3.1.tgz", - "integrity": "sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz", + "integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/typescript-estree": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz", - "integrity": "sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz", + "integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==", "dev": true, "requires": { - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1" + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0" } }, "@typescript-eslint/type-utils": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz", - "integrity": "sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz", + "integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "7.3.1", - "@typescript-eslint/utils": "7.3.1", + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/utils": "7.4.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/types": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.3.1.tgz", - "integrity": "sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz", + "integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz", - "integrity": "sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz", + "integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==", "dev": true, "requires": { - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -18029,27 +18011,27 @@ } }, "@typescript-eslint/utils": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.3.1.tgz", - "integrity": "sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz", + "integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/typescript-estree": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", "semver": "^7.5.4" } }, "@typescript-eslint/visitor-keys": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz", - "integrity": "sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz", + "integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==", "dev": true, "requires": { - "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/types": "7.4.0", "eslint-visitor-keys": "^3.4.1" } }, @@ -19481,9 +19463,9 @@ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==" }, "detect-newline": { "version": "3.1.0", @@ -20015,16 +19997,16 @@ } }, "express": { - "version": "4.18.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", - "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -20053,9 +20035,9 @@ }, "dependencies": { "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, "debug": { "version": "2.6.9", @@ -22672,9 +22654,9 @@ "dev": true }, "picomatch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", - "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==" }, "pirates": { "version": "4.0.6", @@ -23377,9 +23359,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "requires": { "lru-cache": "^6.0.0" }, @@ -23514,32 +23496,32 @@ } }, "sharp": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.2.tgz", - "integrity": "sha512-WlYOPyyPDiiM07j/UO+E720ju6gtNtHjEGg5vovUk1Lgxyjm2LFO+37Nt/UI3MMh2l6hxTWQWi7qk3cXJTutcQ==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.3.tgz", + "integrity": "sha512-vHUeXJU1UvlO/BNwTpT0x/r53WkLUVxrmb5JTgW92fdFCFk0ispLMAeu/jPO2vjkXM1fYUi3K7/qcLF47pwM1A==", "requires": { - "@img/sharp-darwin-arm64": "0.33.2", - "@img/sharp-darwin-x64": "0.33.2", - "@img/sharp-libvips-darwin-arm64": "1.0.1", - "@img/sharp-libvips-darwin-x64": "1.0.1", - "@img/sharp-libvips-linux-arm": "1.0.1", - "@img/sharp-libvips-linux-arm64": "1.0.1", - "@img/sharp-libvips-linux-s390x": "1.0.1", - "@img/sharp-libvips-linux-x64": "1.0.1", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.1", - "@img/sharp-libvips-linuxmusl-x64": "1.0.1", - "@img/sharp-linux-arm": "0.33.2", - "@img/sharp-linux-arm64": "0.33.2", - "@img/sharp-linux-s390x": "0.33.2", - "@img/sharp-linux-x64": "0.33.2", - "@img/sharp-linuxmusl-arm64": "0.33.2", - "@img/sharp-linuxmusl-x64": "0.33.2", - "@img/sharp-wasm32": "0.33.2", - "@img/sharp-win32-ia32": "0.33.2", - "@img/sharp-win32-x64": "0.33.2", + "@img/sharp-darwin-arm64": "0.33.3", + "@img/sharp-darwin-x64": "0.33.3", + "@img/sharp-libvips-darwin-arm64": "1.0.2", + "@img/sharp-libvips-darwin-x64": "1.0.2", + "@img/sharp-libvips-linux-arm": "1.0.2", + "@img/sharp-libvips-linux-arm64": "1.0.2", + "@img/sharp-libvips-linux-s390x": "1.0.2", + "@img/sharp-libvips-linux-x64": "1.0.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.2", + "@img/sharp-libvips-linuxmusl-x64": "1.0.2", + "@img/sharp-linux-arm": "0.33.3", + "@img/sharp-linux-arm64": "0.33.3", + "@img/sharp-linux-s390x": "0.33.3", + "@img/sharp-linux-x64": "0.33.3", + "@img/sharp-linuxmusl-arm64": "0.33.3", + "@img/sharp-linuxmusl-x64": "0.33.3", + "@img/sharp-wasm32": "0.33.3", + "@img/sharp-win32-ia32": "0.33.3", + "@img/sharp-win32-x64": "0.33.3", "color": "^4.2.3", - "detect-libc": "^2.0.2", - "semver": "^7.5.4" + "detect-libc": "^2.0.3", + "semver": "^7.6.0" } }, "shebang-command": { diff --git a/server/package.json b/server/package.json index e098e376f..4e3915682 100644 --- a/server/package.json +++ b/server/package.json @@ -168,6 +168,6 @@ "globalSetup": "/test/global-setup.js" }, "volta": { - "node": "20.11.1" + "node": "20.12.0" } } diff --git a/web/package-lock.json b/web/package-lock.json index 5fb9aa66b..a855def3b 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -2264,16 +2264,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz", - "integrity": "sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz", + "integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/type-utils": "7.3.1", - "@typescript-eslint/utils": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/type-utils": "7.4.0", + "@typescript-eslint/utils": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -2332,15 +2332,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.3.1.tgz", - "integrity": "sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz", + "integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/typescript-estree": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4" }, "engines": { @@ -2360,13 +2360,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz", - "integrity": "sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz", + "integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1" + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2377,13 +2377,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz", - "integrity": "sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz", + "integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.3.1", - "@typescript-eslint/utils": "7.3.1", + "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/utils": "7.4.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -2404,9 +2404,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.3.1.tgz", - "integrity": "sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz", + "integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2417,13 +2417,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz", - "integrity": "sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz", + "integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/visitor-keys": "7.3.1", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/visitor-keys": "7.4.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2502,17 +2502,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.3.1.tgz", - "integrity": "sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz", + "integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.3.1", - "@typescript-eslint/types": "7.3.1", - "@typescript-eslint/typescript-estree": "7.3.1", + "@typescript-eslint/scope-manager": "7.4.0", + "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/typescript-estree": "7.4.0", "semver": "^7.5.4" }, "engines": { @@ -2560,12 +2560,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz", - "integrity": "sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz", + "integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/types": "7.4.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -2803,9 +2803,9 @@ "dev": true }, "node_modules/@zoom-image/core": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@zoom-image/core/-/core-0.33.1.tgz", - "integrity": "sha512-RHzehi287J7SpPV3bHjHcTYMus5u2DlarvcCQJjy1O+Nh4PT8DCg+jiyhCd0SDkClWbppyrqtoDbSbogJpo85g==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@zoom-image/core/-/core-0.33.2.tgz", + "integrity": "sha512-Nqg/JrvtaScXJ16L7dcPQjun3F0enajULtokYWy13VtETqqBOBqKDa3feTJH7JXrYvEs/w6e4AU56UvzGG1wXA==", "dependencies": { "@namnode/store": "^0.1.0" }, @@ -2815,11 +2815,11 @@ } }, "node_modules/@zoom-image/svelte": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@zoom-image/svelte/-/svelte-0.2.7.tgz", - "integrity": "sha512-CyrP4NloVrA/w6Dzucdw0bNxZLmJ7dTFQ9dMu7KsIPuIf9KYGKPbWF/IBDPQGmxnDK+BB+0EGFW8411JyAP94g==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@zoom-image/svelte/-/svelte-0.2.8.tgz", + "integrity": "sha512-z+xCyMHIeTmSYZYdDde/EAz08odlBWMv6jmHOcz95DMt3sJ/+vlVtUEMCzZfuK2KqV8v59EKZHAeutMzTx3QPg==", "dependencies": { - "@zoom-image/core": "0.33.1" + "@zoom-image/core": "0.33.2" }, "funding": { "type": "github", @@ -5677,9 +5677,9 @@ } }, "node_modules/jiti": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", - "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "dev": true, "bin": { "jiti": "bin/jiti.js" @@ -8223,9 +8223,9 @@ "peer": true }, "node_modules/tailwindcss": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", - "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -8236,7 +8236,7 @@ "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.19.1", + "jiti": "^1.21.0", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", @@ -8295,19 +8295,25 @@ } }, "node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", "dev": true, "engines": { "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/tailwindcss/node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", "dev": true, + "bin": { + "yaml": "bin.mjs" + }, "engines": { "node": ">= 14" } @@ -8671,9 +8677,9 @@ } }, "node_modules/vite": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.3.tgz", - "integrity": "sha512-+i1oagbvkVIhEy9TnEV+fgXsng13nZM90JQbrcPrf6DvW2mXARlz+DK7DLiDP+qeKoD1FCVx/1SpFL1CLq9Mhw==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", + "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==", "dev": true, "dependencies": { "esbuild": "^0.20.1", From cd0e537e3ea44db4d081bbdeb1dfcff8fcf8cd8a Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Tue, 2 Apr 2024 10:23:17 -0400 Subject: [PATCH 08/25] feat: persistent memories (#8330) * feat: persistent memories * refactor: use new add/remove asset utility --- e2e/src/api/specs/memory.e2e-spec.ts | 376 ++++++++++++++++ mobile/openapi/.openapi-generator/FILES | 15 + mobile/openapi/README.md | 11 + mobile/openapi/doc/MemoryApi.md | 406 +++++++++++++++++ mobile/openapi/doc/MemoryCreateDto.md | 20 + mobile/openapi/doc/MemoryResponseDto.md | 25 ++ mobile/openapi/doc/MemoryType.md | 14 + mobile/openapi/doc/MemoryUpdateDto.md | 17 + mobile/openapi/lib/api.dart | 5 + mobile/openapi/lib/api/memory_api.dart | 359 +++++++++++++++ mobile/openapi/lib/api_client.dart | 8 + mobile/openapi/lib/api_helper.dart | 3 + .../openapi/lib/model/memory_create_dto.dart | 157 +++++++ .../lib/model/memory_response_dto.dart | 267 +++++++++++ mobile/openapi/lib/model/memory_type.dart | 82 ++++ .../openapi/lib/model/memory_update_dto.dart | 141 ++++++ mobile/openapi/test/memory_api_test.dart | 56 +++ .../openapi/test/memory_create_dto_test.dart | 52 +++ .../test/memory_response_dto_test.dart | 77 ++++ mobile/openapi/test/memory_type_test.dart | 21 + .../openapi/test/memory_update_dto_test.dart | 37 ++ open-api/immich-openapi-specs.json | 424 ++++++++++++++++++ open-api/typescript-sdk/src/fetch-client.ts | 109 +++++ server/src/controllers/index.ts | 2 + server/src/controllers/memory.controller.ts | 64 +++ server/src/cores/access.core.ts | 16 + server/src/dtos/memory.dto.ts | 84 ++++ server/src/entities/index.ts | 2 + server/src/entities/memory.entity.ts | 67 +++ server/src/interfaces/access.interface.ts | 4 + server/src/interfaces/memory.interface.ts | 14 + .../1711637874206-AddMemoryTable.ts | 26 ++ server/src/queries/access.repository.sql | 14 + server/src/queries/memory.repository.sql | 18 + server/src/repositories/access.repository.ts | 27 ++ server/src/repositories/index.ts | 3 + server/src/repositories/memory.repository.ts | 104 +++++ server/src/services/index.ts | 2 + server/src/services/memory.service.spec.ts | 214 +++++++++ server/src/services/memory.service.ts | 105 +++++ server/test/fixtures/memory.stub.ts | 30 ++ .../repositories/access.repository.mock.ts | 5 + .../repositories/memory.repository.mock.ts | 14 + 43 files changed, 3497 insertions(+) create mode 100644 e2e/src/api/specs/memory.e2e-spec.ts create mode 100644 mobile/openapi/doc/MemoryApi.md create mode 100644 mobile/openapi/doc/MemoryCreateDto.md create mode 100644 mobile/openapi/doc/MemoryResponseDto.md create mode 100644 mobile/openapi/doc/MemoryType.md create mode 100644 mobile/openapi/doc/MemoryUpdateDto.md create mode 100644 mobile/openapi/lib/api/memory_api.dart create mode 100644 mobile/openapi/lib/model/memory_create_dto.dart create mode 100644 mobile/openapi/lib/model/memory_response_dto.dart create mode 100644 mobile/openapi/lib/model/memory_type.dart create mode 100644 mobile/openapi/lib/model/memory_update_dto.dart create mode 100644 mobile/openapi/test/memory_api_test.dart create mode 100644 mobile/openapi/test/memory_create_dto_test.dart create mode 100644 mobile/openapi/test/memory_response_dto_test.dart create mode 100644 mobile/openapi/test/memory_type_test.dart create mode 100644 mobile/openapi/test/memory_update_dto_test.dart create mode 100644 server/src/controllers/memory.controller.ts create mode 100644 server/src/dtos/memory.dto.ts create mode 100644 server/src/entities/memory.entity.ts create mode 100644 server/src/interfaces/memory.interface.ts create mode 100644 server/src/migrations/1711637874206-AddMemoryTable.ts create mode 100644 server/src/queries/memory.repository.sql create mode 100644 server/src/repositories/memory.repository.ts create mode 100644 server/src/services/memory.service.spec.ts create mode 100644 server/src/services/memory.service.ts create mode 100644 server/test/fixtures/memory.stub.ts create mode 100644 server/test/repositories/memory.repository.mock.ts diff --git a/e2e/src/api/specs/memory.e2e-spec.ts b/e2e/src/api/specs/memory.e2e-spec.ts new file mode 100644 index 000000000..35af1fea9 --- /dev/null +++ b/e2e/src/api/specs/memory.e2e-spec.ts @@ -0,0 +1,376 @@ +import { + AssetFileUploadResponseDto, + LoginResponseDto, + MemoryResponseDto, + MemoryType, + createMemory, + getMemory, +} from '@immich/sdk'; +import { createUserDto, uuidDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; +import { app, asBearerAuth, utils } from 'src/utils'; +import request from 'supertest'; +import { beforeAll, describe, expect, it } from 'vitest'; + +describe('/memories', () => { + let admin: LoginResponseDto; + let user: LoginResponseDto; + let adminAsset: AssetFileUploadResponseDto; + let userAsset1: AssetFileUploadResponseDto; + let userAsset2: AssetFileUploadResponseDto; + let userMemory: MemoryResponseDto; + + beforeAll(async () => { + await utils.resetDatabase(); + + admin = await utils.adminSetup(); + user = await utils.userSetup(admin.accessToken, createUserDto.user1); + [adminAsset, userAsset1, userAsset2] = await Promise.all([ + utils.createAsset(admin.accessToken), + utils.createAsset(user.accessToken), + utils.createAsset(user.accessToken), + ]); + userMemory = await createMemory( + { + memoryCreateDto: { + type: MemoryType.OnThisDay, + memoryAt: new Date(2021).toISOString(), + data: { year: 2021 }, + assetIds: [], + }, + }, + { headers: asBearerAuth(user.accessToken) }, + ); + }); + + describe('GET /memories', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/memories'); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + }); + + describe('POST /memories', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post('/memories'); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should validate data when type is on this day', async () => { + const { status, body } = await request(app) + .post('/memories') + .set('Authorization', `Bearer ${user.accessToken}`) + .send({ + type: 'on_this_day', + data: {}, + memoryAt: new Date(2021).toISOString(), + }); + + expect(status).toBe(400); + expect(body).toEqual( + errorDto.badRequest(['data.year must be a positive number', 'data.year must be an integer number']), + ); + }); + + it('should create a new memory', async () => { + const { status, body } = await request(app) + .post('/memories') + .set('Authorization', `Bearer ${user.accessToken}`) + .send({ + type: 'on_this_day', + data: { year: 2021 }, + memoryAt: new Date(2021).toISOString(), + }); + + expect(status).toBe(201); + expect(body).toEqual({ + id: expect.any(String), + type: 'on_this_day', + data: { year: 2021 }, + createdAt: expect.any(String), + updatedAt: expect.any(String), + deletedAt: null, + seenAt: null, + isSaved: false, + memoryAt: expect.any(String), + ownerId: user.userId, + assets: [], + }); + }); + + it('should create a new memory (with assets)', async () => { + const { status, body } = await request(app) + .post('/memories') + .set('Authorization', `Bearer ${user.accessToken}`) + .send({ + type: 'on_this_day', + data: { year: 2021 }, + memoryAt: new Date(2021).toISOString(), + assetIds: [userAsset1.id, userAsset2.id], + }); + + expect(status).toBe(201); + expect(body).toMatchObject({ + id: expect.any(String), + assets: expect.arrayContaining([ + expect.objectContaining({ id: userAsset1.id }), + expect.objectContaining({ id: userAsset2.id }), + ]), + }); + expect(body.assets).toHaveLength(2); + }); + + it('should create a new memory and ignore assets the user does not have access to', async () => { + const { status, body } = await request(app) + .post('/memories') + .set('Authorization', `Bearer ${user.accessToken}`) + .send({ + type: 'on_this_day', + data: { year: 2021 }, + memoryAt: new Date(2021).toISOString(), + assetIds: [userAsset1.id, adminAsset.id], + }); + + expect(status).toBe(201); + expect(body).toMatchObject({ + id: expect.any(String), + assets: [expect.objectContaining({ id: userAsset1.id })], + }); + expect(body.assets).toHaveLength(1); + }); + }); + + describe('GET /memories/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get(`/memories/${uuidDto.invalid}`); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require a valid id', async () => { + const { status, body } = await request(app) + .get(`/memories/${uuidDto.invalid}`) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); + }); + + it('should require access', async () => { + const { status, body } = await request(app) + .get(`/memories/${userMemory.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should get the memory', async () => { + const { status, body } = await request(app) + .get(`/memories/${userMemory.id}`) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(200); + expect(body).toMatchObject({ id: userMemory.id }); + }); + }); + + describe('PUT /memories/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).put(`/memories/${uuidDto.invalid}`).send({ isSaved: true }); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require a valid id', async () => { + const { status, body } = await request(app) + .put(`/memories/${uuidDto.invalid}`) + .send({ isSaved: true }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); + }); + + it('should require access', async () => { + const { status, body } = await request(app) + .put(`/memories/${userMemory.id}`) + .send({ isSaved: true }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should update the memory', async () => { + const before = await getMemory({ id: userMemory.id }, { headers: asBearerAuth(user.accessToken) }); + expect(before.isSaved).toBe(false); + + const { status, body } = await request(app) + .put(`/memories/${userMemory.id}`) + .send({ isSaved: true }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(200); + expect(body).toMatchObject({ + id: userMemory.id, + isSaved: true, + }); + }); + }); + + describe('PUT /memories/:id/assets', () => { + it('should require authentication', async () => { + const { status, body } = await request(app) + .put(`/memories/${userMemory.id}/assets`) + .send({ ids: [userAsset1.id] }); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require a valid id', async () => { + const { status, body } = await request(app) + .put(`/memories/${uuidDto.invalid}/assets`) + .send({ ids: [userAsset1.id] }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); + }); + + it('should require access', async () => { + const { status, body } = await request(app) + .put(`/memories/${userMemory.id}/assets`) + .send({ ids: [userAsset1.id] }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should require a valid asset id', async () => { + const { status, body } = await request(app) + .put(`/memories/${userMemory.id}/assets`) + .send({ ids: [uuidDto.invalid] }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['each value in ids must be a UUID'])); + }); + + it('should require asset access', async () => { + const { status, body } = await request(app) + .put(`/memories/${userMemory.id}/assets`) + .send({ ids: [adminAsset.id] }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(200); + expect(body).toHaveLength(1); + expect(body[0]).toEqual({ + id: adminAsset.id, + success: false, + error: 'no_permission', + }); + }); + + it('should add assets to the memory', async () => { + const { status, body } = await request(app) + .put(`/memories/${userMemory.id}/assets`) + .send({ ids: [userAsset1.id] }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(200); + expect(body).toHaveLength(1); + expect(body[0]).toEqual({ id: userAsset1.id, success: true }); + }); + }); + + describe('DELETE /memories/:id/assets', () => { + it('should require authentication', async () => { + const { status, body } = await request(app) + .delete(`/memories/${userMemory.id}/assets`) + .send({ ids: [userAsset1.id] }); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require a valid id', async () => { + const { status, body } = await request(app) + .delete(`/memories/${uuidDto.invalid}/assets`) + .send({ ids: [userAsset1.id] }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); + }); + + it('should require access', async () => { + const { status, body } = await request(app) + .delete(`/memories/${userMemory.id}/assets`) + .send({ ids: [userAsset1.id] }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should require a valid asset id', async () => { + const { status, body } = await request(app) + .delete(`/memories/${userMemory.id}/assets`) + .send({ ids: [uuidDto.invalid] }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['each value in ids must be a UUID'])); + }); + + it('should only remove assets in the memory', async () => { + const { status, body } = await request(app) + .delete(`/memories/${userMemory.id}/assets`) + .send({ ids: [adminAsset.id] }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(200); + expect(body).toHaveLength(1); + expect(body[0]).toEqual({ + id: adminAsset.id, + success: false, + error: 'not_found', + }); + }); + + it('should remove assets from the memory', async () => { + const { status, body } = await request(app) + .delete(`/memories/${userMemory.id}/assets`) + .send({ ids: [userAsset1.id] }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(200); + expect(body).toHaveLength(1); + expect(body[0]).toEqual({ id: userAsset1.id, success: true }); + }); + }); + + describe('DELETE /memories/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).delete(`/memories/${uuidDto.invalid}`); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require a valid id', async () => { + const { status, body } = await request(app) + .delete(`/memories/${uuidDto.invalid}`) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); + }); + + it('should require access', async () => { + const { status, body } = await request(app) + .delete(`/memories/${userMemory.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should delete the memory', async () => { + const { status } = await request(app) + .delete(`/memories/${userMemory.id}`) + .send({ isSaved: true }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(204); + }); + }); +}); diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 795943e29..4e109c14d 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -90,7 +90,12 @@ doc/LoginResponseDto.md doc/LogoutResponseDto.md doc/MapMarkerResponseDto.md doc/MapTheme.md +doc/MemoryApi.md +doc/MemoryCreateDto.md doc/MemoryLaneResponseDto.md +doc/MemoryResponseDto.md +doc/MemoryType.md +doc/MemoryUpdateDto.md doc/MergePersonDto.md doc/MetadataSearchDto.md doc/ModelType.md @@ -205,6 +210,7 @@ lib/api/download_api.dart lib/api/face_api.dart lib/api/job_api.dart lib/api/library_api.dart +lib/api/memory_api.dart lib/api/o_auth_api.dart lib/api/partner_api.dart lib/api/person_api.dart @@ -301,7 +307,11 @@ lib/model/login_response_dto.dart lib/model/logout_response_dto.dart lib/model/map_marker_response_dto.dart lib/model/map_theme.dart +lib/model/memory_create_dto.dart lib/model/memory_lane_response_dto.dart +lib/model/memory_response_dto.dart +lib/model/memory_type.dart +lib/model/memory_update_dto.dart lib/model/merge_person_dto.dart lib/model/metadata_search_dto.dart lib/model/model_type.dart @@ -481,7 +491,12 @@ test/login_response_dto_test.dart test/logout_response_dto_test.dart test/map_marker_response_dto_test.dart test/map_theme_test.dart +test/memory_api_test.dart +test/memory_create_dto_test.dart test/memory_lane_response_dto_test.dart +test/memory_response_dto_test.dart +test/memory_type_test.dart +test/memory_update_dto_test.dart test/merge_person_dto_test.dart test/metadata_search_dto_test.dart test/model_type_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index a46f2383e..fede2901c 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -140,6 +140,13 @@ Class | Method | HTTP request | Description *LibraryApi* | [**scanLibrary**](doc//LibraryApi.md#scanlibrary) | **POST** /library/{id}/scan | *LibraryApi* | [**updateLibrary**](doc//LibraryApi.md#updatelibrary) | **PUT** /library/{id} | *LibraryApi* | [**validate**](doc//LibraryApi.md#validate) | **POST** /library/{id}/validate | +*MemoryApi* | [**addMemoryAssets**](doc//MemoryApi.md#addmemoryassets) | **PUT** /memories/{id}/assets | +*MemoryApi* | [**createMemory**](doc//MemoryApi.md#creatememory) | **POST** /memories | +*MemoryApi* | [**deleteMemory**](doc//MemoryApi.md#deletememory) | **DELETE** /memories/{id} | +*MemoryApi* | [**getMemory**](doc//MemoryApi.md#getmemory) | **GET** /memories/{id} | +*MemoryApi* | [**removeMemoryAssets**](doc//MemoryApi.md#removememoryassets) | **DELETE** /memories/{id}/assets | +*MemoryApi* | [**searchMemories**](doc//MemoryApi.md#searchmemories) | **GET** /memories | +*MemoryApi* | [**updateMemory**](doc//MemoryApi.md#updatememory) | **PUT** /memories/{id} | *OAuthApi* | [**finishOAuth**](doc//OAuthApi.md#finishoauth) | **POST** /oauth/callback | *OAuthApi* | [**linkOAuthAccount**](doc//OAuthApi.md#linkoauthaccount) | **POST** /oauth/link | *OAuthApi* | [**redirectOAuthToMobile**](doc//OAuthApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect | @@ -293,7 +300,11 @@ Class | Method | HTTP request | Description - [LogoutResponseDto](doc//LogoutResponseDto.md) - [MapMarkerResponseDto](doc//MapMarkerResponseDto.md) - [MapTheme](doc//MapTheme.md) + - [MemoryCreateDto](doc//MemoryCreateDto.md) - [MemoryLaneResponseDto](doc//MemoryLaneResponseDto.md) + - [MemoryResponseDto](doc//MemoryResponseDto.md) + - [MemoryType](doc//MemoryType.md) + - [MemoryUpdateDto](doc//MemoryUpdateDto.md) - [MergePersonDto](doc//MergePersonDto.md) - [MetadataSearchDto](doc//MetadataSearchDto.md) - [ModelType](doc//ModelType.md) diff --git a/mobile/openapi/doc/MemoryApi.md b/mobile/openapi/doc/MemoryApi.md new file mode 100644 index 000000000..5795669a5 --- /dev/null +++ b/mobile/openapi/doc/MemoryApi.md @@ -0,0 +1,406 @@ +# openapi.api.MemoryApi + +## Load the API package +```dart +import 'package:openapi/api.dart'; +``` + +All URIs are relative to */api* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**addMemoryAssets**](MemoryApi.md#addmemoryassets) | **PUT** /memories/{id}/assets | +[**createMemory**](MemoryApi.md#creatememory) | **POST** /memories | +[**deleteMemory**](MemoryApi.md#deletememory) | **DELETE** /memories/{id} | +[**getMemory**](MemoryApi.md#getmemory) | **GET** /memories/{id} | +[**removeMemoryAssets**](MemoryApi.md#removememoryassets) | **DELETE** /memories/{id}/assets | +[**searchMemories**](MemoryApi.md#searchmemories) | **GET** /memories | +[**updateMemory**](MemoryApi.md#updatememory) | **PUT** /memories/{id} | + + +# **addMemoryAssets** +> List addMemoryAssets(id, bulkIdsDto) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = MemoryApi(); +final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | +final bulkIdsDto = BulkIdsDto(); // BulkIdsDto | + +try { + final result = api_instance.addMemoryAssets(id, bulkIdsDto); + print(result); +} catch (e) { + print('Exception when calling MemoryApi->addMemoryAssets: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **id** | **String**| | + **bulkIdsDto** | [**BulkIdsDto**](BulkIdsDto.md)| | + +### Return type + +[**List**](BulkIdResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **createMemory** +> MemoryResponseDto createMemory(memoryCreateDto) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = MemoryApi(); +final memoryCreateDto = MemoryCreateDto(); // MemoryCreateDto | + +try { + final result = api_instance.createMemory(memoryCreateDto); + print(result); +} catch (e) { + print('Exception when calling MemoryApi->createMemory: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **memoryCreateDto** | [**MemoryCreateDto**](MemoryCreateDto.md)| | + +### Return type + +[**MemoryResponseDto**](MemoryResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **deleteMemory** +> deleteMemory(id) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = MemoryApi(); +final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | + +try { + api_instance.deleteMemory(id); +} catch (e) { + print('Exception when calling MemoryApi->deleteMemory: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **id** | **String**| | + +### Return type + +void (empty response body) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **getMemory** +> MemoryResponseDto getMemory(id) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = MemoryApi(); +final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | + +try { + final result = api_instance.getMemory(id); + print(result); +} catch (e) { + print('Exception when calling MemoryApi->getMemory: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **id** | **String**| | + +### Return type + +[**MemoryResponseDto**](MemoryResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **removeMemoryAssets** +> List removeMemoryAssets(id, bulkIdsDto) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = MemoryApi(); +final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | +final bulkIdsDto = BulkIdsDto(); // BulkIdsDto | + +try { + final result = api_instance.removeMemoryAssets(id, bulkIdsDto); + print(result); +} catch (e) { + print('Exception when calling MemoryApi->removeMemoryAssets: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **id** | **String**| | + **bulkIdsDto** | [**BulkIdsDto**](BulkIdsDto.md)| | + +### Return type + +[**List**](BulkIdResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **searchMemories** +> List searchMemories() + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = MemoryApi(); + +try { + final result = api_instance.searchMemories(); + print(result); +} catch (e) { + print('Exception when calling MemoryApi->searchMemories: $e\n'); +} +``` + +### Parameters +This endpoint does not need any parameter. + +### Return type + +[**List**](MemoryResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **updateMemory** +> MemoryResponseDto updateMemory(id, memoryUpdateDto) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = MemoryApi(); +final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | +final memoryUpdateDto = MemoryUpdateDto(); // MemoryUpdateDto | + +try { + final result = api_instance.updateMemory(id, memoryUpdateDto); + print(result); +} catch (e) { + print('Exception when calling MemoryApi->updateMemory: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **id** | **String**| | + **memoryUpdateDto** | [**MemoryUpdateDto**](MemoryUpdateDto.md)| | + +### Return type + +[**MemoryResponseDto**](MemoryResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/mobile/openapi/doc/MemoryCreateDto.md b/mobile/openapi/doc/MemoryCreateDto.md new file mode 100644 index 000000000..5bcbd54f4 --- /dev/null +++ b/mobile/openapi/doc/MemoryCreateDto.md @@ -0,0 +1,20 @@ +# openapi.model.MemoryCreateDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**assetIds** | **List** | | [optional] [default to const []] +**data** | [**Object**](.md) | | +**isSaved** | **bool** | | [optional] +**memoryAt** | [**DateTime**](DateTime.md) | | +**seenAt** | [**DateTime**](DateTime.md) | | [optional] +**type** | [**MemoryType**](MemoryType.md) | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/doc/MemoryResponseDto.md b/mobile/openapi/doc/MemoryResponseDto.md new file mode 100644 index 000000000..ef379be04 --- /dev/null +++ b/mobile/openapi/doc/MemoryResponseDto.md @@ -0,0 +1,25 @@ +# openapi.model.MemoryResponseDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**assets** | [**List**](AssetResponseDto.md) | | [default to const []] +**createdAt** | [**DateTime**](DateTime.md) | | +**data** | [**Object**](.md) | | +**deletedAt** | [**DateTime**](DateTime.md) | | [optional] +**id** | **String** | | +**isSaved** | **bool** | | +**memoryAt** | [**DateTime**](DateTime.md) | | +**ownerId** | **String** | | +**seenAt** | [**DateTime**](DateTime.md) | | [optional] +**type** | **String** | | +**updatedAt** | [**DateTime**](DateTime.md) | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/doc/MemoryType.md b/mobile/openapi/doc/MemoryType.md new file mode 100644 index 000000000..c8dea25be --- /dev/null +++ b/mobile/openapi/doc/MemoryType.md @@ -0,0 +1,14 @@ +# openapi.model.MemoryType + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/doc/MemoryUpdateDto.md b/mobile/openapi/doc/MemoryUpdateDto.md new file mode 100644 index 000000000..7a48e84e8 --- /dev/null +++ b/mobile/openapi/doc/MemoryUpdateDto.md @@ -0,0 +1,17 @@ +# openapi.model.MemoryUpdateDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**isSaved** | **bool** | | [optional] +**memoryAt** | [**DateTime**](DateTime.md) | | [optional] +**seenAt** | [**DateTime**](DateTime.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 1600dfb33..7d8ab5288 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -39,6 +39,7 @@ part 'api/download_api.dart'; part 'api/face_api.dart'; part 'api/job_api.dart'; part 'api/library_api.dart'; +part 'api/memory_api.dart'; part 'api/o_auth_api.dart'; part 'api/partner_api.dart'; part 'api/person_api.dart'; @@ -128,7 +129,11 @@ part 'model/login_response_dto.dart'; part 'model/logout_response_dto.dart'; part 'model/map_marker_response_dto.dart'; part 'model/map_theme.dart'; +part 'model/memory_create_dto.dart'; part 'model/memory_lane_response_dto.dart'; +part 'model/memory_response_dto.dart'; +part 'model/memory_type.dart'; +part 'model/memory_update_dto.dart'; part 'model/merge_person_dto.dart'; part 'model/metadata_search_dto.dart'; part 'model/model_type.dart'; diff --git a/mobile/openapi/lib/api/memory_api.dart b/mobile/openapi/lib/api/memory_api.dart new file mode 100644 index 000000000..6b4a619b5 --- /dev/null +++ b/mobile/openapi/lib/api/memory_api.dart @@ -0,0 +1,359 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 MemoryApi { + MemoryApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Performs an HTTP 'PUT /memories/{id}/assets' operation and returns the [Response]. + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future addMemoryAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { + // ignore: prefer_const_declarations + final path = r'/memories/{id}/assets' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = bulkIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + path, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future?> addMemoryAssets(String id, BulkIdsDto bulkIdsDto,) async { + final response = await addMemoryAssetsWithHttpInfo(id, bulkIdsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Performs an HTTP 'POST /memories' operation and returns the [Response]. + /// Parameters: + /// + /// * [MemoryCreateDto] memoryCreateDto (required): + Future createMemoryWithHttpInfo(MemoryCreateDto memoryCreateDto,) async { + // ignore: prefer_const_declarations + final path = r'/memories'; + + // ignore: prefer_final_locals + Object? postBody = memoryCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + path, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [MemoryCreateDto] memoryCreateDto (required): + Future createMemory(MemoryCreateDto memoryCreateDto,) async { + final response = await createMemoryWithHttpInfo(memoryCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MemoryResponseDto',) as MemoryResponseDto; + + } + return null; + } + + /// Performs an HTTP 'DELETE /memories/{id}' operation and returns the [Response]. + /// Parameters: + /// + /// * [String] id (required): + Future deleteMemoryWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final path = r'/memories/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [String] id (required): + Future deleteMemory(String id,) async { + final response = await deleteMemoryWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Performs an HTTP 'GET /memories/{id}' operation and returns the [Response]. + /// Parameters: + /// + /// * [String] id (required): + Future getMemoryWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final path = r'/memories/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [String] id (required): + Future getMemory(String id,) async { + final response = await getMemoryWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MemoryResponseDto',) as MemoryResponseDto; + + } + return null; + } + + /// Performs an HTTP 'DELETE /memories/{id}/assets' operation and returns the [Response]. + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future removeMemoryAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { + // ignore: prefer_const_declarations + final path = r'/memories/{id}/assets' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = bulkIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + path, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future?> removeMemoryAssets(String id, BulkIdsDto bulkIdsDto,) async { + final response = await removeMemoryAssetsWithHttpInfo(id, bulkIdsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Performs an HTTP 'GET /memories' operation and returns the [Response]. + Future searchMemoriesWithHttpInfo() async { + // ignore: prefer_const_declarations + final path = r'/memories'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + Future?> searchMemories() async { + final response = await searchMemoriesWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Performs an HTTP 'PUT /memories/{id}' operation and returns the [Response]. + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [MemoryUpdateDto] memoryUpdateDto (required): + Future updateMemoryWithHttpInfo(String id, MemoryUpdateDto memoryUpdateDto,) async { + // ignore: prefer_const_declarations + final path = r'/memories/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = memoryUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + path, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [MemoryUpdateDto] memoryUpdateDto (required): + Future updateMemory(String id, MemoryUpdateDto memoryUpdateDto,) async { + final response = await updateMemoryWithHttpInfo(id, memoryUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MemoryResponseDto',) as MemoryResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 4a145d0c4..8784ad641 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -336,8 +336,16 @@ class ApiClient { return MapMarkerResponseDto.fromJson(value); case 'MapTheme': return MapThemeTypeTransformer().decode(value); + case 'MemoryCreateDto': + return MemoryCreateDto.fromJson(value); case 'MemoryLaneResponseDto': return MemoryLaneResponseDto.fromJson(value); + case 'MemoryResponseDto': + return MemoryResponseDto.fromJson(value); + case 'MemoryType': + return MemoryTypeTypeTransformer().decode(value); + case 'MemoryUpdateDto': + return MemoryUpdateDto.fromJson(value); case 'MergePersonDto': return MergePersonDto.fromJson(value); case 'MetadataSearchDto': diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index 9d2d86cba..7ad74d951 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -97,6 +97,9 @@ String parameterToString(dynamic value) { if (value is MapTheme) { return MapThemeTypeTransformer().encode(value).toString(); } + if (value is MemoryType) { + return MemoryTypeTypeTransformer().encode(value).toString(); + } if (value is ModelType) { return ModelTypeTypeTransformer().encode(value).toString(); } diff --git a/mobile/openapi/lib/model/memory_create_dto.dart b/mobile/openapi/lib/model/memory_create_dto.dart new file mode 100644 index 000000000..5d08a631c --- /dev/null +++ b/mobile/openapi/lib/model/memory_create_dto.dart @@ -0,0 +1,157 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 MemoryCreateDto { + /// Returns a new [MemoryCreateDto] instance. + MemoryCreateDto({ + this.assetIds = const [], + required this.data, + this.isSaved, + required this.memoryAt, + this.seenAt, + required this.type, + }); + + List assetIds; + + Object data; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isSaved; + + DateTime memoryAt; + + /// + /// 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. + /// + DateTime? seenAt; + + MemoryType type; + + @override + bool operator ==(Object other) => identical(this, other) || other is MemoryCreateDto && + _deepEquality.equals(other.assetIds, assetIds) && + other.data == data && + other.isSaved == isSaved && + other.memoryAt == memoryAt && + other.seenAt == seenAt && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetIds.hashCode) + + (data.hashCode) + + (isSaved == null ? 0 : isSaved!.hashCode) + + (memoryAt.hashCode) + + (seenAt == null ? 0 : seenAt!.hashCode) + + (type.hashCode); + + @override + String toString() => 'MemoryCreateDto[assetIds=$assetIds, data=$data, isSaved=$isSaved, memoryAt=$memoryAt, seenAt=$seenAt, type=$type]'; + + Map toJson() { + final json = {}; + json[r'assetIds'] = this.assetIds; + json[r'data'] = this.data; + if (this.isSaved != null) { + json[r'isSaved'] = this.isSaved; + } else { + // json[r'isSaved'] = null; + } + json[r'memoryAt'] = this.memoryAt.toUtc().toIso8601String(); + if (this.seenAt != null) { + json[r'seenAt'] = this.seenAt!.toUtc().toIso8601String(); + } else { + // json[r'seenAt'] = null; + } + json[r'type'] = this.type; + return json; + } + + /// Returns a new [MemoryCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MemoryCreateDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return MemoryCreateDto( + assetIds: json[r'assetIds'] is Iterable + ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) + : const [], + data: mapValueOfType(json, r'data')!, + isSaved: mapValueOfType(json, r'isSaved'), + memoryAt: mapDateTime(json, r'memoryAt', r'')!, + seenAt: mapDateTime(json, r'seenAt', r''), + type: MemoryType.fromJson(json[r'type'])!, + ); + } + 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 = MemoryCreateDto.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 = MemoryCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MemoryCreateDto-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] = MemoryCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'data', + 'memoryAt', + 'type', + }; +} + diff --git a/mobile/openapi/lib/model/memory_response_dto.dart b/mobile/openapi/lib/model/memory_response_dto.dart new file mode 100644 index 000000000..918099458 --- /dev/null +++ b/mobile/openapi/lib/model/memory_response_dto.dart @@ -0,0 +1,267 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 MemoryResponseDto { + /// Returns a new [MemoryResponseDto] instance. + MemoryResponseDto({ + this.assets = const [], + required this.createdAt, + required this.data, + this.deletedAt, + required this.id, + required this.isSaved, + required this.memoryAt, + required this.ownerId, + this.seenAt, + required this.type, + required this.updatedAt, + }); + + List assets; + + DateTime createdAt; + + Object data; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? deletedAt; + + String id; + + bool isSaved; + + DateTime memoryAt; + + String ownerId; + + /// + /// 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. + /// + DateTime? seenAt; + + MemoryResponseDtoTypeEnum type; + + DateTime updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is MemoryResponseDto && + _deepEquality.equals(other.assets, assets) && + other.createdAt == createdAt && + other.data == data && + other.deletedAt == deletedAt && + other.id == id && + other.isSaved == isSaved && + other.memoryAt == memoryAt && + other.ownerId == ownerId && + other.seenAt == seenAt && + other.type == type && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assets.hashCode) + + (createdAt.hashCode) + + (data.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (id.hashCode) + + (isSaved.hashCode) + + (memoryAt.hashCode) + + (ownerId.hashCode) + + (seenAt == null ? 0 : seenAt!.hashCode) + + (type.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'MemoryResponseDto[assets=$assets, createdAt=$createdAt, data=$data, deletedAt=$deletedAt, id=$id, isSaved=$isSaved, memoryAt=$memoryAt, ownerId=$ownerId, seenAt=$seenAt, type=$type, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + json[r'assets'] = this.assets; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + json[r'data'] = this.data; + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); + } else { + // json[r'deletedAt'] = null; + } + json[r'id'] = this.id; + json[r'isSaved'] = this.isSaved; + json[r'memoryAt'] = this.memoryAt.toUtc().toIso8601String(); + json[r'ownerId'] = this.ownerId; + if (this.seenAt != null) { + json[r'seenAt'] = this.seenAt!.toUtc().toIso8601String(); + } else { + // json[r'seenAt'] = null; + } + json[r'type'] = this.type; + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [MemoryResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MemoryResponseDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return MemoryResponseDto( + assets: AssetResponseDto.listFromJson(json[r'assets']), + createdAt: mapDateTime(json, r'createdAt', r'')!, + data: mapValueOfType(json, r'data')!, + deletedAt: mapDateTime(json, r'deletedAt', r''), + id: mapValueOfType(json, r'id')!, + isSaved: mapValueOfType(json, r'isSaved')!, + memoryAt: mapDateTime(json, r'memoryAt', r'')!, + ownerId: mapValueOfType(json, r'ownerId')!, + seenAt: mapDateTime(json, r'seenAt', r''), + type: MemoryResponseDtoTypeEnum.fromJson(json[r'type'])!, + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + ); + } + 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 = MemoryResponseDto.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 = MemoryResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MemoryResponseDto-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] = MemoryResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assets', + 'createdAt', + 'data', + 'id', + 'isSaved', + 'memoryAt', + 'ownerId', + 'type', + 'updatedAt', + }; +} + + +class MemoryResponseDtoTypeEnum { + /// Instantiate a new enum with the provided [value]. + const MemoryResponseDtoTypeEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const onThisDay = MemoryResponseDtoTypeEnum._(r'on_this_day'); + + /// List of all possible values in this [enum][MemoryResponseDtoTypeEnum]. + static const values = [ + onThisDay, + ]; + + static MemoryResponseDtoTypeEnum? fromJson(dynamic value) => MemoryResponseDtoTypeEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MemoryResponseDtoTypeEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [MemoryResponseDtoTypeEnum] to String, +/// and [decode] dynamic data back to [MemoryResponseDtoTypeEnum]. +class MemoryResponseDtoTypeEnumTypeTransformer { + factory MemoryResponseDtoTypeEnumTypeTransformer() => _instance ??= const MemoryResponseDtoTypeEnumTypeTransformer._(); + + const MemoryResponseDtoTypeEnumTypeTransformer._(); + + String encode(MemoryResponseDtoTypeEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a MemoryResponseDtoTypeEnum. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + MemoryResponseDtoTypeEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'on_this_day': return MemoryResponseDtoTypeEnum.onThisDay; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [MemoryResponseDtoTypeEnumTypeTransformer] instance. + static MemoryResponseDtoTypeEnumTypeTransformer? _instance; +} + + diff --git a/mobile/openapi/lib/model/memory_type.dart b/mobile/openapi/lib/model/memory_type.dart new file mode 100644 index 000000000..513b7c2d4 --- /dev/null +++ b/mobile/openapi/lib/model/memory_type.dart @@ -0,0 +1,82 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 MemoryType { + /// Instantiate a new enum with the provided [value]. + const MemoryType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const onThisDay = MemoryType._(r'on_this_day'); + + /// List of all possible values in this [enum][MemoryType]. + static const values = [ + onThisDay, + ]; + + static MemoryType? fromJson(dynamic value) => MemoryTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MemoryType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [MemoryType] to String, +/// and [decode] dynamic data back to [MemoryType]. +class MemoryTypeTypeTransformer { + factory MemoryTypeTypeTransformer() => _instance ??= const MemoryTypeTypeTransformer._(); + + const MemoryTypeTypeTransformer._(); + + String encode(MemoryType data) => data.value; + + /// Decodes a [dynamic value][data] to a MemoryType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + MemoryType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'on_this_day': return MemoryType.onThisDay; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [MemoryTypeTypeTransformer] instance. + static MemoryTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/memory_update_dto.dart b/mobile/openapi/lib/model/memory_update_dto.dart new file mode 100644 index 000000000..adf42330d --- /dev/null +++ b/mobile/openapi/lib/model/memory_update_dto.dart @@ -0,0 +1,141 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 MemoryUpdateDto { + /// Returns a new [MemoryUpdateDto] instance. + MemoryUpdateDto({ + this.isSaved, + this.memoryAt, + this.seenAt, + }); + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isSaved; + + /// + /// 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. + /// + DateTime? memoryAt; + + /// + /// 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. + /// + DateTime? seenAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is MemoryUpdateDto && + other.isSaved == isSaved && + other.memoryAt == memoryAt && + other.seenAt == seenAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (isSaved == null ? 0 : isSaved!.hashCode) + + (memoryAt == null ? 0 : memoryAt!.hashCode) + + (seenAt == null ? 0 : seenAt!.hashCode); + + @override + String toString() => 'MemoryUpdateDto[isSaved=$isSaved, memoryAt=$memoryAt, seenAt=$seenAt]'; + + Map toJson() { + final json = {}; + if (this.isSaved != null) { + json[r'isSaved'] = this.isSaved; + } else { + // json[r'isSaved'] = null; + } + if (this.memoryAt != null) { + json[r'memoryAt'] = this.memoryAt!.toUtc().toIso8601String(); + } else { + // json[r'memoryAt'] = null; + } + if (this.seenAt != null) { + json[r'seenAt'] = this.seenAt!.toUtc().toIso8601String(); + } else { + // json[r'seenAt'] = null; + } + return json; + } + + /// Returns a new [MemoryUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MemoryUpdateDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return MemoryUpdateDto( + isSaved: mapValueOfType(json, r'isSaved'), + memoryAt: mapDateTime(json, r'memoryAt', r''), + seenAt: mapDateTime(json, r'seenAt', r''), + ); + } + 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 = MemoryUpdateDto.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 = MemoryUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MemoryUpdateDto-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] = MemoryUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/test/memory_api_test.dart b/mobile/openapi/test/memory_api_test.dart new file mode 100644 index 000000000..1a930782e --- /dev/null +++ b/mobile/openapi/test/memory_api_test.dart @@ -0,0 +1,56 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + + +/// tests for MemoryApi +void main() { + // final instance = MemoryApi(); + + group('tests for MemoryApi', () { + //Future> addMemoryAssets(String id, BulkIdsDto bulkIdsDto) async + test('test addMemoryAssets', () async { + // TODO + }); + + //Future createMemory(MemoryCreateDto memoryCreateDto) async + test('test createMemory', () async { + // TODO + }); + + //Future deleteMemory(String id) async + test('test deleteMemory', () async { + // TODO + }); + + //Future getMemory(String id) async + test('test getMemory', () async { + // TODO + }); + + //Future> removeMemoryAssets(String id, BulkIdsDto bulkIdsDto) async + test('test removeMemoryAssets', () async { + // TODO + }); + + //Future> searchMemories() async + test('test searchMemories', () async { + // TODO + }); + + //Future updateMemory(String id, MemoryUpdateDto memoryUpdateDto) async + test('test updateMemory', () async { + // TODO + }); + + }); +} diff --git a/mobile/openapi/test/memory_create_dto_test.dart b/mobile/openapi/test/memory_create_dto_test.dart new file mode 100644 index 000000000..f2909bd46 --- /dev/null +++ b/mobile/openapi/test/memory_create_dto_test.dart @@ -0,0 +1,52 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for MemoryCreateDto +void main() { + // final instance = MemoryCreateDto(); + + group('test MemoryCreateDto', () { + // List assetIds (default value: const []) + test('to test the property `assetIds`', () async { + // TODO + }); + + // Object data + test('to test the property `data`', () async { + // TODO + }); + + // bool isSaved + test('to test the property `isSaved`', () async { + // TODO + }); + + // DateTime memoryAt + test('to test the property `memoryAt`', () async { + // TODO + }); + + // DateTime seenAt + test('to test the property `seenAt`', () async { + // TODO + }); + + // MemoryType type + test('to test the property `type`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/memory_response_dto_test.dart b/mobile/openapi/test/memory_response_dto_test.dart new file mode 100644 index 000000000..da25bbb6e --- /dev/null +++ b/mobile/openapi/test/memory_response_dto_test.dart @@ -0,0 +1,77 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for MemoryResponseDto +void main() { + // final instance = MemoryResponseDto(); + + group('test MemoryResponseDto', () { + // List assets (default value: const []) + test('to test the property `assets`', () async { + // TODO + }); + + // DateTime createdAt + test('to test the property `createdAt`', () async { + // TODO + }); + + // Object data + test('to test the property `data`', () async { + // TODO + }); + + // DateTime deletedAt + test('to test the property `deletedAt`', () async { + // TODO + }); + + // String id + test('to test the property `id`', () async { + // TODO + }); + + // bool isSaved + test('to test the property `isSaved`', () async { + // TODO + }); + + // DateTime memoryAt + test('to test the property `memoryAt`', () async { + // TODO + }); + + // String ownerId + test('to test the property `ownerId`', () async { + // TODO + }); + + // DateTime seenAt + test('to test the property `seenAt`', () async { + // TODO + }); + + // String type + test('to test the property `type`', () async { + // TODO + }); + + // DateTime updatedAt + test('to test the property `updatedAt`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/memory_type_test.dart b/mobile/openapi/test/memory_type_test.dart new file mode 100644 index 000000000..0a6589d9a --- /dev/null +++ b/mobile/openapi/test/memory_type_test.dart @@ -0,0 +1,21 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for MemoryType +void main() { + + group('test MemoryType', () { + + }); + +} diff --git a/mobile/openapi/test/memory_update_dto_test.dart b/mobile/openapi/test/memory_update_dto_test.dart new file mode 100644 index 000000000..173128e39 --- /dev/null +++ b/mobile/openapi/test/memory_update_dto_test.dart @@ -0,0 +1,37 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for MemoryUpdateDto +void main() { + // final instance = MemoryUpdateDto(); + + group('test MemoryUpdateDto', () { + // bool isSaved + test('to test the property `isSaved`', () async { + // TODO + }); + + // DateTime memoryAt + test('to test the property `memoryAt`', () async { + // TODO + }); + + // DateTime seenAt + test('to test the property `seenAt`', () async { + // TODO + }); + + + }); + +} diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 16a9ad63a..da7bad8f0 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -3435,6 +3435,314 @@ ] } }, + "/memories": { + "get": { + "operationId": "searchMemories", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/MemoryResponseDto" + }, + "type": "array" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Memory" + ] + }, + "post": { + "operationId": "createMemory", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryCreateDto" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Memory" + ] + } + }, + "/memories/{id}": { + "delete": { + "operationId": "deleteMemory", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Memory" + ] + }, + "get": { + "operationId": "getMemory", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Memory" + ] + }, + "put": { + "operationId": "updateMemory", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryUpdateDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Memory" + ] + } + }, + "/memories/{id}/assets": { + "delete": { + "operationId": "removeMemoryAssets", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkIdsDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/BulkIdResponseDto" + }, + "type": "array" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Memory" + ] + }, + "put": { + "operationId": "addMemoryAssets", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkIdsDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/BulkIdResponseDto" + }, + "type": "array" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Memory" + ] + } + }, "/oauth/authorize": { "post": { "operationId": "startOAuth", @@ -8451,6 +8759,40 @@ ], "type": "string" }, + "MemoryCreateDto": { + "properties": { + "assetIds": { + "items": { + "format": "uuid", + "type": "string" + }, + "type": "array" + }, + "data": { + "type": "object" + }, + "isSaved": { + "type": "boolean" + }, + "memoryAt": { + "format": "date-time", + "type": "string" + }, + "seenAt": { + "format": "date-time", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/MemoryType" + } + }, + "required": [ + "data", + "memoryAt", + "type" + ], + "type": "object" + }, "MemoryLaneResponseDto": { "properties": { "assets": { @@ -8474,6 +8816,88 @@ ], "type": "object" }, + "MemoryResponseDto": { + "properties": { + "assets": { + "items": { + "$ref": "#/components/schemas/AssetResponseDto" + }, + "type": "array" + }, + "createdAt": { + "format": "date-time", + "type": "string" + }, + "data": { + "type": "object" + }, + "deletedAt": { + "format": "date-time", + "type": "string" + }, + "id": { + "type": "string" + }, + "isSaved": { + "type": "boolean" + }, + "memoryAt": { + "format": "date-time", + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "seenAt": { + "format": "date-time", + "type": "string" + }, + "type": { + "enum": [ + "on_this_day" + ], + "type": "string" + }, + "updatedAt": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "assets", + "createdAt", + "data", + "id", + "isSaved", + "memoryAt", + "ownerId", + "type", + "updatedAt" + ], + "type": "object" + }, + "MemoryType": { + "enum": [ + "on_this_day" + ], + "type": "string" + }, + "MemoryUpdateDto": { + "properties": { + "isSaved": { + "type": "boolean" + }, + "memoryAt": { + "format": "date-time", + "type": "string" + }, + "seenAt": { + "format": "date-time", + "type": "string" + } + }, + "type": "object" + }, "MergePersonDto": { "properties": { "ids": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index e63ccb4d6..1584a79cf 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -494,6 +494,32 @@ export type ValidateLibraryImportPathResponseDto = { export type ValidateLibraryResponseDto = { importPaths?: ValidateLibraryImportPathResponseDto[]; }; +export type MemoryResponseDto = { + assets: AssetResponseDto[]; + createdAt: string; + data: object; + deletedAt?: string; + id: string; + isSaved: boolean; + memoryAt: string; + ownerId: string; + seenAt?: string; + "type": Type2; + updatedAt: string; +}; +export type MemoryCreateDto = { + assetIds?: string[]; + data: object; + isSaved?: boolean; + memoryAt: string; + seenAt?: string; + "type": MemoryType; +}; +export type MemoryUpdateDto = { + isSaved?: boolean; + memoryAt?: string; + seenAt?: string; +}; export type OAuthConfigDto = { redirectUri: string; }; @@ -1908,6 +1934,83 @@ export function validate({ id, validateLibraryDto }: { body: validateLibraryDto }))); } +export function searchMemories(opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: MemoryResponseDto[]; + }>("/memories", { + ...opts + })); +} +export function createMemory({ memoryCreateDto }: { + memoryCreateDto: MemoryCreateDto; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 201; + data: MemoryResponseDto; + }>("/memories", oazapfts.json({ + ...opts, + method: "POST", + body: memoryCreateDto + }))); +} +export function deleteMemory({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchText(`/memories/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +export function getMemory({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: MemoryResponseDto; + }>(`/memories/${encodeURIComponent(id)}`, { + ...opts + })); +} +export function updateMemory({ id, memoryUpdateDto }: { + id: string; + memoryUpdateDto: MemoryUpdateDto; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: MemoryResponseDto; + }>(`/memories/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: memoryUpdateDto + }))); +} +export function removeMemoryAssets({ id, bulkIdsDto }: { + id: string; + bulkIdsDto: BulkIdsDto; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: BulkIdResponseDto[]; + }>(`/memories/${encodeURIComponent(id)}/assets`, oazapfts.json({ + ...opts, + method: "DELETE", + body: bulkIdsDto + }))); +} +export function addMemoryAssets({ id, bulkIdsDto }: { + id: string; + bulkIdsDto: BulkIdsDto; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: BulkIdResponseDto[]; + }>(`/memories/${encodeURIComponent(id)}/assets`, oazapfts.json({ + ...opts, + method: "PUT", + body: bulkIdsDto + }))); +} export function startOAuth({ oAuthConfigDto }: { oAuthConfigDto: OAuthConfigDto; }, opts?: Oazapfts.RequestOpts) { @@ -2842,6 +2945,12 @@ export enum LibraryType { Upload = "UPLOAD", External = "EXTERNAL" } +export enum Type2 { + OnThisDay = "on_this_day" +} +export enum MemoryType { + OnThisDay = "on_this_day" +} export enum SearchSuggestionType { Country = "country", State = "state", diff --git a/server/src/controllers/index.ts b/server/src/controllers/index.ts index 00cf7bbab..ce51aa4c0 100644 --- a/server/src/controllers/index.ts +++ b/server/src/controllers/index.ts @@ -10,6 +10,7 @@ import { DownloadController } from 'src/controllers/download.controller'; import { FaceController } from 'src/controllers/face.controller'; import { JobController } from 'src/controllers/job.controller'; import { LibraryController } from 'src/controllers/library.controller'; +import { MemoryController } from 'src/controllers/memory.controller'; import { OAuthController } from 'src/controllers/oauth.controller'; import { PartnerController } from 'src/controllers/partner.controller'; import { PersonController } from 'src/controllers/person.controller'; @@ -36,6 +37,7 @@ export const controllers = [ FaceController, JobController, LibraryController, + MemoryController, OAuthController, PartnerController, SearchController, diff --git a/server/src/controllers/memory.controller.ts b/server/src/controllers/memory.controller.ts new file mode 100644 index 000000000..771d70594 --- /dev/null +++ b/server/src/controllers/memory.controller.ts @@ -0,0 +1,64 @@ +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; +import { AuthDto } from 'src/dtos/auth.dto'; +import { MemoryCreateDto, MemoryResponseDto, MemoryUpdateDto } from 'src/dtos/memory.dto'; +import { Auth, Authenticated } from 'src/middleware/auth.guard'; +import { MemoryService } from 'src/services/memory.service'; +import { UUIDParamDto } from 'src/validation'; + +@ApiTags('Memory') +@Controller('memories') +@Authenticated() +export class MemoryController { + constructor(private service: MemoryService) {} + + @Get() + searchMemories(@Auth() auth: AuthDto): Promise { + return this.service.search(auth); + } + + @Post() + createMemory(@Auth() auth: AuthDto, @Body() dto: MemoryCreateDto): Promise { + return this.service.create(auth, dto); + } + + @Get(':id') + getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { + return this.service.get(auth, id); + } + + @Put(':id') + updateMemory( + @Auth() auth: AuthDto, + @Param() { id }: UUIDParamDto, + @Body() dto: MemoryUpdateDto, + ): Promise { + return this.service.update(auth, id, dto); + } + + @Delete(':id') + @HttpCode(HttpStatus.NO_CONTENT) + deleteMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { + return this.service.remove(auth, id); + } + + @Put(':id/assets') + addMemoryAssets( + @Auth() auth: AuthDto, + @Param() { id }: UUIDParamDto, + @Body() dto: BulkIdsDto, + ): Promise { + return this.service.addAssets(auth, id, dto); + } + + @Delete(':id/assets') + @HttpCode(HttpStatus.OK) + removeMemoryAssets( + @Auth() auth: AuthDto, + @Body() dto: BulkIdsDto, + @Param() { id }: UUIDParamDto, + ): Promise { + return this.service.removeAssets(auth, id, dto); + } +} diff --git a/server/src/cores/access.core.ts b/server/src/cores/access.core.ts index 8d021031e..72644870d 100644 --- a/server/src/cores/access.core.ts +++ b/server/src/cores/access.core.ts @@ -33,6 +33,10 @@ export enum Permission { TIMELINE_READ = 'timeline.read', TIMELINE_DOWNLOAD = 'timeline.download', + MEMORY_READ = 'memory.read', + MEMORY_WRITE = 'memory.write', + MEMORY_DELETE = 'memory.delete', + PERSON_READ = 'person.read', PERSON_WRITE = 'person.write', PERSON_MERGE = 'person.merge', @@ -259,6 +263,18 @@ export class AccessCore { return ids.has(auth.user.id) ? new Set([auth.user.id]) : new Set(); } + case Permission.MEMORY_READ: { + return this.repository.memory.checkOwnerAccess(auth.user.id, ids); + } + + case Permission.MEMORY_WRITE: { + return this.repository.memory.checkOwnerAccess(auth.user.id, ids); + } + + case Permission.MEMORY_DELETE: { + return this.repository.memory.checkOwnerAccess(auth.user.id, ids); + } + case Permission.PERSON_READ: { return await this.repository.person.checkOwnerAccess(auth.user.id, ids); } diff --git a/server/src/dtos/memory.dto.ts b/server/src/dtos/memory.dto.ts new file mode 100644 index 000000000..ecd62785f --- /dev/null +++ b/server/src/dtos/memory.dto.ts @@ -0,0 +1,84 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsEnum, IsInt, IsObject, IsPositive, ValidateNested } from 'class-validator'; +import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; +import { MemoryEntity, MemoryType } from 'src/entities/memory.entity'; +import { ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; + +class MemoryBaseDto { + @ValidateBoolean({ optional: true }) + isSaved?: boolean; + + @ValidateDate({ optional: true }) + seenAt?: Date; +} + +class OnThisDayDto { + @IsInt() + @IsPositive() + year!: number; +} + +type MemoryData = OnThisDayDto; + +export class MemoryUpdateDto extends MemoryBaseDto { + @ValidateDate({ optional: true }) + memoryAt?: Date; +} + +export class MemoryCreateDto extends MemoryBaseDto { + @IsEnum(MemoryType) + @ApiProperty({ enum: MemoryType, enumName: 'MemoryType' }) + type!: MemoryType; + + @IsObject() + @ValidateNested() + @Type((options) => { + switch (options?.object.type) { + case MemoryType.ON_THIS_DAY: { + return OnThisDayDto; + } + + default: { + return Object; + } + } + }) + data!: MemoryData; + + @ValidateDate() + memoryAt!: Date; + + @ValidateUUID({ optional: true, each: true }) + assetIds?: string[]; +} + +export class MemoryResponseDto { + id!: string; + createdAt!: Date; + updatedAt!: Date; + deletedAt?: Date; + memoryAt!: Date; + seenAt?: Date; + ownerId!: string; + type!: MemoryType; + data!: MemoryData; + isSaved!: boolean; + assets!: AssetResponseDto[]; +} + +export const mapMemory = (entity: MemoryEntity): MemoryResponseDto => { + return { + id: entity.id, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, + deletedAt: entity.deletedAt, + memoryAt: entity.memoryAt, + seenAt: entity.seenAt, + ownerId: entity.ownerId, + type: entity.type, + data: entity.data, + isSaved: entity.isSaved, + assets: entity.assets.map((asset) => mapAsset(asset)), + }; +}; diff --git a/server/src/entities/index.ts b/server/src/entities/index.ts index 4b568cd9c..761b47693 100644 --- a/server/src/entities/index.ts +++ b/server/src/entities/index.ts @@ -9,6 +9,7 @@ import { AuditEntity } from 'src/entities/audit.entity'; import { ExifEntity } from 'src/entities/exif.entity'; import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity'; import { LibraryEntity } from 'src/entities/library.entity'; +import { MemoryEntity } from 'src/entities/memory.entity'; import { MoveEntity } from 'src/entities/move.entity'; import { PartnerEntity } from 'src/entities/partner.entity'; import { PersonEntity } from 'src/entities/person.entity'; @@ -32,6 +33,7 @@ export const entities = [ AuditEntity, ExifEntity, GeodataPlacesEntity, + MemoryEntity, MoveEntity, PartnerEntity, PersonEntity, diff --git a/server/src/entities/memory.entity.ts b/server/src/entities/memory.entity.ts new file mode 100644 index 000000000..d7dcff4b8 --- /dev/null +++ b/server/src/entities/memory.entity.ts @@ -0,0 +1,67 @@ +import { AssetEntity } from 'src/entities/asset.entity'; +import { UserEntity } from 'src/entities/user.entity'; +import { + Column, + CreateDateColumn, + DeleteDateColumn, + Entity, + JoinTable, + ManyToMany, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +export enum MemoryType { + /** pictures taken on this day X years ago */ + ON_THIS_DAY = 'on_this_day', +} + +export type OnThisDayData = { year: number }; + +export interface MemoryData { + [MemoryType.ON_THIS_DAY]: OnThisDayData; +} + +@Entity('memories') +export class MemoryEntity { + @PrimaryGeneratedColumn('uuid') + id!: string; + + @CreateDateColumn({ type: 'timestamptz' }) + createdAt!: Date; + + @UpdateDateColumn({ type: 'timestamptz' }) + updatedAt!: Date; + + @DeleteDateColumn({ type: 'timestamptz' }) + deletedAt?: Date; + + @ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) + owner!: UserEntity; + + @Column() + ownerId!: string; + + @Column() + type!: T; + + @Column({ type: 'jsonb' }) + data!: MemoryData[T]; + + /** unless set to true, will be automatically deleted in the future */ + @Column({ default: false }) + isSaved!: boolean; + + /** memories are sorted in ascending order by this value */ + @Column({ type: 'timestamptz' }) + memoryAt!: Date; + + /** when the user last viewed the memory */ + @Column({ type: 'timestamptz', nullable: true }) + seenAt?: Date; + + @ManyToMany(() => AssetEntity) + @JoinTable() + assets!: AssetEntity[]; +} diff --git a/server/src/interfaces/access.interface.ts b/server/src/interfaces/access.interface.ts index 7924a29dd..8b9bdcc4b 100644 --- a/server/src/interfaces/access.interface.ts +++ b/server/src/interfaces/access.interface.ts @@ -32,6 +32,10 @@ export interface IAccessRepository { checkPartnerAccess(userId: string, partnerIds: Set): Promise>; }; + memory: { + checkOwnerAccess(userId: string, memoryIds: Set): Promise>; + }; + person: { checkFaceOwnerAccess(userId: string, assetFaceId: Set): Promise>; checkOwnerAccess(userId: string, personIds: Set): Promise>; diff --git a/server/src/interfaces/memory.interface.ts b/server/src/interfaces/memory.interface.ts new file mode 100644 index 000000000..505e1662c --- /dev/null +++ b/server/src/interfaces/memory.interface.ts @@ -0,0 +1,14 @@ +import { MemoryEntity } from 'src/entities/memory.entity'; + +export const IMemoryRepository = 'IMemoryRepository'; + +export interface IMemoryRepository { + search(ownerId: string): Promise; + get(id: string): Promise; + create(memory: Partial): Promise; + update(memory: Partial): Promise; + delete(id: string): Promise; + getAssetIds(id: string, assetIds: string[]): Promise>; + addAssetIds(id: string, assetIds: string[]): Promise; + removeAssetIds(id: string, assetIds: string[]): Promise; +} diff --git a/server/src/migrations/1711637874206-AddMemoryTable.ts b/server/src/migrations/1711637874206-AddMemoryTable.ts new file mode 100644 index 000000000..6309cb508 --- /dev/null +++ b/server/src/migrations/1711637874206-AddMemoryTable.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddMemoryTable1711637874206 implements MigrationInterface { + name = 'AddMemoryTable1711637874206' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "memories" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "ownerId" uuid NOT NULL, "type" character varying NOT NULL, "data" jsonb NOT NULL, "isSaved" boolean NOT NULL DEFAULT false, "memoryAt" TIMESTAMP WITH TIME ZONE NOT NULL, "seenAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_aaa0692d9496fe827b0568612f8" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "memories_assets_assets" ("memoriesId" uuid NOT NULL, "assetsId" uuid NOT NULL, CONSTRAINT "PK_fcaf7112a013d1703c011c6793d" PRIMARY KEY ("memoriesId", "assetsId"))`); + await queryRunner.query(`CREATE INDEX "IDX_984e5c9ab1f04d34538cd32334" ON "memories_assets_assets" ("memoriesId") `); + await queryRunner.query(`CREATE INDEX "IDX_6942ecf52d75d4273de19d2c16" ON "memories_assets_assets" ("assetsId") `); + await queryRunner.query(`ALTER TABLE "memories" ADD CONSTRAINT "FK_575842846f0c28fa5da46c99b19" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "FK_984e5c9ab1f04d34538cd32334e" FOREIGN KEY ("memoriesId") REFERENCES "memories"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "FK_6942ecf52d75d4273de19d2c16f" FOREIGN KEY ("assetsId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "memories_assets_assets" DROP CONSTRAINT "FK_6942ecf52d75d4273de19d2c16f"`); + await queryRunner.query(`ALTER TABLE "memories_assets_assets" DROP CONSTRAINT "FK_984e5c9ab1f04d34538cd32334e"`); + await queryRunner.query(`ALTER TABLE "memories" DROP CONSTRAINT "FK_575842846f0c28fa5da46c99b19"`); + await queryRunner.query(`DROP INDEX "public"."IDX_6942ecf52d75d4273de19d2c16"`); + await queryRunner.query(`DROP INDEX "public"."IDX_984e5c9ab1f04d34538cd32334"`); + await queryRunner.query(`DROP TABLE "memories_assets_assets"`); + await queryRunner.query(`DROP TABLE "memories"`); + } + +} diff --git a/server/src/queries/access.repository.sql b/server/src/queries/access.repository.sql index a0c4e1927..0e1cab6d0 100644 --- a/server/src/queries/access.repository.sql +++ b/server/src/queries/access.repository.sql @@ -196,6 +196,20 @@ WHERE ) AND ("LibraryEntity"."deletedAt" IS NULL) +-- AccessRepository.memory.checkOwnerAccess +SELECT + "MemoryEntity"."id" AS "MemoryEntity_id" +FROM + "memories" "MemoryEntity" +WHERE + ( + ( + ("MemoryEntity"."id" IN ($1)) + AND ("MemoryEntity"."ownerId" = $2) + ) + ) + AND ("MemoryEntity"."deletedAt" IS NULL) + -- AccessRepository.person.checkOwnerAccess SELECT "PersonEntity"."id" AS "PersonEntity_id" diff --git a/server/src/queries/memory.repository.sql b/server/src/queries/memory.repository.sql new file mode 100644 index 000000000..aa3df240c --- /dev/null +++ b/server/src/queries/memory.repository.sql @@ -0,0 +1,18 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- MemoryRepository.getAssetIds +SELECT + "memories_assets"."assetsId" AS "assetId" +FROM + "memories_assets_assets" "memories_assets" +WHERE + "memories_assets"."memoriesId" = $1 + AND "memories_assets"."assetsId" IN ($2) + +-- MemoryRepository.removeAssetIds +DELETE FROM "memories_assets_assets" +WHERE + ( + "memoriesId" = $1 + AND "assetsId" IN ($2) + ) diff --git a/server/src/repositories/access.repository.ts b/server/src/repositories/access.repository.ts index 37b5be0e8..fd74eb2ec 100644 --- a/server/src/repositories/access.repository.ts +++ b/server/src/repositories/access.repository.ts @@ -5,6 +5,7 @@ import { AlbumEntity } from 'src/entities/album.entity'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetEntity } from 'src/entities/asset.entity'; import { LibraryEntity } from 'src/entities/library.entity'; +import { MemoryEntity } from 'src/entities/memory.entity'; import { PartnerEntity } from 'src/entities/partner.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; @@ -19,6 +20,7 @@ type IAssetAccess = IAccessRepository['asset']; type IAuthDeviceAccess = IAccessRepository['authDevice']; type ILibraryAccess = IAccessRepository['library']; type ITimelineAccess = IAccessRepository['timeline']; +type IMemoryAccess = IAccessRepository['memory']; type IPersonAccess = IAccessRepository['person']; type IPartnerAccess = IAccessRepository['partner']; @@ -345,6 +347,28 @@ class TimelineAccess implements ITimelineAccess { } } +class MemoryAccess implements IMemoryAccess { + constructor(private memoryRepository: Repository) {} + + @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) + @ChunkedSet({ paramIndex: 1 }) + async checkOwnerAccess(userId: string, memoryIds: Set): Promise> { + if (memoryIds.size === 0) { + return new Set(); + } + + return this.memoryRepository + .find({ + select: { id: true }, + where: { + id: In([...memoryIds]), + ownerId: userId, + }, + }) + .then((memories) => new Set(memories.map((memory) => memory.id))); + } +} + class PersonAccess implements IPersonAccess { constructor( private assetFaceRepository: Repository, @@ -416,6 +440,7 @@ export class AccessRepository implements IAccessRepository { asset: IAssetAccess; authDevice: IAuthDeviceAccess; library: ILibraryAccess; + memory: IMemoryAccess; person: IPersonAccess; partner: IPartnerAccess; timeline: ITimelineAccess; @@ -425,6 +450,7 @@ export class AccessRepository implements IAccessRepository { @InjectRepository(AssetEntity) assetRepository: Repository, @InjectRepository(AlbumEntity) albumRepository: Repository, @InjectRepository(LibraryEntity) libraryRepository: Repository, + @InjectRepository(MemoryEntity) memoryRepository: Repository, @InjectRepository(PartnerEntity) partnerRepository: Repository, @InjectRepository(PersonEntity) personRepository: Repository, @InjectRepository(AssetFaceEntity) assetFaceRepository: Repository, @@ -436,6 +462,7 @@ export class AccessRepository implements IAccessRepository { this.asset = new AssetAccess(albumRepository, assetRepository, partnerRepository, sharedLinkRepository); this.authDevice = new AuthDeviceAccess(tokenRepository); this.library = new LibraryAccess(libraryRepository); + this.memory = new MemoryAccess(memoryRepository); this.person = new PersonAccess(assetFaceRepository, personRepository); this.partner = new PartnerAccess(partnerRepository); this.timeline = new TimelineAccess(partnerRepository); diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index 4ed114216..336d5df0f 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -13,6 +13,7 @@ import { IJobRepository } from 'src/interfaces/job.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { IMediaRepository } from 'src/interfaces/media.interface'; +import { IMemoryRepository } from 'src/interfaces/memory.interface'; import { IMetadataRepository } from 'src/interfaces/metadata.interface'; import { IMetricRepository } from 'src/interfaces/metric.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; @@ -42,6 +43,7 @@ import { JobRepository } from 'src/repositories/job.repository'; import { LibraryRepository } from 'src/repositories/library.repository'; import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; import { MediaRepository } from 'src/repositories/media.repository'; +import { MemoryRepository } from 'src/repositories/memory.repository'; import { MetadataRepository } from 'src/repositories/metadata.repository'; import { MetricRepository } from 'src/repositories/metric.repository'; import { MoveRepository } from 'src/repositories/move.repository'; @@ -72,6 +74,7 @@ export const repositories = [ { provide: ILibraryRepository, useClass: LibraryRepository }, { provide: IKeyRepository, useClass: ApiKeyRepository }, { provide: IMachineLearningRepository, useClass: MachineLearningRepository }, + { provide: IMemoryRepository, useClass: MemoryRepository }, { provide: IMetadataRepository, useClass: MetadataRepository }, { provide: IMetricRepository, useClass: MetricRepository }, { provide: IMoveRepository, useClass: MoveRepository }, diff --git a/server/src/repositories/memory.repository.ts b/server/src/repositories/memory.repository.ts new file mode 100644 index 000000000..ae8346d00 --- /dev/null +++ b/server/src/repositories/memory.repository.ts @@ -0,0 +1,104 @@ +import { Injectable } from '@nestjs/common'; +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; +import { AssetEntity } from 'src/entities/asset.entity'; +import { MemoryEntity } from 'src/entities/memory.entity'; +import { IMemoryRepository } from 'src/interfaces/memory.interface'; +import { Instrumentation } from 'src/utils/instrumentation'; +import { DataSource, In, Repository } from 'typeorm'; + +@Instrumentation() +@Injectable() +export class MemoryRepository implements IMemoryRepository { + constructor( + @InjectRepository(AssetEntity) private assetRepository: Repository, + @InjectRepository(MemoryEntity) private repository: Repository, + @InjectDataSource() private dataSource: DataSource, + ) {} + + search(ownerId: string): Promise { + return this.repository.find({ + where: { + ownerId, + }, + order: { + memoryAt: 'DESC', + }, + }); + } + + get(id: string): Promise { + return this.repository.findOne({ + where: { + id, + }, + relations: { + assets: true, + }, + }); + } + + create(memory: Partial): Promise { + return this.save(memory); + } + + update(memory: Partial): Promise { + return this.save(memory); + } + + async delete(id: string): Promise { + await this.repository.delete({ id }); + } + + @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) + @ChunkedSet({ paramIndex: 1 }) + async getAssetIds(id: string, assetIds: string[]): Promise> { + if (assetIds.length === 0) { + return new Set(); + } + + const results = await this.dataSource + .createQueryBuilder() + .select('memories_assets.assetsId', 'assetId') + .from('memories_assets_assets', 'memories_assets') + .where('"memories_assets"."memoriesId" = :memoryId', { memoryId: id }) + .andWhere('memories_assets.assetsId IN (:...assetIds)', { assetIds }) + .getRawMany(); + + return new Set(results.map((row) => row['assetId'])); + } + + @GenerateSql({ params: [{ albumId: DummyValue.UUID, assetIds: [DummyValue.UUID] }] }) + async addAssetIds(id: string, assetIds: string[]): Promise { + await this.dataSource + .createQueryBuilder() + .insert() + .into('memories_assets_assets', ['memoriesId', 'assetsId']) + .values(assetIds.map((assetId) => ({ memoriesId: id, assetsId: assetId }))) + .execute(); + } + + @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) + @Chunked({ paramIndex: 1 }) + async removeAssetIds(id: string, assetIds: string[]): Promise { + await this.dataSource + .createQueryBuilder() + .delete() + .from('memories_assets_assets') + .where({ + memoriesId: id, + assetsId: In(assetIds), + }) + .execute(); + } + + private async save(memory: Partial): Promise { + const { id } = await this.repository.save(memory); + return this.repository.findOneOrFail({ + where: { id }, + relations: { + assets: true, + }, + }); + } +} diff --git a/server/src/services/index.ts b/server/src/services/index.ts index 5a97d16fe..3c903c927 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -11,6 +11,7 @@ import { DownloadService } from 'src/services/download.service'; import { JobService } from 'src/services/job.service'; import { LibraryService } from 'src/services/library.service'; import { MediaService } from 'src/services/media.service'; +import { MemoryService } from 'src/services/memory.service'; import { MetadataService } from 'src/services/metadata.service'; import { MicroservicesService } from 'src/services/microservices.service'; import { PartnerService } from 'src/services/partner.service'; @@ -42,6 +43,7 @@ export const services = [ JobService, LibraryService, MediaService, + MemoryService, MetadataService, PartnerService, PersonService, diff --git a/server/src/services/memory.service.spec.ts b/server/src/services/memory.service.spec.ts new file mode 100644 index 000000000..5f045ffde --- /dev/null +++ b/server/src/services/memory.service.spec.ts @@ -0,0 +1,214 @@ +import { BadRequestException } from '@nestjs/common'; +import { MemoryType } from 'src/entities/memory.entity'; +import { IMemoryRepository } from 'src/interfaces/memory.interface'; +import { MemoryService } from 'src/services/memory.service'; +import { authStub } from 'test/fixtures/auth.stub'; +import { memoryStub } from 'test/fixtures/memory.stub'; +import { userStub } from 'test/fixtures/user.stub'; +import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; +import { newMemoryRepositoryMock } from 'test/repositories/memory.repository.mock'; + +describe(MemoryService.name, () => { + let accessMock: IAccessRepositoryMock; + let memoryMock: jest.Mocked; + let sut: MemoryService; + + beforeEach(() => { + accessMock = newAccessRepositoryMock(); + memoryMock = newMemoryRepositoryMock(); + + sut = new MemoryService(accessMock, memoryMock); + }); + + it('should be defined', () => { + expect(sut).toBeDefined(); + }); + + describe('search', () => { + it('should search memories', async () => { + memoryMock.search.mockResolvedValue([memoryStub.memory1, memoryStub.empty]); + await expect(sut.search(authStub.admin)).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: 'memory1', assets: expect.any(Array) }), + expect.objectContaining({ id: 'memoryEmpty', assets: [] }), + ]), + ); + }); + + it('should map ', async () => { + await expect(sut.search(authStub.admin)).resolves.toEqual([]); + }); + }); + + describe('get', () => { + it('should throw an error when no access', async () => { + await expect(sut.get(authStub.admin, 'not-found')).rejects.toBeInstanceOf(BadRequestException); + }); + + it('should throw an error when the memory is not found', async () => { + accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['race-condition'])); + await expect(sut.get(authStub.admin, 'race-condition')).rejects.toBeInstanceOf(BadRequestException); + }); + + it('should get a memory by id', async () => { + memoryMock.get.mockResolvedValue(memoryStub.memory1); + accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); + await expect(sut.get(authStub.admin, 'memory1')).resolves.toMatchObject({ id: 'memory1' }); + expect(memoryMock.get).toHaveBeenCalledWith('memory1'); + expect(accessMock.memory.checkOwnerAccess).toHaveBeenCalledWith(userStub.admin.id, new Set(['memory1'])); + }); + }); + + describe('create', () => { + it('should skip assets the user does not have access to', async () => { + memoryMock.create.mockResolvedValue(memoryStub.empty); + await expect( + sut.create(authStub.admin, { + type: MemoryType.ON_THIS_DAY, + data: { year: 2024 }, + assetIds: ['not-mine'], + memoryAt: new Date(2024), + }), + ).resolves.toMatchObject({ assets: [] }); + expect(memoryMock.create).toHaveBeenCalledWith(expect.objectContaining({ assets: [] })); + }); + + it('should create a memory', async () => { + accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1'])); + memoryMock.create.mockResolvedValue(memoryStub.memory1); + await expect( + sut.create(authStub.admin, { + type: MemoryType.ON_THIS_DAY, + data: { year: 2024 }, + assetIds: ['asset1'], + memoryAt: new Date(2024), + }), + ).resolves.toBeDefined(); + expect(memoryMock.create).toHaveBeenCalledWith( + expect.objectContaining({ + ownerId: userStub.admin.id, + assets: [{ id: 'asset1' }], + }), + ); + }); + + it('should create a memory without assets', async () => { + memoryMock.create.mockResolvedValue(memoryStub.memory1); + await expect( + sut.create(authStub.admin, { + type: MemoryType.ON_THIS_DAY, + data: { year: 2024 }, + memoryAt: new Date(2024), + }), + ).resolves.toBeDefined(); + }); + }); + + describe('update', () => { + it('should require access', async () => { + await expect(sut.update(authStub.admin, 'not-found', { isSaved: true })).rejects.toBeInstanceOf( + BadRequestException, + ); + expect(memoryMock.update).not.toHaveBeenCalled(); + }); + + it('should update a memory', async () => { + accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); + memoryMock.update.mockResolvedValue(memoryStub.memory1); + await expect(sut.update(authStub.admin, 'memory1', { isSaved: true })).resolves.toBeDefined(); + expect(memoryMock.update).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'memory1', + isSaved: true, + }), + ); + }); + }); + + describe('remove', () => { + it('should require access', async () => { + await expect(sut.remove(authStub.admin, 'not-found')).rejects.toBeInstanceOf(BadRequestException); + expect(memoryMock.delete).not.toHaveBeenCalled(); + }); + + it('should delete a memory', async () => { + accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); + await expect(sut.remove(authStub.admin, 'memory1')).resolves.toBeUndefined(); + expect(memoryMock.delete).toHaveBeenCalledWith('memory1'); + }); + }); + + describe('addAssets', () => { + it('should require memory access', async () => { + await expect(sut.addAssets(authStub.admin, 'not-found', { ids: ['asset1'] })).rejects.toBeInstanceOf( + BadRequestException, + ); + expect(memoryMock.addAssetIds).not.toHaveBeenCalled(); + }); + + it('should require asset access', async () => { + accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); + memoryMock.get.mockResolvedValue(memoryStub.memory1); + await expect(sut.addAssets(authStub.admin, 'memory1', { ids: ['not-found'] })).resolves.toEqual([ + { error: 'no_permission', id: 'not-found', success: false }, + ]); + expect(memoryMock.addAssetIds).not.toHaveBeenCalled(); + }); + + it('should skip assets already in the memory', async () => { + accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); + memoryMock.get.mockResolvedValue(memoryStub.memory1); + memoryMock.getAssetIds.mockResolvedValue(new Set(['asset1'])); + await expect(sut.addAssets(authStub.admin, 'memory1', { ids: ['asset1'] })).resolves.toEqual([ + { error: 'duplicate', id: 'asset1', success: false }, + ]); + expect(memoryMock.addAssetIds).not.toHaveBeenCalled(); + }); + + it('should add assets', async () => { + accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); + accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1'])); + memoryMock.get.mockResolvedValue(memoryStub.memory1); + await expect(sut.addAssets(authStub.admin, 'memory1', { ids: ['asset1'] })).resolves.toEqual([ + { id: 'asset1', success: true }, + ]); + expect(memoryMock.addAssetIds).toHaveBeenCalledWith('memory1', ['asset1']); + }); + }); + + describe('removeAssets', () => { + it('should require memory access', async () => { + await expect(sut.removeAssets(authStub.admin, 'not-found', { ids: ['asset1'] })).rejects.toBeInstanceOf( + BadRequestException, + ); + expect(memoryMock.removeAssetIds).not.toHaveBeenCalled(); + }); + + it('should skip assets not in the memory', async () => { + accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); + await expect(sut.removeAssets(authStub.admin, 'memory1', { ids: ['not-found'] })).resolves.toEqual([ + { error: 'not_found', id: 'not-found', success: false }, + ]); + expect(memoryMock.removeAssetIds).not.toHaveBeenCalled(); + }); + + it('should require asset access', async () => { + accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); + memoryMock.getAssetIds.mockResolvedValue(new Set(['asset1'])); + await expect(sut.removeAssets(authStub.admin, 'memory1', { ids: ['asset1'] })).resolves.toEqual([ + { error: 'no_permission', id: 'asset1', success: false }, + ]); + expect(memoryMock.removeAssetIds).not.toHaveBeenCalled(); + }); + + it('should remove assets', async () => { + accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); + accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1'])); + memoryMock.getAssetIds.mockResolvedValue(new Set(['asset1'])); + await expect(sut.removeAssets(authStub.admin, 'memory1', { ids: ['asset1'] })).resolves.toEqual([ + { id: 'asset1', success: true }, + ]); + expect(memoryMock.removeAssetIds).toHaveBeenCalledWith('memory1', ['asset1']); + }); + }); +}); diff --git a/server/src/services/memory.service.ts b/server/src/services/memory.service.ts new file mode 100644 index 000000000..a73eb3ec0 --- /dev/null +++ b/server/src/services/memory.service.ts @@ -0,0 +1,105 @@ +import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { AccessCore, Permission } from 'src/cores/access.core'; +import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; +import { AuthDto } from 'src/dtos/auth.dto'; +import { MemoryCreateDto, MemoryResponseDto, MemoryUpdateDto, mapMemory } from 'src/dtos/memory.dto'; +import { AssetEntity } from 'src/entities/asset.entity'; +import { IAccessRepository } from 'src/interfaces/access.interface'; +import { IMemoryRepository } from 'src/interfaces/memory.interface'; +import { addAssets, removeAssets } from 'src/utils/asset.util'; + +@Injectable() +export class MemoryService { + private access: AccessCore; + + constructor( + @Inject(IAccessRepository) private accessRepository: IAccessRepository, + @Inject(IMemoryRepository) private repository: IMemoryRepository, + ) { + this.access = AccessCore.create(accessRepository); + } + + async search(auth: AuthDto) { + const memories = await this.repository.search(auth.user.id); + return memories.map((memory) => mapMemory(memory)); + } + + async get(auth: AuthDto, id: string): Promise { + await this.access.requirePermission(auth, Permission.MEMORY_READ, id); + const memory = await this.findOrFail(id); + return mapMemory(memory); + } + + async create(auth: AuthDto, dto: MemoryCreateDto) { + // TODO validate type/data combination + + const assetIds = dto.assetIds || []; + const allowedAssetIds = await this.access.checkAccess(auth, Permission.ASSET_SHARE, assetIds); + const memory = await this.repository.create({ + ownerId: auth.user.id, + type: dto.type, + data: dto.data, + isSaved: dto.isSaved, + memoryAt: dto.memoryAt, + seenAt: dto.seenAt, + assets: [...allowedAssetIds].map((id) => ({ id }) as AssetEntity), + }); + + return mapMemory(memory); + } + + async update(auth: AuthDto, id: string, dto: MemoryUpdateDto): Promise { + await this.access.requirePermission(auth, Permission.MEMORY_WRITE, id); + + const memory = await this.repository.update({ + id, + isSaved: dto.isSaved, + memoryAt: dto.memoryAt, + seenAt: dto.seenAt, + }); + + return mapMemory(memory); + } + + async remove(auth: AuthDto, id: string): Promise { + await this.access.requirePermission(auth, Permission.MEMORY_DELETE, id); + await this.repository.delete(id); + } + + async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { + await this.access.requirePermission(auth, Permission.MEMORY_READ, id); + + const repos = { accessRepository: this.accessRepository, repository: this.repository }; + const results = await addAssets(auth, repos, { id, assetIds: dto.ids }); + + const hasSuccess = results.find(({ success }) => success); + if (hasSuccess) { + await this.repository.update({ id, updatedAt: new Date() }); + } + + return results; + } + + async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { + await this.access.requirePermission(auth, Permission.MEMORY_WRITE, id); + + const repos = { accessRepository: this.accessRepository, repository: this.repository }; + const permissions = [Permission.ASSET_SHARE]; + const results = await removeAssets(auth, repos, { id, assetIds: dto.ids, permissions }); + + const hasSuccess = results.find(({ success }) => success); + if (hasSuccess) { + await this.repository.update({ id, updatedAt: new Date() }); + } + + return results; + } + + private async findOrFail(id: string) { + const memory = await this.repository.get(id); + if (!memory) { + throw new BadRequestException('Memory not found'); + } + return memory; + } +} diff --git a/server/test/fixtures/memory.stub.ts b/server/test/fixtures/memory.stub.ts new file mode 100644 index 000000000..bb84a8f1d --- /dev/null +++ b/server/test/fixtures/memory.stub.ts @@ -0,0 +1,30 @@ +import { MemoryEntity, MemoryType } from 'src/entities/memory.entity'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { userStub } from 'test/fixtures/user.stub'; + +export const memoryStub = { + empty: { + id: 'memoryEmpty', + createdAt: new Date(), + updatedAt: new Date(), + memoryAt: new Date(2024), + ownerId: userStub.admin.id, + owner: userStub.admin, + type: MemoryType.ON_THIS_DAY, + data: { year: 2024 }, + isSaved: false, + assets: [], + }, + memory1: { + id: 'memory1', + createdAt: new Date(), + updatedAt: new Date(), + memoryAt: new Date(2024), + ownerId: userStub.admin.id, + owner: userStub.admin, + type: MemoryType.ON_THIS_DAY, + data: { year: 2024 }, + isSaved: false, + assets: [assetStub.image1], + }, +}; diff --git a/server/test/repositories/access.repository.mock.ts b/server/test/repositories/access.repository.mock.ts index a61451277..fe7de7c83 100644 --- a/server/test/repositories/access.repository.mock.ts +++ b/server/test/repositories/access.repository.mock.ts @@ -8,6 +8,7 @@ export interface IAccessRepositoryMock { authDevice: jest.Mocked; library: jest.Mocked; timeline: jest.Mocked; + memory: jest.Mocked; person: jest.Mocked; partner: jest.Mocked; } @@ -49,6 +50,10 @@ export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock => checkPartnerAccess: jest.fn().mockResolvedValue(new Set()), }, + memory: { + checkOwnerAccess: jest.fn().mockResolvedValue(new Set()), + }, + person: { checkFaceOwnerAccess: jest.fn().mockResolvedValue(new Set()), checkOwnerAccess: jest.fn().mockResolvedValue(new Set()), diff --git a/server/test/repositories/memory.repository.mock.ts b/server/test/repositories/memory.repository.mock.ts new file mode 100644 index 000000000..85b17a198 --- /dev/null +++ b/server/test/repositories/memory.repository.mock.ts @@ -0,0 +1,14 @@ +import { IMemoryRepository } from 'src/interfaces/memory.interface'; + +export const newMemoryRepositoryMock = (): jest.Mocked => { + return { + search: jest.fn().mockResolvedValue([]), + get: jest.fn(), + create: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + getAssetIds: jest.fn().mockResolvedValue(new Set()), + addAssetIds: jest.fn(), + removeAssetIds: jest.fn(), + }; +}; From 3682e76deefc87d8c1ed73d4fca77f897923bd94 Mon Sep 17 00:00:00 2001 From: Matthew Momjian <50788000+mmomjian@users.noreply.github.com> Date: Tue, 2 Apr 2024 10:23:53 -0400 Subject: [PATCH 09/25] feat(docs): Supported Formats (#8394) * Create supported-formats.md * Update supported-formats.md * Update supported-formats.md * Update supported-formats.md * Update supported-formats.md * Update supported-formats.md * Update supported-formats.md * Update supported-formats.md * Update supported-formats.md * Update supported-formats.md * linting --- docs/docs/features/supported-formats.md | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/docs/features/supported-formats.md diff --git a/docs/docs/features/supported-formats.md b/docs/docs/features/supported-formats.md new file mode 100644 index 000000000..a2dc56b66 --- /dev/null +++ b/docs/docs/features/supported-formats.md @@ -0,0 +1,42 @@ +# Supported formats + +Immich supports a number of image and video formats, the most common of which are outlined here. + +:::note +For the full list, you can refer to the [Immich source code](https://github.com/immich-app/immich/blob/main/server/src/utils/mime-types.ts). +::: + +## Image formats + +| Format | Extension(s) | Supported? | Notes | +| :-------- | :---------------------------- | :----------------: | :-------------- | +| `AVIF` | `.avif` | :white_check_mark: | | +| `BMP` | `.bmp` | :white_check_mark: | | +| `GIF` | `.gif` | :white_check_mark: | | +| `HEIC` | `.heic` | :white_check_mark: | | +| `HEIF` | `.heif` | :white_check_mark: | | +| `JPEG` | `.jpeg` `.jpg` `.jpe` `.insp` | :white_check_mark: | | +| `JPEG XL` | `.jxl` | :white_check_mark: | | +| `PNG` | `.png` | :white_check_mark: | | +| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop | +| `RAW` | `.raw` | :white_check_mark: | | +| `RW2` | `.rw2` | :white_check_mark: | | +| `SVG` | `.svg` | :white_check_mark: | | +| `TIFF` | `.tif` `.tiff` | :white_check_mark: | | +| `WEBP` | `.webp` | :white_check_mark: | | + +## Video formats + +| Format | Extension(s) | Supported? | Notes | +| :---------- | :-------------------- | :----------------: | :---- | +| `3GPP` | `.3gp` `.3gpp` | :white_check_mark: | | +| `AVI` | `.avi` | :white_check_mark: | | +| `FLV` | `.flv` | :white_check_mark: | | +| `M4V` | `.m4v` | :white_check_mark: | | +| `MATROSKA` | `.mkv` | :white_check_mark: | | +| `MP2T` | `.mts` `.m2ts` | :white_check_mark: | | +| `MP4` | `.mp4` `.insv` | :white_check_mark: | | +| `MPEG` | `.mpg` `.mpe` `.mpeg` | :white_check_mark: | | +| `QUICKTIME` | `.mov` | :white_check_mark: | | +| `WEBM` | `.webm` | :white_check_mark: | | +| `WMV` | `.wmv` | :white_check_mark: | | From 700622e521950061c5d9d9fe2c779a7d01b11b65 Mon Sep 17 00:00:00 2001 From: Matthew Momjian <50788000+mmomjian@users.noreply.github.com> Date: Tue, 2 Apr 2024 10:24:06 -0400 Subject: [PATCH 10/25] docs: update FAQ for Docker (#8418) * Update FAQ.mdx * Update FAQ.mdx * linting --- docs/docs/FAQ.mdx | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/docs/FAQ.mdx b/docs/docs/FAQ.mdx index 884beee4b..98b3db996 100644 --- a/docs/docs/FAQ.mdx +++ b/docs/docs/FAQ.mdx @@ -253,8 +253,19 @@ The initial backup is the most intensive due to the number of jobs running. The ### Can I limit the amount of CPU and RAM usage? -By default, a container has no resource constraints and can use as much of a given resource as the host's kernel scheduler allows. -You can look at the [original docker docs](https://docs.docker.com/config/containers/resource_constraints/) or use this [guide](https://www.baeldung.com/ops/docker-memory-limit) to learn how to limit this. +By default, a container has no resource constraints and can use as much of a given resource as the host's kernel scheduler allows. To limit this, you can add the following to the `docker-compose.yml` block of any containers that you want to have limited resources. + +```yaml +deploy: + resources: + limits: + # Number of CPU threads + cpus: '1.00' + # Gigabytes of memory + memory: '1G' +``` + +For more details, you can look at the [original docker docs](https://docs.docker.com/config/containers/resource_constraints/) or use this [guide](https://www.baeldung.com/ops/docker-memory-limit). ### How can I boost machine learning speed? @@ -296,6 +307,17 @@ You may need to add mount points or docker volumes for the following internal co The non-root user/group needs read/write access to the volume mounts, including `UPLOAD_LOCATION`. +For a further hardened system, you can add the following block to every container except for `immich_postgres`. + +```yaml +security_opt: + # Prevent escalation of privileges after container is started + - no-new-privileges:true +cap_drop: + # Prevent access to raw network traffic + - NET_RAW +``` + ### How can I **purge** data from Immich? Data for Immich comes in two forms: From 0714d119d71fb3cc210cf99834efe9f6cf0137ac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:26:26 +0000 Subject: [PATCH 11/25] chore(deps): update node.js to ef3f477 (#8449) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/Dockerfile b/cli/Dockerfile index 2eab54831..6195d8d82 100644 --- a/cli/Dockerfile +++ b/cli/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20-alpine3.19@sha256:bf77dc26e48ea95fca9d1aceb5acfa69d2e546b765ec2abfb502975f1a2d4def as core +FROM node:20-alpine3.19@sha256:ef3f47741e161900ddd07addcaca7e76534a9205e4cd73b2ed091ba339004a75 as core WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ From 7b1d4a67877d04c7ced722d51af938dfb4f03cb9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:26:47 +0000 Subject: [PATCH 12/25] fix(deps): update typescript-projects to v10.3.7 (#8461) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- server/package-lock.json | 72 ++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index cad273e1e..5e11c3411 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -2538,9 +2538,9 @@ } }, "node_modules/@nestjs/common": { - "version": "10.3.6", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.6.tgz", - "integrity": "sha512-ExWGwjKs8L3sAm6SMRIV/N2HzVol4VFO3EbDhKL/HV1FQXAqj7otjXv/SzGBlI+/ax1nQfEdGgZbSMeZeeR6VA==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.7.tgz", + "integrity": "sha512-gKFtFzcJznrwsRYjtNZoPAvSOPYdNgxbTYoAyLTpoy393cIKgLmJTHu6ReH8/qIB9AaZLdGaFLkx98W/tFWFUw==", "dependencies": { "iterare": "1.2.1", "tslib": "2.6.2", @@ -2581,9 +2581,9 @@ } }, "node_modules/@nestjs/core": { - "version": "10.3.6", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.6.tgz", - "integrity": "sha512-06RFGSFGwDiMRdWFm1Rofp9A1G+1+W7dH2U07S6mE2XtABx5UBwA5kGBZL8z9osdNnBg+6WMpSpkNvQ35f60EA==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.7.tgz", + "integrity": "sha512-hsdlnfiQ3kgqHL5k7js3CU0PV7hBJVi+LfFMgCkoagRxNMf67z0GFGeOV2jk5d65ssB19qdYsDa1MGVuEaoUpg==", "hasInstallScript": true, "dependencies": { "@nuxtjs/opencollective": "0.3.2", @@ -2649,9 +2649,9 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "10.3.6", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.6.tgz", - "integrity": "sha512-yNHuBn/ktjssl+hJd3RXNyMjqL7jXOCDHhnc0JvAmRf3L54/RBgKyDcDLZ6Mv/KYOrmfId5PNeAAjd9IPmcyBw==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.7.tgz", + "integrity": "sha512-noNJ+PyIxQJLCKfuXz0tcQtlVAynfLIuKy62g70lEZ86UrIqSrZFqvWs/rFUgkbT6J8H7Rmv11hASOnX+7M2rA==", "dependencies": { "body-parser": "1.20.2", "cors": "2.8.5", @@ -2669,9 +2669,9 @@ } }, "node_modules/@nestjs/platform-socket.io": { - "version": "10.3.6", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.6.tgz", - "integrity": "sha512-DHFFmwWKgiHdqkLOG44RsaVolcuiLpnzQLOHA/2cDOpHfO7bYNnN4uc8WwWskYOHzzhIya3oJSHeSiLbHPjIkQ==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.7.tgz", + "integrity": "sha512-T9VbVgEUnbid/RiywN9/8YQ8pAGDP++0nX73l4kIWeDWkz5DEh4aLB7O/JvLA3/xRHdjTZ4RiRZazwqSWi1Sog==", "dependencies": { "socket.io": "4.7.5", "tslib": "2.6.2" @@ -2754,9 +2754,9 @@ } }, "node_modules/@nestjs/testing": { - "version": "10.3.6", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.6.tgz", - "integrity": "sha512-9atg+aGqhg3ZhwTz2oHMa3hg3JJ6XQfQcRhkiIsOegCIiV/esqBH7R71kAJC6ppOlKtT3XHngFvlIxo4tODKfw==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.7.tgz", + "integrity": "sha512-PmwZXyoCC/m3F3IFgpgD+SNN6cDPQa/vi3YQxFruvfX3cuHq+P6ZFvBB7hwaKKsLlhA0so42LsMm41oFBkdouw==", "dev": true, "dependencies": { "tslib": "2.6.2" @@ -2796,9 +2796,9 @@ } }, "node_modules/@nestjs/websockets": { - "version": "10.3.6", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.6.tgz", - "integrity": "sha512-XdHF4E+O2TLqqdP211OmlkcXIJ+GMGqfm7/i+Zlg1KwwGu7vG/HOX+m/H9x8UMD41rnWO3vXBqvCLugZpTCiGw==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.7.tgz", + "integrity": "sha512-iYdsWiRNPUy0XzPoW44bx2MW1griuraTr5fNhoe2rUSNO0mEW1aeXp4v56KeZDLAss31WbeckC5P3N223Fys5g==", "dependencies": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -16144,9 +16144,9 @@ } }, "@nestjs/common": { - "version": "10.3.6", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.6.tgz", - "integrity": "sha512-ExWGwjKs8L3sAm6SMRIV/N2HzVol4VFO3EbDhKL/HV1FQXAqj7otjXv/SzGBlI+/ax1nQfEdGgZbSMeZeeR6VA==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.7.tgz", + "integrity": "sha512-gKFtFzcJznrwsRYjtNZoPAvSOPYdNgxbTYoAyLTpoy393cIKgLmJTHu6ReH8/qIB9AaZLdGaFLkx98W/tFWFUw==", "requires": { "iterare": "1.2.1", "tslib": "2.6.2", @@ -16165,9 +16165,9 @@ } }, "@nestjs/core": { - "version": "10.3.6", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.6.tgz", - "integrity": "sha512-06RFGSFGwDiMRdWFm1Rofp9A1G+1+W7dH2U07S6mE2XtABx5UBwA5kGBZL8z9osdNnBg+6WMpSpkNvQ35f60EA==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.7.tgz", + "integrity": "sha512-hsdlnfiQ3kgqHL5k7js3CU0PV7hBJVi+LfFMgCkoagRxNMf67z0GFGeOV2jk5d65ssB19qdYsDa1MGVuEaoUpg==", "requires": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", @@ -16192,9 +16192,9 @@ "requires": {} }, "@nestjs/platform-express": { - "version": "10.3.6", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.6.tgz", - "integrity": "sha512-yNHuBn/ktjssl+hJd3RXNyMjqL7jXOCDHhnc0JvAmRf3L54/RBgKyDcDLZ6Mv/KYOrmfId5PNeAAjd9IPmcyBw==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.7.tgz", + "integrity": "sha512-noNJ+PyIxQJLCKfuXz0tcQtlVAynfLIuKy62g70lEZ86UrIqSrZFqvWs/rFUgkbT6J8H7Rmv11hASOnX+7M2rA==", "requires": { "body-parser": "1.20.2", "cors": "2.8.5", @@ -16204,9 +16204,9 @@ } }, "@nestjs/platform-socket.io": { - "version": "10.3.6", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.6.tgz", - "integrity": "sha512-DHFFmwWKgiHdqkLOG44RsaVolcuiLpnzQLOHA/2cDOpHfO7bYNnN4uc8WwWskYOHzzhIya3oJSHeSiLbHPjIkQ==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.7.tgz", + "integrity": "sha512-T9VbVgEUnbid/RiywN9/8YQ8pAGDP++0nX73l4kIWeDWkz5DEh4aLB7O/JvLA3/xRHdjTZ4RiRZazwqSWi1Sog==", "requires": { "socket.io": "4.7.5", "tslib": "2.6.2" @@ -16256,9 +16256,9 @@ } }, "@nestjs/testing": { - "version": "10.3.6", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.6.tgz", - "integrity": "sha512-9atg+aGqhg3ZhwTz2oHMa3hg3JJ6XQfQcRhkiIsOegCIiV/esqBH7R71kAJC6ppOlKtT3XHngFvlIxo4tODKfw==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.7.tgz", + "integrity": "sha512-PmwZXyoCC/m3F3IFgpgD+SNN6cDPQa/vi3YQxFruvfX3cuHq+P6ZFvBB7hwaKKsLlhA0so42LsMm41oFBkdouw==", "dev": true, "requires": { "tslib": "2.6.2" @@ -16273,9 +16273,9 @@ } }, "@nestjs/websockets": { - "version": "10.3.6", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.6.tgz", - "integrity": "sha512-XdHF4E+O2TLqqdP211OmlkcXIJ+GMGqfm7/i+Zlg1KwwGu7vG/HOX+m/H9x8UMD41rnWO3vXBqvCLugZpTCiGw==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.7.tgz", + "integrity": "sha512-iYdsWiRNPUy0XzPoW44bx2MW1griuraTr5fNhoe2rUSNO0mEW1aeXp4v56KeZDLAss31WbeckC5P3N223Fys5g==", "requires": { "iterare": "1.2.1", "object-hash": "3.0.0", From 97c340b8a40c7e8ecd39a8e0a83873b07cd0ddf7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:26:55 +0000 Subject: [PATCH 13/25] chore(deps): update node.js to fa5d3cf (#8450) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- server/Dockerfile | 2 +- web/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/Dockerfile b/server/Dockerfile index c1615d501..19f6c76eb 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -24,7 +24,7 @@ RUN npm prune --omit=dev --omit=optional COPY --from=dev /usr/src/app/node_modules/@img ./node_modules/@img # web build -FROM node:iron-alpine3.18@sha256:876514790dabd49fae7d9c4dfbba027954bd91d8e7d36da76334466533bc6b0c as web +FROM node:iron-alpine3.18@sha256:fa5d3cf51725bd42d32e67917623038539dbe720dab082f590785c001eb4dfef as web WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ diff --git a/web/Dockerfile b/web/Dockerfile index 422ad9746..c59b6c716 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -1,4 +1,4 @@ -FROM node:iron-alpine3.18@sha256:876514790dabd49fae7d9c4dfbba027954bd91d8e7d36da76334466533bc6b0c +FROM node:iron-alpine3.18@sha256:fa5d3cf51725bd42d32e67917623038539dbe720dab082f590785c001eb4dfef RUN apk add --no-cache tini USER node From 7cc19b50fc9c9dbb7a5e9ff1545db2b13e8436b5 Mon Sep 17 00:00:00 2001 From: Matthew Momjian <50788000+mmomjian@users.noreply.github.com> Date: Tue, 2 Apr 2024 10:56:17 -0400 Subject: [PATCH 14/25] docs: update DB_URL_FILE (#8465) * Update environment-variables.md * linting --- docs/docs/install/environment-variables.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index 9da1f3ce9..5f727999e 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -164,13 +164,14 @@ The following variables support the use of [Docker secrets](https://docs.docker. To use any of these, replace the regular environment variable with the equivalent `_FILE` environment variable. The value of the `_FILE` variable should be set to the path of a file containing the variable value. -| Regular Variable | Equivalent Docker Secrets '\_FILE' Variable | -| :----------------: | :-----------------------------------------: | -| `DB_HOSTNAME` | `DB_HOSTNAME_FILE`\*1 | -| `DB_DATABASE_NAME` | `DB_DATABASE_NAME_FILE`\*1 | -| `DB_USERNAME` | `DB_USERNAME_FILE`\*1 | -| `DB_PASSWORD` | `DB_PASSWORD_FILE`\*1 | -| `REDIS_PASSWORD` | `REDIS_PASSWORD_FILE`\*2 | +| Regular Variable | Equivalent Docker Secrets '\_FILE' Variable | +| :----------------- | :------------------------------------------ | +| `DB_HOSTNAME` | `DB_HOSTNAME_FILE`\*1 | +| `DB_DATABASE_NAME` | `DB_DATABASE_NAME_FILE`\*1 | +| `DB_USERNAME` | `DB_USERNAME_FILE`\*1 | +| `DB_PASSWORD` | `DB_PASSWORD_FILE`\*1 | +| `DB_URL` | `DB_URL_FILE`\*1 | +| `REDIS_PASSWORD` | `REDIS_PASSWORD_FILE`\*2 | \*1: See the [official documentation](https://github.com/docker-library/docs/tree/master/postgres#docker-secrets) for details on how to use Docker Secrets in the Postgres image. From 28e8e539f6bbbe511ed6c9ef4bc24d8526636fe5 Mon Sep 17 00:00:00 2001 From: Guillermo Date: Tue, 2 Apr 2024 09:04:52 -0600 Subject: [PATCH 15/25] feat(web): add keyboard shortcut to stack selected photos (#5983) * feat(web): add keyboard shortcut to stack selected photos * refactor(web): deduplicate logic to stack assets * Fix linting errors * fix(web): incorrect count of stacked photos * chore: cleanup --------- Co-authored-by: Jason Rasmussen --- .../photos-page/actions/stack-action.svelte | 47 ++----------------- .../components/photos-page/asset-grid.svelte | 9 ++++ .../shared-components/show-shortcuts.svelte | 1 + web/src/lib/utils/asset-utils.ts | 39 +++++++++++++++ 4 files changed, 53 insertions(+), 43 deletions(-) diff --git a/web/src/lib/components/photos-page/actions/stack-action.svelte b/web/src/lib/components/photos-page/actions/stack-action.svelte index 1df6cce30..a857da1dd 100644 --- a/web/src/lib/components/photos-page/actions/stack-action.svelte +++ b/web/src/lib/components/photos-page/actions/stack-action.svelte @@ -1,13 +1,8 @@ diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index 53847c581..8991c6915 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -18,6 +18,7 @@ import Scrollbar from '../shared-components/scrollbar/scrollbar.svelte'; import ShowShortcuts from '../shared-components/show-shortcuts.svelte'; import AssetDateGroup from './asset-date-group.svelte'; + import { stackAssets } from '$lib/utils/asset-utils'; import DeleteAssetDialog from './delete-asset-dialog.svelte'; import { handlePromiseError } from '$lib/utils'; import { selectAllAssets } from '$lib/utils/asset-utils'; @@ -85,6 +86,13 @@ handlePromiseError(trashOrDelete(true)); }; + const onStackAssets = async () => { + await stackAssets(Array.from($selectedAssets), (ids) => { + assetStore.removeAssets(ids); + dispatch('escape'); + }); + }; + $: shortcutList = (() => { if ($isSearchEnabled || $showAssetViewer) { return []; @@ -102,6 +110,7 @@ { shortcut: { key: 'Delete' }, onShortcut: onDelete }, { shortcut: { key: 'Delete', shift: true }, onShortcut: onForceDelete }, { shortcut: { key: 'D', ctrl: true }, onShortcut: () => deselectAllAssets() }, + { shortcut: { key: 's' }, onShortcut: () => onStackAssets() }, ); } diff --git a/web/src/lib/components/shared-components/show-shortcuts.svelte b/web/src/lib/components/shared-components/show-shortcuts.svelte index cfa3768bb..a1a8576a6 100644 --- a/web/src/lib/components/shared-components/show-shortcuts.svelte +++ b/web/src/lib/components/shared-components/show-shortcuts.svelte @@ -25,6 +25,7 @@ actions: [ { key: ['f'], action: 'Favorite or unfavorite photo' }, { key: ['i'], action: 'Show or hide info' }, + { key: ['s'], action: 'Stack selected photos' }, { key: ['⇧', 'a'], action: 'Archive or unarchive photo' }, { key: ['⇧', 'd'], action: 'Download' }, { key: ['Space'], action: 'Play or pause video' }, diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts index 532479912..a4255b75d 100644 --- a/web/src/lib/utils/asset-utils.ts +++ b/web/src/lib/utils/asset-utils.ts @@ -11,6 +11,7 @@ import { createAlbum, defaults, getDownloadInfo, + updateAssets, type AssetResponseDto, type AssetTypeEnum, type DownloadInfoDto, @@ -270,6 +271,44 @@ export const getSelectedAssets = (assets: Set, user: UserRespo return ids; }; +export async function stackAssets(assets: Array, onStack: (ds: string[]) => void) { + try { + const parent = assets.at(0); + if (!parent) { + return; + } + + const children = assets.slice(1); + const ids = children.map(({ id }) => id); + + if (children.length > 0) { + await updateAssets({ assetBulkUpdateDto: { ids, stackParentId: parent.id } }); + } + + let childrenCount = parent.stackCount || 1; + for (const asset of children) { + asset.stackParentId = parent.id; + // Add grand-children's count to new parent + childrenCount += asset.stackCount || 1; + // Reset children stack info + asset.stackCount = null; + asset.stack = []; + } + + parent.stackCount = childrenCount; + + notificationController.show({ + message: `Stacked ${ids.length + 1} assets`, + type: NotificationType.Info, + timeout: 1500, + }); + + onStack(ids); + } catch (error) { + handleError(error, `Unable to stack`); + } +} + export const selectAllAssets = async (assetStore: AssetStore, assetInteractionStore: AssetInteractionStore) => { if (get(isSelectingAllAssets)) { // Selection is already ongoing From f7afc0334e0ddb7c39b68fb6f7289ab2c2c9a69b Mon Sep 17 00:00:00 2001 From: Ben Basten <45583362+ben-basten@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:05:02 +0000 Subject: [PATCH 16/25] feat(web,a11y): standardize base modal (#8388) * consistent headings * remove escape key handler * add aria attributes --- .../album-page/share-info-modal.svelte | 8 +---- .../album-page/user-selection-modal.svelte | 10 +----- .../asset-viewer/asset-viewer.svelte | 13 ++----- .../actions/create-shared-link.svelte | 14 +------- .../album-selection-modal.svelte | 19 +++++----- .../shared-components/base-modal.svelte | 36 +++++++++++++++---- .../create-shared-link-modal.svelte | 20 +++++------ .../profile-image-cropper.svelte | 7 +--- .../partner-selection-modal.svelte | 10 +----- web/src/routes/(user)/photos/+page.svelte | 2 +- 10 files changed, 53 insertions(+), 86 deletions(-) diff --git a/web/src/lib/components/album-page/share-info-modal.svelte b/web/src/lib/components/album-page/share-info-modal.svelte index 5f60d2b40..b96974e0e 100644 --- a/web/src/lib/components/album-page/share-info-modal.svelte +++ b/web/src/lib/components/album-page/share-info-modal.svelte @@ -66,13 +66,7 @@ {#if !selectedRemoveUser} - dispatch('close')}> - - -

Options

-
-
- +
diff --git a/web/src/lib/components/album-page/user-selection-modal.svelte b/web/src/lib/components/album-page/user-selection-modal.svelte index b7c0b53b6..d7b7cc514 100644 --- a/web/src/lib/components/album-page/user-selection-modal.svelte +++ b/web/src/lib/components/album-page/user-selection-modal.svelte @@ -13,7 +13,6 @@ import { createEventDispatcher, onMount } from 'svelte'; import Button from '../elements/buttons/button.svelte'; import BaseModal from '../shared-components/base-modal.svelte'; - import ImmichLogo from '../shared-components/immich-logo.svelte'; import UserAvatar from '../shared-components/user-avatar.svelte'; export let album: AlbumResponseDto; @@ -55,14 +54,7 @@ }; - dispatch('close')}> - - - -

Invite to album

-
-
- + {#if selectedUsers.length > 0}

To

diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index d81f00068..523642a9e 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -737,7 +737,6 @@ on:newAlbum={({ detail }) => handleAddToNewAlbum(detail)} on:album={({ detail }) => handleAddToAlbum(detail)} on:close={() => (isShowAlbumPicker = false)} - on:escape={() => (isShowAlbumPicker = false)} /> {/if} @@ -751,19 +750,11 @@ {/if} {#if isShowProfileImageCrop} - (isShowProfileImageCrop = false)} - on:escape={() => (isShowProfileImageCrop = false)} - /> + (isShowProfileImageCrop = false)} /> {/if} {#if isShowShareModal} - (isShowShareModal = false)} - on:escape={() => (isShowShareModal = false)} - /> + (isShowShareModal = false)} /> {/if}
diff --git a/web/src/lib/components/photos-page/actions/create-shared-link.svelte b/web/src/lib/components/photos-page/actions/create-shared-link.svelte index b3e68d303..ef9121ced 100644 --- a/web/src/lib/components/photos-page/actions/create-shared-link.svelte +++ b/web/src/lib/components/photos-page/actions/create-shared-link.svelte @@ -2,26 +2,14 @@ import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte'; import { mdiShareVariantOutline } from '@mdi/js'; - import { createEventDispatcher } from 'svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte'; let showModal = false; - const dispatch = createEventDispatcher<{ - escape: void; - }>(); const { getAssets } = getAssetControlContext(); - const escape = () => { - dispatch('escape'); - showModal = false; - }; (showModal = true)} /> {#if showModal} - id)} - on:close={() => (showModal = false)} - on:escape={escape} - /> + id)} on:close={() => (showModal = false)} /> {/if} diff --git a/web/src/lib/components/shared-components/album-selection-modal.svelte b/web/src/lib/components/shared-components/album-selection-modal.svelte index 9e3caf00e..f86ede716 100644 --- a/web/src/lib/components/shared-components/album-selection-modal.svelte +++ b/web/src/lib/components/shared-components/album-selection-modal.svelte @@ -43,17 +43,16 @@ const handleNew = () => { dispatch('newAlbum', search.length > 0 ? search : ''); }; + + const getTitle = () => { + if (shared) { + return 'Add to shared album'; + } + return 'Add to album'; + }; - - - -

- Add to {shared ? 'Shared ' : ''}Album -

-
-
- +
{#if loading} {#each { length: 3 } as _} @@ -69,11 +68,9 @@
{/each} {:else} -
diff --git a/web/src/lib/components/shared-components/base-modal.svelte b/web/src/lib/components/shared-components/base-modal.svelte index c5bf5380a..4ffc91368 100644 --- a/web/src/lib/components/shared-components/base-modal.svelte +++ b/web/src/lib/components/shared-components/base-modal.svelte @@ -7,12 +7,26 @@ import { clickOutside } from '$lib/utils/click-outside'; import { mdiClose } from '@mdi/js'; import FocusTrap from '$lib/components/shared-components/focus-trap.svelte'; + import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte'; + import Icon from '$lib/components/elements/icon.svelte'; const dispatch = createEventDispatcher<{ - escape: void; close: void; }>(); + /** + * Unique identifier for the modal. + */ + export let id: string; + export let title: string; export let zIndex = 9999; + /** + * If true, the logo will be displayed next to the modal title. + */ + export let showLogo = false; + /** + * Optional icon to display next to the modal title, if `showLogo` is false. + */ + export let icon: string | undefined = undefined; onMount(() => { if (browser) { @@ -36,7 +50,8 @@
dispatch('close'), - onEscape: () => dispatch('escape'), + onEscape: () => dispatch('close'), }} class="max-h-[800px] min-h-[200px] w-[450px] overflow-y-auto rounded-lg bg-immich-bg shadow-md dark:bg-immich-dark-gray dark:text-immich-dark-fg immich-scrollbar" tabindex="-1" >
-
- -

Modal Title

-
+
+ {#if showLogo} + +
+ {:else if icon} + +
+ {/if} +

+ {title} +

dispatch('close')} icon={mdiClose} size={'20'} title="Close" /> diff --git a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte index 801cb9f3e..fcc7c792f 100644 --- a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte +++ b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte @@ -143,20 +143,16 @@ handleError(error, 'Failed to edit shared link'); } }; + + const getTitle = () => { + if (editingLink) { + return 'Edit link'; + } + return 'Create link to share'; + }; - dispatch('close')} on:escape={() => dispatch('escape')}> - - - - {#if editingLink} -

Edit link

- {:else} -

Create link to share

- {/if} -
-
- +
{#if shareType === SharedLinkType.Album} {#if !editingLink} diff --git a/web/src/lib/components/shared-components/profile-image-cropper.svelte b/web/src/lib/components/shared-components/profile-image-cropper.svelte index 36cb6152f..de583e0a0 100644 --- a/web/src/lib/components/shared-components/profile-image-cropper.svelte +++ b/web/src/lib/components/shared-components/profile-image-cropper.svelte @@ -71,12 +71,7 @@ }; - - - -

Set profile picture

-
-
+
- dispatch('close')}> - - - -

Add partner

-
-
- +
{#if availableUsers.length > 0} {#each availableUsers as user} diff --git a/web/src/routes/(user)/photos/+page.svelte b/web/src/routes/(user)/photos/+page.svelte index ffc30d656..00409cdc1 100644 --- a/web/src/routes/(user)/photos/+page.svelte +++ b/web/src/routes/(user)/photos/+page.svelte @@ -53,7 +53,7 @@ assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()} > - (handleEscapeKey = true)} /> + From 62d307321afa59f3308196915704a962124e089b Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Tue, 2 Apr 2024 09:56:33 -0700 Subject: [PATCH 17/25] docs: add some details for getting started as a developer (#8468) --- docs/docs/developer/setup.md | 9 ++++----- server/README.md | 3 +++ web/README.md | 5 +++++ 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 server/README.md create mode 100644 web/README.md diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md index dc61c37d4..d553ca7d8 100644 --- a/docs/docs/developer/setup.md +++ b/docs/docs/developer/setup.md @@ -18,12 +18,11 @@ Thanks for being interested in contributing 😊 ### Server and web app -This environment includes the following services: +This environment includes the services below. Additional details are available in each service's README. -- Core server - `/server/src/immich` -- Machine learning - `/machine-learning` -- Microservices - `/server/src/microservicess` -- Web app - `/web` +- Server - [`/server`](https://github.com/immich-app/immich/tree/main/server) +- Web app - [`/web`](https://github.com/immich-app/immich/tree/main/web) +- Machine learning - [`/machine-learning`](https://github.com/immich-app/immich/tree/main/machine-learning) - Redis - PostgreSQL development database with exposed port `5432` so you can use any database client to acess it diff --git a/server/README.md b/server/README.md new file mode 100644 index 000000000..61b6c2538 --- /dev/null +++ b/server/README.md @@ -0,0 +1,3 @@ +# Immich server project + +This project uses the [NestJS](https://nestjs.com/) web framework. Please refer to [the NestJS docs](https://docs.nestjs.com/) for information on getting started as a contributor to this project. diff --git a/web/README.md b/web/README.md new file mode 100644 index 000000000..e9693ceb0 --- /dev/null +++ b/web/README.md @@ -0,0 +1,5 @@ +# Immich web project + +This project uses the [SvelteKit](https://kit.svelte.dev/) web framework. Please refer to [the SvelteKit docs](https://kit.svelte.dev/docs) for information on getting started as a contributor to this project. In particular, it will help you navigate the project's code if you understand the basics of [SvelteKit routing](https://kit.svelte.dev/docs/routing). + +When developing locally, you will run a SvelteKit Node.js server. When this project is deployed to production, it is built as a SPA and deployed as part of [../server](the server project). From 282bccaca52e79c8c6e39d8ef5d6828c7aeb4d52 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 2 Apr 2024 14:09:55 -0500 Subject: [PATCH 18/25] chore(web): fine tuning styling for base modal (#8469) * chore(web): refine base modal styling * styling * remove api spec file --- .../components/shared-components/base-modal.svelte | 12 +++++------- .../create-shared-link-modal.svelte | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/web/src/lib/components/shared-components/base-modal.svelte b/web/src/lib/components/shared-components/base-modal.svelte index 4ffc91368..c09653d6e 100644 --- a/web/src/lib/components/shared-components/base-modal.svelte +++ b/web/src/lib/components/shared-components/base-modal.svelte @@ -61,19 +61,17 @@ onOutclick: () => dispatch('close'), onEscape: () => dispatch('close'), }} - class="max-h-[800px] min-h-[200px] w-[450px] overflow-y-auto rounded-lg bg-immich-bg shadow-md dark:bg-immich-dark-gray dark:text-immich-dark-fg immich-scrollbar" + class="max-h-[800px] min-h-[200px] w-[450px] overflow-y-auto rounded-3xl bg-immich-bg shadow-md dark:bg-immich-dark-gray dark:text-immich-dark-fg immich-scrollbar" tabindex="-1" >
-
+
{#if showLogo} - -
+ {:else if icon} - -
+ {/if} -

+

{title}

diff --git a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte index fcc7c792f..9e05af625 100644 --- a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte +++ b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte @@ -237,11 +237,11 @@ {#if !sharedLink} {#if editingLink}
- +
{:else}
- +
{/if} {:else} From 6dfa9e1146e8aa7e9580533fd7a2c5260ee2de9d Mon Sep 17 00:00:00 2001 From: seasox Date: Tue, 2 Apr 2024 21:12:47 +0200 Subject: [PATCH 19/25] fix(web): do not set $isShowDetail to false when navigating to a person (#8472) do not set isShowDetail to false when navigating to a person from detail view --- web/src/lib/components/asset-viewer/asset-viewer.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 523642a9e..e139f9c7e 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -264,7 +264,6 @@ }; const handleCloseViewer = () => { - $isShowDetail = false; closeViewer(); }; From 8337da183c981643dbf58fc7117a64130da833c0 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 2 Apr 2024 14:21:58 -0500 Subject: [PATCH 20/25] chore: update openapi (#8470) --- mobile/openapi/.openapi-generator/FILES | 3 + mobile/openapi/README.md | 1 + mobile/openapi/doc/MemoryCreateDto.md | 2 +- mobile/openapi/doc/MemoryResponseDto.md | 2 +- mobile/openapi/doc/OnThisDayDto.md | 15 +++ mobile/openapi/lib/api.dart | 1 + mobile/openapi/lib/api_client.dart | 2 + .../openapi/lib/model/memory_create_dto.dart | 4 +- .../lib/model/memory_response_dto.dart | 4 +- mobile/openapi/lib/model/on_this_day_dto.dart | 98 +++++++++++++++++++ .../openapi/test/memory_create_dto_test.dart | 2 +- .../test/memory_response_dto_test.dart | 2 +- mobile/openapi/test/on_this_day_dto_test.dart | 27 +++++ open-api/immich-openapi-specs.json | 15 ++- open-api/typescript-sdk/src/fetch-client.ts | 7 +- 15 files changed, 173 insertions(+), 12 deletions(-) create mode 100644 mobile/openapi/doc/OnThisDayDto.md create mode 100644 mobile/openapi/lib/model/on_this_day_dto.dart create mode 100644 mobile/openapi/test/on_this_day_dto_test.dart diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 4e109c14d..263687549 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -103,6 +103,7 @@ doc/OAuthApi.md doc/OAuthAuthorizeResponseDto.md doc/OAuthCallbackDto.md doc/OAuthConfigDto.md +doc/OnThisDayDto.md doc/PartnerApi.md doc/PartnerResponseDto.md doc/PathEntityType.md @@ -318,6 +319,7 @@ lib/model/model_type.dart lib/model/o_auth_authorize_response_dto.dart lib/model/o_auth_callback_dto.dart lib/model/o_auth_config_dto.dart +lib/model/on_this_day_dto.dart lib/model/partner_response_dto.dart lib/model/path_entity_type.dart lib/model/path_type.dart @@ -504,6 +506,7 @@ test/o_auth_api_test.dart test/o_auth_authorize_response_dto_test.dart test/o_auth_callback_dto_test.dart test/o_auth_config_dto_test.dart +test/on_this_day_dto_test.dart test/partner_api_test.dart test/partner_response_dto_test.dart test/path_entity_type_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index fede2901c..a64183077 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -311,6 +311,7 @@ Class | Method | HTTP request | Description - [OAuthAuthorizeResponseDto](doc//OAuthAuthorizeResponseDto.md) - [OAuthCallbackDto](doc//OAuthCallbackDto.md) - [OAuthConfigDto](doc//OAuthConfigDto.md) + - [OnThisDayDto](doc//OnThisDayDto.md) - [PartnerResponseDto](doc//PartnerResponseDto.md) - [PathEntityType](doc//PathEntityType.md) - [PathType](doc//PathType.md) diff --git a/mobile/openapi/doc/MemoryCreateDto.md b/mobile/openapi/doc/MemoryCreateDto.md index 5bcbd54f4..e0b0ef5a5 100644 --- a/mobile/openapi/doc/MemoryCreateDto.md +++ b/mobile/openapi/doc/MemoryCreateDto.md @@ -9,7 +9,7 @@ import 'package:openapi/api.dart'; Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **assetIds** | **List** | | [optional] [default to const []] -**data** | [**Object**](.md) | | +**data** | [**OnThisDayDto**](OnThisDayDto.md) | | **isSaved** | **bool** | | [optional] **memoryAt** | [**DateTime**](DateTime.md) | | **seenAt** | [**DateTime**](DateTime.md) | | [optional] diff --git a/mobile/openapi/doc/MemoryResponseDto.md b/mobile/openapi/doc/MemoryResponseDto.md index ef379be04..59a836311 100644 --- a/mobile/openapi/doc/MemoryResponseDto.md +++ b/mobile/openapi/doc/MemoryResponseDto.md @@ -10,7 +10,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **assets** | [**List**](AssetResponseDto.md) | | [default to const []] **createdAt** | [**DateTime**](DateTime.md) | | -**data** | [**Object**](.md) | | +**data** | [**OnThisDayDto**](OnThisDayDto.md) | | **deletedAt** | [**DateTime**](DateTime.md) | | [optional] **id** | **String** | | **isSaved** | **bool** | | diff --git a/mobile/openapi/doc/OnThisDayDto.md b/mobile/openapi/doc/OnThisDayDto.md new file mode 100644 index 000000000..178b4482b --- /dev/null +++ b/mobile/openapi/doc/OnThisDayDto.md @@ -0,0 +1,15 @@ +# openapi.model.OnThisDayDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**year** | **num** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 7d8ab5288..ae458f5de 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -140,6 +140,7 @@ part 'model/model_type.dart'; part 'model/o_auth_authorize_response_dto.dart'; part 'model/o_auth_callback_dto.dart'; part 'model/o_auth_config_dto.dart'; +part 'model/on_this_day_dto.dart'; part 'model/partner_response_dto.dart'; part 'model/path_entity_type.dart'; part 'model/path_type.dart'; diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 8784ad641..757f47568 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -358,6 +358,8 @@ class ApiClient { return OAuthCallbackDto.fromJson(value); case 'OAuthConfigDto': return OAuthConfigDto.fromJson(value); + case 'OnThisDayDto': + return OnThisDayDto.fromJson(value); case 'PartnerResponseDto': return PartnerResponseDto.fromJson(value); case 'PathEntityType': diff --git a/mobile/openapi/lib/model/memory_create_dto.dart b/mobile/openapi/lib/model/memory_create_dto.dart index 5d08a631c..c48a07c5f 100644 --- a/mobile/openapi/lib/model/memory_create_dto.dart +++ b/mobile/openapi/lib/model/memory_create_dto.dart @@ -23,7 +23,7 @@ class MemoryCreateDto { List assetIds; - Object data; + OnThisDayDto data; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -97,7 +97,7 @@ class MemoryCreateDto { assetIds: json[r'assetIds'] is Iterable ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) : const [], - data: mapValueOfType(json, r'data')!, + data: OnThisDayDto.fromJson(json[r'data'])!, isSaved: mapValueOfType(json, r'isSaved'), memoryAt: mapDateTime(json, r'memoryAt', r'')!, seenAt: mapDateTime(json, r'seenAt', r''), diff --git a/mobile/openapi/lib/model/memory_response_dto.dart b/mobile/openapi/lib/model/memory_response_dto.dart index 918099458..8671e3c64 100644 --- a/mobile/openapi/lib/model/memory_response_dto.dart +++ b/mobile/openapi/lib/model/memory_response_dto.dart @@ -30,7 +30,7 @@ class MemoryResponseDto { DateTime createdAt; - Object data; + OnThisDayDto data; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -126,7 +126,7 @@ class MemoryResponseDto { return MemoryResponseDto( assets: AssetResponseDto.listFromJson(json[r'assets']), createdAt: mapDateTime(json, r'createdAt', r'')!, - data: mapValueOfType(json, r'data')!, + data: OnThisDayDto.fromJson(json[r'data'])!, deletedAt: mapDateTime(json, r'deletedAt', r''), id: mapValueOfType(json, r'id')!, isSaved: mapValueOfType(json, r'isSaved')!, diff --git a/mobile/openapi/lib/model/on_this_day_dto.dart b/mobile/openapi/lib/model/on_this_day_dto.dart new file mode 100644 index 000000000..ef1471dfb --- /dev/null +++ b/mobile/openapi/lib/model/on_this_day_dto.dart @@ -0,0 +1,98 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 OnThisDayDto { + /// Returns a new [OnThisDayDto] instance. + OnThisDayDto({ + required this.year, + }); + + num year; + + @override + bool operator ==(Object other) => identical(this, other) || other is OnThisDayDto && + other.year == year; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (year.hashCode); + + @override + String toString() => 'OnThisDayDto[year=$year]'; + + Map toJson() { + final json = {}; + json[r'year'] = this.year; + return json; + } + + /// Returns a new [OnThisDayDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static OnThisDayDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return OnThisDayDto( + year: num.parse('${json[r'year']}'), + ); + } + 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 = OnThisDayDto.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 = OnThisDayDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of OnThisDayDto-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] = OnThisDayDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'year', + }; +} + diff --git a/mobile/openapi/test/memory_create_dto_test.dart b/mobile/openapi/test/memory_create_dto_test.dart index f2909bd46..bfb6f09e1 100644 --- a/mobile/openapi/test/memory_create_dto_test.dart +++ b/mobile/openapi/test/memory_create_dto_test.dart @@ -21,7 +21,7 @@ void main() { // TODO }); - // Object data + // OnThisDayDto data test('to test the property `data`', () async { // TODO }); diff --git a/mobile/openapi/test/memory_response_dto_test.dart b/mobile/openapi/test/memory_response_dto_test.dart index da25bbb6e..90b2e7834 100644 --- a/mobile/openapi/test/memory_response_dto_test.dart +++ b/mobile/openapi/test/memory_response_dto_test.dart @@ -26,7 +26,7 @@ void main() { // TODO }); - // Object data + // OnThisDayDto data test('to test the property `data`', () async { // TODO }); diff --git a/mobile/openapi/test/on_this_day_dto_test.dart b/mobile/openapi/test/on_this_day_dto_test.dart new file mode 100644 index 000000000..71379f8bb --- /dev/null +++ b/mobile/openapi/test/on_this_day_dto_test.dart @@ -0,0 +1,27 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// 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 + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for OnThisDayDto +void main() { + // final instance = OnThisDayDto(); + + group('test OnThisDayDto', () { + // num year + test('to test the property `year`', () async { + // TODO + }); + + + }); + +} diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index da7bad8f0..70e797f52 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -8769,7 +8769,7 @@ "type": "array" }, "data": { - "type": "object" + "$ref": "#/components/schemas/OnThisDayDto" }, "isSaved": { "type": "boolean" @@ -8829,7 +8829,7 @@ "type": "string" }, "data": { - "type": "object" + "$ref": "#/components/schemas/OnThisDayDto" }, "deletedAt": { "format": "date-time", @@ -9110,6 +9110,17 @@ ], "type": "object" }, + "OnThisDayDto": { + "properties": { + "year": { + "type": "number" + } + }, + "required": [ + "year" + ], + "type": "object" + }, "PartnerResponseDto": { "properties": { "avatarColor": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 1584a79cf..bed1380d9 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -494,10 +494,13 @@ export type ValidateLibraryImportPathResponseDto = { export type ValidateLibraryResponseDto = { importPaths?: ValidateLibraryImportPathResponseDto[]; }; +export type OnThisDayDto = { + year: number; +}; export type MemoryResponseDto = { assets: AssetResponseDto[]; createdAt: string; - data: object; + data: OnThisDayDto; deletedAt?: string; id: string; isSaved: boolean; @@ -509,7 +512,7 @@ export type MemoryResponseDto = { }; export type MemoryCreateDto = { assetIds?: string[]; - data: object; + data: OnThisDayDto; isSaved?: boolean; memoryAt: string; seenAt?: string; From 4ed68cf67385efac66d5c5035cd79f849a36959c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 19:22:50 +0000 Subject: [PATCH 21/25] fix(deps): update dependency orjson to v3.10.0 (#8473) --- machine-learning/poetry.lock | 103 ++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/machine-learning/poetry.lock b/machine-learning/poetry.lock index 6b7ebe254..a6efaa1e6 100644 --- a/machine-learning/poetry.lock +++ b/machine-learning/poetry.lock @@ -2110,61 +2110,62 @@ numpy = [ [[package]] name = "orjson" -version = "3.9.15" +version = "3.10.0" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.9.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d61f7ce4727a9fa7680cd6f3986b0e2c732639f46a5e0156e550e35258aa313a"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4feeb41882e8aa17634b589533baafdceb387e01e117b1ec65534ec724023d04"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fbbeb3c9b2edb5fd044b2a070f127a0ac456ffd079cb82746fc84af01ef021a4"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b66bcc5670e8a6b78f0313bcb74774c8291f6f8aeef10fe70e910b8040f3ab75"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2973474811db7b35c30248d1129c64fd2bdf40d57d84beed2a9a379a6f57d0ab"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fe41b6f72f52d3da4db524c8653e46243c8c92df826ab5ffaece2dba9cccd58"}, - {file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4228aace81781cc9d05a3ec3a6d2673a1ad0d8725b4e915f1089803e9efd2b99"}, - {file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f7b65bfaf69493c73423ce9db66cfe9138b2f9ef62897486417a8fcb0a92bfe"}, - {file = "orjson-3.9.15-cp310-none-win32.whl", hash = "sha256:2d99e3c4c13a7b0fb3792cc04c2829c9db07838fb6973e578b85c1745e7d0ce7"}, - {file = "orjson-3.9.15-cp310-none-win_amd64.whl", hash = "sha256:b725da33e6e58e4a5d27958568484aa766e825e93aa20c26c91168be58e08cbb"}, - {file = "orjson-3.9.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c8e8fe01e435005d4421f183038fc70ca85d2c1e490f51fb972db92af6e047c2"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87f1097acb569dde17f246faa268759a71a2cb8c96dd392cd25c668b104cad2f"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff0f9913d82e1d1fadbd976424c316fbc4d9c525c81d047bbdd16bd27dd98cfc"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8055ec598605b0077e29652ccfe9372247474375e0e3f5775c91d9434e12d6b1"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6768a327ea1ba44c9114dba5fdda4a214bdb70129065cd0807eb5f010bfcbb5"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12365576039b1a5a47df01aadb353b68223da413e2e7f98c02403061aad34bde"}, - {file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:71c6b009d431b3839d7c14c3af86788b3cfac41e969e3e1c22f8a6ea13139404"}, - {file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e18668f1bd39e69b7fed19fa7cd1cd110a121ec25439328b5c89934e6d30d357"}, - {file = "orjson-3.9.15-cp311-none-win32.whl", hash = "sha256:62482873e0289cf7313461009bf62ac8b2e54bc6f00c6fabcde785709231a5d7"}, - {file = "orjson-3.9.15-cp311-none-win_amd64.whl", hash = "sha256:b3d336ed75d17c7b1af233a6561cf421dee41d9204aa3cfcc6c9c65cd5bb69a8"}, - {file = "orjson-3.9.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:82425dd5c7bd3adfe4e94c78e27e2fa02971750c2b7ffba648b0f5d5cc016a73"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c51378d4a8255b2e7c1e5cc430644f0939539deddfa77f6fac7b56a9784160a"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ae4e06be04dc00618247c4ae3f7c3e561d5bc19ab6941427f6d3722a0875ef7"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcef128f970bb63ecf9a65f7beafd9b55e3aaf0efc271a4154050fc15cdb386e"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b72758f3ffc36ca566ba98a8e7f4f373b6c17c646ff8ad9b21ad10c29186f00d"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c57bc7b946cf2efa67ac55766e41764b66d40cbd9489041e637c1304400494"}, - {file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:946c3a1ef25338e78107fba746f299f926db408d34553b4754e90a7de1d44068"}, - {file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2f256d03957075fcb5923410058982aea85455d035607486ccb847f095442bda"}, - {file = "orjson-3.9.15-cp312-none-win_amd64.whl", hash = "sha256:5bb399e1b49db120653a31463b4a7b27cf2fbfe60469546baf681d1b39f4edf2"}, - {file = "orjson-3.9.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b17f0f14a9c0ba55ff6279a922d1932e24b13fc218a3e968ecdbf791b3682b25"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f6cbd8e6e446fb7e4ed5bac4661a29e43f38aeecbf60c4b900b825a353276a1"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76bc6356d07c1d9f4b782813094d0caf1703b729d876ab6a676f3aaa9a47e37c"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdfa97090e2d6f73dced247a2f2d8004ac6449df6568f30e7fa1a045767c69a6"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7413070a3e927e4207d00bd65f42d1b780fb0d32d7b1d951f6dc6ade318e1b5a"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cf1596680ac1f01839dba32d496136bdd5d8ffb858c280fa82bbfeb173bdd40"}, - {file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:809d653c155e2cc4fd39ad69c08fdff7f4016c355ae4b88905219d3579e31eb7"}, - {file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:920fa5a0c5175ab14b9c78f6f820b75804fb4984423ee4c4f1e6d748f8b22bc1"}, - {file = "orjson-3.9.15-cp38-none-win32.whl", hash = "sha256:2b5c0f532905e60cf22a511120e3719b85d9c25d0e1c2a8abb20c4dede3b05a5"}, - {file = "orjson-3.9.15-cp38-none-win_amd64.whl", hash = "sha256:67384f588f7f8daf040114337d34a5188346e3fae6c38b6a19a2fe8c663a2f9b"}, - {file = "orjson-3.9.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6fc2fe4647927070df3d93f561d7e588a38865ea0040027662e3e541d592811e"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34cbcd216e7af5270f2ffa63a963346845eb71e174ea530867b7443892d77180"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f541587f5c558abd93cb0de491ce99a9ef8d1ae29dd6ab4dbb5a13281ae04cbd"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92255879280ef9c3c0bcb327c5a1b8ed694c290d61a6a532458264f887f052cb"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05a1f57fb601c426635fcae9ddbe90dfc1ed42245eb4c75e4960440cac667262"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ede0bde16cc6e9b96633df1631fbcd66491d1063667f260a4f2386a098393790"}, - {file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e88b97ef13910e5f87bcbc4dd7979a7de9ba8702b54d3204ac587e83639c0c2b"}, - {file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57d5d8cf9c27f7ef6bc56a5925c7fbc76b61288ab674eb352c26ac780caa5b10"}, - {file = "orjson-3.9.15-cp39-none-win32.whl", hash = "sha256:001f4eb0ecd8e9ebd295722d0cbedf0748680fb9998d3993abaed2f40587257a"}, - {file = "orjson-3.9.15-cp39-none-win_amd64.whl", hash = "sha256:ea0b183a5fe6b2b45f3b854b0d19c4e932d6f5934ae1f723b07cf9560edd4ec7"}, - {file = "orjson-3.9.15.tar.gz", hash = "sha256:95cae920959d772f30ab36d3b25f83bb0f3be671e986c72ce22f8fa700dae061"}, + {file = "orjson-3.10.0-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47af5d4b850a2d1328660661f0881b67fdbe712aea905dadd413bdea6f792c33"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c90681333619d78360d13840c7235fdaf01b2b129cb3a4f1647783b1971542b6"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:400c5b7c4222cb27b5059adf1fb12302eebcabf1978f33d0824aa5277ca899bd"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5dcb32e949eae80fb335e63b90e5808b4b0f64e31476b3777707416b41682db5"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7d507c7493252c0a0264b5cc7e20fa2f8622b8a83b04d819b5ce32c97cf57b"}, + {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e286a51def6626f1e0cc134ba2067dcf14f7f4b9550f6dd4535fd9d79000040b"}, + {file = "orjson-3.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8acd4b82a5f3a3ec8b1dc83452941d22b4711964c34727eb1e65449eead353ca"}, + {file = "orjson-3.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:30707e646080dd3c791f22ce7e4a2fc2438765408547c10510f1f690bd336217"}, + {file = "orjson-3.10.0-cp310-none-win32.whl", hash = "sha256:115498c4ad34188dcb73464e8dc80e490a3e5e88a925907b6fedcf20e545001a"}, + {file = "orjson-3.10.0-cp310-none-win_amd64.whl", hash = "sha256:6735dd4a5a7b6df00a87d1d7a02b84b54d215fb7adac50dd24da5997ffb4798d"}, + {file = "orjson-3.10.0-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9587053e0cefc284e4d1cd113c34468b7d3f17666d22b185ea654f0775316a26"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bef1050b1bdc9ea6c0d08468e3e61c9386723633b397e50b82fda37b3563d72"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d16c6963ddf3b28c0d461641517cd312ad6b3cf303d8b87d5ef3fa59d6844337"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4251964db47ef090c462a2d909f16c7c7d5fe68e341dabce6702879ec26d1134"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73bbbdc43d520204d9ef0817ac03fa49c103c7f9ea94f410d2950755be2c349c"}, + {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:414e5293b82373606acf0d66313aecb52d9c8c2404b1900683eb32c3d042dbd7"}, + {file = "orjson-3.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:feaed5bb09877dc27ed0d37f037ddef6cb76d19aa34b108db270d27d3d2ef747"}, + {file = "orjson-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5127478260db640323cea131ee88541cb1a9fbce051f0b22fa2f0892f44da302"}, + {file = "orjson-3.10.0-cp311-none-win32.whl", hash = "sha256:b98345529bafe3c06c09996b303fc0a21961820d634409b8639bc16bd4f21b63"}, + {file = "orjson-3.10.0-cp311-none-win_amd64.whl", hash = "sha256:658ca5cee3379dd3d37dbacd43d42c1b4feee99a29d847ef27a1cb18abdfb23f"}, + {file = "orjson-3.10.0-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4329c1d24fd130ee377e32a72dc54a3c251e6706fccd9a2ecb91b3606fddd998"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef0f19fdfb6553342b1882f438afd53c7cb7aea57894c4490c43e4431739c700"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c4f60db24161534764277f798ef53b9d3063092f6d23f8f962b4a97edfa997a0"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1de3fd5c7b208d836f8ecb4526995f0d5877153a4f6f12f3e9bf11e49357de98"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f93e33f67729d460a177ba285002035d3f11425ed3cebac5f6ded4ef36b28344"}, + {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:237ba922aef472761acd697eef77fef4831ab769a42e83c04ac91e9f9e08fa0e"}, + {file = "orjson-3.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98c1bfc6a9bec52bc8f0ab9b86cc0874b0299fccef3562b793c1576cf3abb570"}, + {file = "orjson-3.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30d795a24be16c03dca0c35ca8f9c8eaaa51e3342f2c162d327bd0225118794a"}, + {file = "orjson-3.10.0-cp312-none-win32.whl", hash = "sha256:6a3f53dc650bc860eb26ec293dfb489b2f6ae1cbfc409a127b01229980e372f7"}, + {file = "orjson-3.10.0-cp312-none-win_amd64.whl", hash = "sha256:983db1f87c371dc6ffc52931eb75f9fe17dc621273e43ce67bee407d3e5476e9"}, + {file = "orjson-3.10.0-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9a667769a96a72ca67237224a36faf57db0c82ab07d09c3aafc6f956196cfa1b"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade1e21dfde1d37feee8cf6464c20a2f41fa46c8bcd5251e761903e46102dc6b"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:23c12bb4ced1c3308eff7ba5c63ef8f0edb3e4c43c026440247dd6c1c61cea4b"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2d014cf8d4dc9f03fc9f870de191a49a03b1bcda51f2a957943fb9fafe55aac"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eadecaa16d9783affca33597781328e4981b048615c2ddc31c47a51b833d6319"}, + {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd583341218826f48bd7c6ebf3310b4126216920853cbc471e8dbeaf07b0b80e"}, + {file = "orjson-3.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:90bfc137c75c31d32308fd61951d424424426ddc39a40e367704661a9ee97095"}, + {file = "orjson-3.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13b5d3c795b09a466ec9fcf0bd3ad7b85467d91a60113885df7b8d639a9d374b"}, + {file = "orjson-3.10.0-cp38-none-win32.whl", hash = "sha256:5d42768db6f2ce0162544845facb7c081e9364a5eb6d2ef06cd17f6050b048d8"}, + {file = "orjson-3.10.0-cp38-none-win_amd64.whl", hash = "sha256:33e6655a2542195d6fd9f850b428926559dee382f7a862dae92ca97fea03a5ad"}, + {file = "orjson-3.10.0-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4050920e831a49d8782a1720d3ca2f1c49b150953667eed6e5d63a62e80f46a2"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1897aa25a944cec774ce4a0e1c8e98fb50523e97366c637b7d0cddabc42e6643"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bf565a69e0082ea348c5657401acec3cbbb31564d89afebaee884614fba36b4"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6ebc17cfbbf741f5c1a888d1854354536f63d84bee537c9a7c0335791bb9009"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2817877d0b69f78f146ab305c5975d0618df41acf8811249ee64231f5953fee"}, + {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57d017863ec8aa4589be30a328dacd13c2dc49de1c170bc8d8c8a98ece0f2925"}, + {file = "orjson-3.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:22c2f7e377ac757bd3476ecb7480c8ed79d98ef89648f0176deb1da5cd014eb7"}, + {file = "orjson-3.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e62ba42bfe64c60c1bc84799944f80704e996592c6b9e14789c8e2a303279912"}, + {file = "orjson-3.10.0-cp39-none-win32.whl", hash = "sha256:60c0b1bdbccd959ebd1575bd0147bd5e10fc76f26216188be4a36b691c937077"}, + {file = "orjson-3.10.0-cp39-none-win_amd64.whl", hash = "sha256:175a41500ebb2fdf320bf78e8b9a75a1279525b62ba400b2b2444e274c2c8bee"}, + {file = "orjson-3.10.0.tar.gz", hash = "sha256:ba4d8cac5f2e2cff36bea6b6481cdb92b38c202bcec603d6f5ff91960595a1ed"}, ] [[package]] From 15a2e6feeb68c61d3af7f2c03b35f6eb90f8ba06 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 10:17:17 -0400 Subject: [PATCH 22/25] fix(deps): update typescript-projects (#8471) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 28 ++++---- server/package-lock.json | 152 +++++++++++++++++++++++++++++---------- web/package-lock.json | 14 ++-- 3 files changed, 135 insertions(+), 59 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index 6edc44424..02a6d37f5 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -2555,16 +2555,16 @@ } }, "node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -3411,12 +3411,12 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { @@ -4456,13 +4456,13 @@ } }, "node_modules/vite": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", - "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==", + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz", + "integrity": "sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==", "dev": true, "dependencies": { "esbuild": "^0.20.1", - "postcss": "^8.4.36", + "postcss": "^8.4.38", "rollup": "^4.13.0" }, "bin": { diff --git a/server/package-lock.json b/server/package-lock.json index 5e11c3411..56be652be 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -2464,6 +2464,52 @@ "balanced-match": "^1.0.0" } }, + "node_modules/@nestjs/cli/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nestjs/cli/node_modules/glob/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nestjs/cli/node_modules/glob/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/@nestjs/cli/node_modules/minimatch": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", @@ -2722,9 +2768,9 @@ "dev": true }, "node_modules/@nestjs/swagger": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.3.0.tgz", - "integrity": "sha512-zLkfKZ+ioYsIZ3dfv7Bj8YHnZMNAGWFUmx2ZDuLp/fBE4P8BSjB7hldzDueFXsmwaPL90v7lgyd82P+s7KME1Q==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.3.1.tgz", + "integrity": "sha512-LUC4mr+5oAleEC/a2j8pNRh1S5xhKXJ1Gal5ZdRjt9XebQgbngXCdW7JTA9WOEcwGtFZN9EnKYdquzH971LZfw==", "dependencies": { "@microsoft/tsdoc": "^0.14.2", "@nestjs/mapped-types": "2.0.5", @@ -8443,15 +8489,15 @@ } }, "node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -8489,9 +8535,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -11080,11 +11126,11 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { @@ -11095,9 +11141,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "engines": { "node": "14 || >=16.14" } @@ -16097,6 +16143,36 @@ "balanced-match": "^1.0.0" } }, + "glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "dependencies": { + "minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true + } + } + }, "minimatch": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", @@ -16243,9 +16319,9 @@ } }, "@nestjs/swagger": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.3.0.tgz", - "integrity": "sha512-zLkfKZ+ioYsIZ3dfv7Bj8YHnZMNAGWFUmx2ZDuLp/fBE4P8BSjB7hldzDueFXsmwaPL90v7lgyd82P+s7KME1Q==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.3.1.tgz", + "integrity": "sha512-LUC4mr+5oAleEC/a2j8pNRh1S5xhKXJ1Gal5ZdRjt9XebQgbngXCdW7JTA9WOEcwGtFZN9EnKYdquzH971LZfw==", "requires": { "@microsoft/tsdoc": "^0.14.2", "@nestjs/mapped-types": "2.0.5", @@ -20532,15 +20608,15 @@ "dev": true }, "glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "requires": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "dependencies": { "brace-expansion": { @@ -20552,9 +20628,9 @@ } }, "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "requires": { "brace-expansion": "^2.0.1" } @@ -22537,18 +22613,18 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", "requires": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "dependencies": { "lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==" + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==" } } }, diff --git a/web/package-lock.json b/web/package-lock.json index a855def3b..a00c329b3 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1825,9 +1825,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.4.tgz", - "integrity": "sha512-eDxK2d4EGzk99QsZNoPXe7jlzA5EGqfcCpUwZ912bhnalsZ2ZsG5wGRthkydupVjYyqdmzEanVKFhLxU2vkPSQ==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.5.tgz", + "integrity": "sha512-ULe3PB00q4+wYRL+IS5FDPsCEVnhEITofm7b9Yz8malcH3r1SAnW/JJ6T13hIMeu8QNRIuVQWo+P4+2VklbnLQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -8677,13 +8677,13 @@ } }, "node_modules/vite": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", - "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==", + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz", + "integrity": "sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==", "dev": true, "dependencies": { "esbuild": "^0.20.1", - "postcss": "^8.4.36", + "postcss": "^8.4.38", "rollup": "^4.13.0" }, "bin": { From 7f854432ae11eb07c28de301820d4fc6e21d32ed Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 3 Apr 2024 09:37:03 -0500 Subject: [PATCH 23/25] fix(web): show download button correctly based on shared link permission (#8288) * fix(web): show download button correctly based on shared link permission * remove console log * Define initial value * simpler implementation * refactor: show download in asset viewer for shared link * chore: hook timeout --------- Co-authored-by: Jason Rasmussen --- e2e/src/api/specs/search.e2e-spec.ts | 2 +- .../asset-viewer/asset-viewer-nav-bar.svelte | 11 +++++++++++ .../lib/components/asset-viewer/asset-viewer.svelte | 9 ++++----- web/src/lib/utils.ts | 13 ++++++------- web/src/routes/(user)/share/[key]/+page.svelte | 2 ++ web/src/routes/(user)/share/[key]/+page.ts | 3 ++- 6 files changed, 26 insertions(+), 14 deletions(-) diff --git a/e2e/src/api/specs/search.e2e-spec.ts b/e2e/src/api/specs/search.e2e-spec.ts index afe131228..a613cdd28 100644 --- a/e2e/src/api/specs/search.e2e-spec.ts +++ b/e2e/src/api/specs/search.e2e-spec.ts @@ -133,7 +133,7 @@ describe('/search', () => { assetLast = assets.at(-1) as AssetFileUploadResponseDto; await deleteAssets({ assetBulkDeleteDto: { ids: [assetSilver.id] } }, { headers: asBearerAuth(admin.accessToken) }); - }); + }, 30_000); afterAll(async () => { utils.disconnectWebsocket(websocket); diff --git a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte index 1772b8641..abcb248f1 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte @@ -161,6 +161,16 @@ }} /> {/if} + + {#if !isOwner && showDownloadButton} + dispatch('download')} + title="Download" + /> + {/if} + {#if showDetailButton} {/if} + {#if isOwner} import Icon from '$lib/components/elements/icon.svelte'; + import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte'; + import FocusTrap from '$lib/components/shared-components/focus-trap.svelte'; import { AssetAction, ProjectionType } from '$lib/constants'; import { updateNumberOfComments } from '$lib/stores/activity.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; @@ -9,7 +11,7 @@ import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { stackAssetsStore } from '$lib/stores/stacked-asset.store'; import { user } from '$lib/stores/user.store'; - import { getAssetJobMessage, isSharedLink, handlePromiseError } from '$lib/utils'; + import { getAssetJobMessage, getSharedLink, handlePromiseError, isSharedLink } from '$lib/utils'; import { addAssetsToAlbum, addAssetsToNewAlbum, downloadFile } from '$lib/utils/asset-utils'; import { handleError } from '$lib/utils/handle-error'; import { shortcuts } from '$lib/utils/shortcut'; @@ -30,7 +32,6 @@ type ActivityResponseDto, type AlbumResponseDto, type AssetResponseDto, - type SharedLinkResponseDto, } from '@immich/sdk'; import { mdiChevronLeft, mdiChevronRight, mdiImageBrokenVariant } from '@mdi/js'; import { createEventDispatcher, onDestroy, onMount } from 'svelte'; @@ -49,14 +50,11 @@ import PhotoViewer from './photo-viewer.svelte'; import SlideshowBar from './slideshow-bar.svelte'; import VideoViewer from './video-viewer.svelte'; - import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte'; - import FocusTrap from '$lib/components/shared-components/focus-trap.svelte'; export let assetStore: AssetStore | null = null; export let asset: AssetResponseDto; export let preloadAssets: AssetResponseDto[] = []; export let showNavigation = true; - export let sharedLink: SharedLinkResponseDto | undefined = undefined; $: isTrashEnabled = $featureFlags.trash; export let withStacked = false; export let isShared = false; @@ -86,6 +84,7 @@ let addToSharedAlbum = true; let shouldPlayMotionPhoto = false; let isShowProfileImageCrop = false; + let sharedLink = getSharedLink(); let shouldShowDownloadButton = sharedLink ? sharedLink.allowDownload : !asset.isOffline; let shouldShowDetailButton = asset.hasMetadata; let shouldShowShareModal = !asset.isTrashed; diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts index 299f04505..e6c21b70a 100644 --- a/web/src/lib/utils.ts +++ b/web/src/lib/utils.ts @@ -10,6 +10,7 @@ import { linkOAuthAccount, startOAuth, unlinkOAuthAccount, + type SharedLinkResponseDto, type UserResponseDto, } from '@immich/sdk'; import { mdiCogRefreshOutline, mdiDatabaseRefreshOutline, mdiImageRefreshOutline } from '@mdi/js'; @@ -129,14 +130,12 @@ export const getJobName = (jobName: JobName) => { }; let _key: string | undefined; +let _sharedLink: SharedLinkResponseDto | undefined; -export const setKey = (key: string) => { - _key = key; -}; - -export const getKey = (): string | undefined => { - return _key; -}; +export const setKey = (key: string) => (_key = key); +export const getKey = (): string | undefined => _key; +export const setSharedLink = (sharedLink: SharedLinkResponseDto) => (_sharedLink = sharedLink); +export const getSharedLink = (): SharedLinkResponseDto | undefined => _sharedLink; export const isSharedLink = () => { return !!_key; diff --git a/web/src/routes/(user)/share/[key]/+page.svelte b/web/src/routes/(user)/share/[key]/+page.svelte index 1b87063da..28ebbc6c8 100644 --- a/web/src/routes/(user)/share/[key]/+page.svelte +++ b/web/src/routes/(user)/share/[key]/+page.svelte @@ -9,6 +9,7 @@ import { handleError } from '$lib/utils/handle-error'; import { getMySharedLink, SharedLinkType } from '@immich/sdk'; import type { PageData } from './$types'; + import { setSharedLink } from '$lib/utils'; export let data: PageData; let { sharedLink, passwordRequired, sharedLinkKey: key, meta } = data; @@ -19,6 +20,7 @@ const handlePasswordSubmit = async () => { try { sharedLink = await getMySharedLink({ password, key }); + setSharedLink(sharedLink); passwordRequired = false; isOwned = $user ? $user.id === sharedLink.userId : false; title = (sharedLink.album ? sharedLink.album.albumName : 'Public Share') + ' - Immich'; diff --git a/web/src/routes/(user)/share/[key]/+page.ts b/web/src/routes/(user)/share/[key]/+page.ts index 380c9d002..89821a447 100644 --- a/web/src/routes/(user)/share/[key]/+page.ts +++ b/web/src/routes/(user)/share/[key]/+page.ts @@ -1,4 +1,4 @@ -import { getAssetThumbnailUrl } from '$lib/utils'; +import { getAssetThumbnailUrl, setSharedLink } from '$lib/utils'; import { authenticate } from '$lib/utils/auth'; import { ThumbnailFormat, getMySharedLink, isHttpError } from '@immich/sdk'; import type { PageLoad } from './$types'; @@ -9,6 +9,7 @@ export const load = (async ({ params }) => { try { const sharedLink = await getMySharedLink({ key }); + setSharedLink(sharedLink); const assetCount = sharedLink.assets.length; const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id; From 0529076ed70c6120f41d0c1121b5575588770c44 Mon Sep 17 00:00:00 2001 From: bo0tzz Date: Thu, 4 Apr 2024 00:20:48 +0200 Subject: [PATCH 24/25] docs: Update environment variable services (#8490) * docs: Update environment variable services * chore: format fix --- docs/docs/install/environment-variables.md | 27 +++++++++++----------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index 5f727999e..a8cbe6059 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -17,10 +17,10 @@ If this should not work, try running `docker compose up -d --force-recreate`. ## Docker Compose -| Variable | Description | Default | Services | -| :---------------- | :-------------------- | :-------: | :-------------------------------------------------- | -| `IMMICH_VERSION` | Image tags | `release` | server, microservices, machine learning, web, proxy | -| `UPLOAD_LOCATION` | Host Path for uploads | | server, microservices | +| Variable | Description | Default | Services | +| :---------------- | :-------------------- | :-------: | :-------------------------------------- | +| `IMMICH_VERSION` | Image tags | `release` | server, microservices, machine learning | +| `UPLOAD_LOCATION` | Host Path for uploads | | server, microservices | :::tip @@ -30,15 +30,15 @@ These environment variables are used by the `docker-compose.yml` file and do **N ## General -| Variable | Description | Default | Services | -| :------------------------------ | :------------------------------------------- | :------------------: | :------------------------------------------- | -| `TZ` | Timezone | | microservices | -| `NODE_ENV` | Environment (production, development) | `production` | server, microservices, machine learning, web | -| `LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, microservices | -| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload` | server, microservices | -| `IMMICH_CONFIG_FILE` | Path to config file | | server, microservices | -| `IMMICH_WEB_ROOT` | Path of root index.html | `/usr/src/app/www` | server | -| `IMMICH_REVERSE_GEOCODING_ROOT` | Path of reverse geocoding dump directory | `/usr/src/resources` | microservices | +| Variable | Description | Default | Services | +| :------------------------------ | :------------------------------------------- | :------------------: | :-------------------------------------- | +| `TZ` | Timezone | | microservices | +| `NODE_ENV` | Environment (production, development) | `production` | server, microservices, machine learning | +| `LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, microservices, machine learning | +| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload` | server, microservices | +| `IMMICH_CONFIG_FILE` | Path to config file | | server, microservices | +| `IMMICH_WEB_ROOT` | Path of root index.html | `/usr/src/app/www` | server | +| `IMMICH_REVERSE_GEOCODING_ROOT` | Path of reverse geocoding dump directory | `/usr/src/resources` | microservices | :::tip `TZ` should be set to a `TZ identifier` from [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List). For example, `TZ="Etc/UTC"`. @@ -50,7 +50,6 @@ These environment variables are used by the `docker-compose.yml` file and do **N | Variable | Description | Default | Services | | :---------------------- | :-------------------- | :-------: | :--------------- | -| `PORT` | Web Port | `3000` | web | | `SERVER_PORT` | Server Port | `3001` | server | | `MICROSERVICES_PORT` | Microservices Port | `3002` | microservices | | `MACHINE_LEARNING_HOST` | Machine Learning Host | `0.0.0.0` | machine learning | From 66650f594487c99715cb75555ce401b721bb8ee6 Mon Sep 17 00:00:00 2001 From: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com> Date: Thu, 4 Apr 2024 03:20:54 +0200 Subject: [PATCH 25/25] fix(web): prevent fetching asset info twice (#8486) --- .../asset-viewer/asset-viewer.svelte | 18 +++++++------- .../photos-page/asset-date-group.svelte | 8 ++----- .../components/photos-page/asset-grid.svelte | 16 +++++-------- web/src/lib/stores/asset-viewing.store.ts | 24 +++++++------------ web/src/lib/stores/asset.store.spec.ts | 16 ++++++------- web/src/lib/stores/assets.store.ts | 12 +++++----- web/src/lib/utils/slideshow-history.ts | 10 ++++---- .../(user)/albums/[albumId]/+page.svelte | 4 ++-- 8 files changed, 47 insertions(+), 61 deletions(-) diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index ccba99a05..b805e4e9b 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -62,7 +62,7 @@ let reactions: ActivityResponseDto[] = []; - const { setAssetId } = assetViewingStore; + const { setAsset } = assetViewingStore; const { restartProgress: restartSlideshowProgress, stopProgress: stopSlideshowProgress, @@ -193,7 +193,7 @@ slideshowStateUnsubscribe = slideshowState.subscribe((value) => { if (value === SlideshowState.PlaySlideshow) { slideshowHistory.reset(); - slideshowHistory.queue(asset.id); + slideshowHistory.queue(asset); handlePromiseError(handlePlaySlideshow()); } else if (value === SlideshowState.StopSlideshow) { handlePromiseError(handleStopSlideshow()); @@ -203,7 +203,7 @@ shuffleSlideshowUnsubscribe = slideshowNavigation.subscribe((value) => { if (value === SlideshowNavigation.Shuffle) { slideshowHistory.reset(); - slideshowHistory.queue(asset.id); + slideshowHistory.queue(asset); } }); @@ -278,9 +278,9 @@ return; } - slideshowHistory.queue(asset.id); + slideshowHistory.queue(asset); - await setAssetId(asset.id); + setAsset(asset); $restartSlideshowProgress = true; }; @@ -299,9 +299,7 @@ if ($slideshowState === SlideshowState.PlaySlideshow && assetStore) { const hasNext = - order === 'previous' - ? await assetStore.getPreviousAssetId(asset.id) - : await assetStore.getNextAssetId(asset.id); + order === 'previous' ? await assetStore.getPreviousAsset(asset.id) : await assetStore.getNextAsset(asset.id); if (hasNext) { $restartSlideshowProgress = true; } else { @@ -441,8 +439,8 @@ let assetViewerHtmlElement: HTMLElement; - const slideshowHistory = new SlideshowHistory((assetId: string) => { - handlePromiseError(setAssetId(assetId)); + const slideshowHistory = new SlideshowHistory((asset) => { + setAsset(asset); $restartSlideshowProgress = true; }); diff --git a/web/src/lib/components/photos-page/asset-date-group.svelte b/web/src/lib/components/photos-page/asset-date-group.svelte index e3af25984..a42f8dc78 100644 --- a/web/src/lib/components/photos-page/asset-date-group.svelte +++ b/web/src/lib/components/photos-page/asset-date-group.svelte @@ -80,17 +80,13 @@ }); } - const assetClickHandler = async ( - asset: AssetResponseDto, - assetsInDateGroup: AssetResponseDto[], - groupTitle: string, - ) => { + const assetClickHandler = (asset: AssetResponseDto, assetsInDateGroup: AssetResponseDto[], groupTitle: string) => { if (isSelectionMode || $isMultiSelectState) { assetSelectHandler(asset, assetsInDateGroup, groupTitle); return; } - await assetViewingStore.setAssetId(asset.id); + assetViewingStore.setAsset(asset); }; const handleSelectGroup = (title: string, assets: AssetResponseDto[]) => dispatch('select', { title, assets }); diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index 8991c6915..9aca51fa0 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -137,26 +137,22 @@ } const handlePrevious = async () => { - const previousAsset = await assetStore.getPreviousAssetId($viewingAsset.id); + const previousAsset = await assetStore.getPreviousAsset($viewingAsset.id); if (previousAsset) { - const preloadId = await assetStore.getPreviousAssetId(previousAsset); - preloadId - ? await assetViewingStore.setAssetId(previousAsset, [preloadId]) - : await assetViewingStore.setAssetId(previousAsset); + const preloadAsset = await assetStore.getPreviousAsset(previousAsset.id); + assetViewingStore.setAsset(previousAsset, preloadAsset ? [preloadAsset] : []); } return !!previousAsset; }; const handleNext = async () => { - const nextAsset = await assetStore.getNextAssetId($viewingAsset.id); + const nextAsset = await assetStore.getNextAsset($viewingAsset.id); if (nextAsset) { - const preloadId = await assetStore.getNextAssetId(nextAsset); - preloadId - ? await assetViewingStore.setAssetId(nextAsset, [preloadId]) - : await assetViewingStore.setAssetId(nextAsset); + const preloadAsset = await assetStore.getNextAsset(nextAsset.id); + assetViewingStore.setAsset(nextAsset, preloadAsset ? [preloadAsset] : []); } return !!nextAsset; diff --git a/web/src/lib/stores/asset-viewing.store.ts b/web/src/lib/stores/asset-viewing.store.ts index 0416d4903..bb3214995 100644 --- a/web/src/lib/stores/asset-viewing.store.ts +++ b/web/src/lib/stores/asset-viewing.store.ts @@ -7,24 +7,17 @@ function createAssetViewingStore() { const preloadAssets = writable([]); const viewState = writable(false); - const setAssetId = async (id: string, preloadIds?: string[]) => { - const data = await getAssetInfo({ id, key: getKey() }); - - if (preloadIds) { - const preloadList = []; - for (const preloadId of preloadIds) { - if (preloadId) { - const preloadAsset = await getAssetInfo({ id: preloadId, key: getKey() }); - preloadList.push(preloadAsset); - } - } - preloadAssets.set(preloadList); - } - - viewingAssetStoreState.set(data); + const setAsset = (asset: AssetResponseDto, assetsToPreload: AssetResponseDto[] = []) => { + preloadAssets.set(assetsToPreload); + viewingAssetStoreState.set(asset); viewState.set(true); }; + const setAssetId = async (id: string) => { + const asset = await getAssetInfo({ id, key: getKey() }); + setAsset(asset); + }; + const showAssetViewer = (show: boolean) => { viewState.set(show); }; @@ -40,6 +33,7 @@ function createAssetViewingStore() { subscribe: viewState.subscribe, set: viewState.set, }, + setAsset, setAssetId, showAssetViewer, }; diff --git a/web/src/lib/stores/asset.store.spec.ts b/web/src/lib/stores/asset.store.spec.ts index 55f3fe1ff..e2f702fba 100644 --- a/web/src/lib/stores/asset.store.spec.ts +++ b/web/src/lib/stores/asset.store.spec.ts @@ -286,7 +286,7 @@ describe('AssetStore', () => { }); }); - describe('getPreviousAssetId', () => { + describe('getPreviousAsset', () => { let assetStore: AssetStore; const bucketAssets: Record = { '2024-03-01T00:00:00.000Z': assetFactory.buildList(1), @@ -307,15 +307,15 @@ describe('AssetStore', () => { }); it('returns null for invalid assetId', async () => { - expect(() => assetStore.getPreviousAssetId('invalid')).not.toThrow(); - expect(await assetStore.getPreviousAssetId('invalid')).toBeNull(); + expect(() => assetStore.getPreviousAsset('invalid')).not.toThrow(); + expect(await assetStore.getPreviousAsset('invalid')).toBeNull(); }); it('returns previous assetId', async () => { await assetStore.loadBucket('2024-01-01T00:00:00.000Z', BucketPosition.Visible); const bucket = assetStore.getBucketByDate('2024-01-01T00:00:00.000Z'); - expect(await assetStore.getPreviousAssetId(bucket!.assets[1].id)).toEqual(bucket!.assets[0].id); + expect(await assetStore.getPreviousAsset(bucket!.assets[1].id)).toEqual(bucket!.assets[0]); }); it('returns previous assetId spanning multiple buckets', async () => { @@ -324,7 +324,7 @@ describe('AssetStore', () => { const bucket = assetStore.getBucketByDate('2024-02-01T00:00:00.000Z'); const previousBucket = assetStore.getBucketByDate('2024-03-01T00:00:00.000Z'); - expect(await assetStore.getPreviousAssetId(bucket!.assets[0].id)).toEqual(previousBucket!.assets[0].id); + expect(await assetStore.getPreviousAsset(bucket!.assets[0].id)).toEqual(previousBucket!.assets[0]); }); it('loads previous bucket', async () => { @@ -333,7 +333,7 @@ describe('AssetStore', () => { const loadBucketSpy = vi.spyOn(assetStore, 'loadBucket'); const bucket = assetStore.getBucketByDate('2024-02-01T00:00:00.000Z'); const previousBucket = assetStore.getBucketByDate('2024-03-01T00:00:00.000Z'); - expect(await assetStore.getPreviousAssetId(bucket!.assets[0].id)).toEqual(previousBucket!.assets[0].id); + expect(await assetStore.getPreviousAsset(bucket!.assets[0].id)).toEqual(previousBucket!.assets[0]); expect(loadBucketSpy).toBeCalledTimes(1); }); @@ -344,12 +344,12 @@ describe('AssetStore', () => { const [assetOne, assetTwo, assetThree] = assetStore.assets; assetStore.removeAssets([assetTwo.id]); - expect(await assetStore.getPreviousAssetId(assetThree.id)).toEqual(assetOne.id); + expect(await assetStore.getPreviousAsset(assetThree.id)).toEqual(assetOne); }); it('returns null when no more assets', async () => { await assetStore.loadBucket('2024-03-01T00:00:00.000Z', BucketPosition.Visible); - expect(await assetStore.getPreviousAssetId(assetStore.assets[0].id)).toBeNull(); + expect(await assetStore.getPreviousAsset(assetStore.assets[0].id)).toBeNull(); }); }); diff --git a/web/src/lib/stores/assets.store.ts b/web/src/lib/stores/assets.store.ts index 4cc92ab86..019a70401 100644 --- a/web/src/lib/stores/assets.store.ts +++ b/web/src/lib/stores/assets.store.ts @@ -451,7 +451,7 @@ export class AssetStore { this.emit(true); } - async getPreviousAssetId(assetId: string): Promise { + async getPreviousAsset(assetId: string): Promise { const info = this.getBucketInfoForAssetId(assetId); if (!info) { return null; @@ -460,7 +460,7 @@ export class AssetStore { const { bucket, assetIndex, bucketIndex } = info; if (assetIndex !== 0) { - return bucket.assets[assetIndex - 1].id; + return bucket.assets[assetIndex - 1]; } if (bucketIndex === 0) { @@ -469,10 +469,10 @@ export class AssetStore { const previousBucket = this.buckets[bucketIndex - 1]; await this.loadBucket(previousBucket.bucketDate, BucketPosition.Unknown); - return previousBucket.assets.at(-1)?.id || null; + return previousBucket.assets.at(-1) || null; } - async getNextAssetId(assetId: string): Promise { + async getNextAsset(assetId: string): Promise { const info = this.getBucketInfoForAssetId(assetId); if (!info) { return null; @@ -481,7 +481,7 @@ export class AssetStore { const { bucket, assetIndex, bucketIndex } = info; if (assetIndex !== bucket.assets.length - 1) { - return bucket.assets[assetIndex + 1].id; + return bucket.assets[assetIndex + 1]; } if (bucketIndex === this.buckets.length - 1) { @@ -490,7 +490,7 @@ export class AssetStore { const nextBucket = this.buckets[bucketIndex + 1]; await this.loadBucket(nextBucket.bucketDate, BucketPosition.Unknown); - return nextBucket.assets[0]?.id || null; + return nextBucket.assets[0] || null; } triggerUpdate() { diff --git a/web/src/lib/utils/slideshow-history.ts b/web/src/lib/utils/slideshow-history.ts index 8b34359d0..b9d5fdea7 100644 --- a/web/src/lib/utils/slideshow-history.ts +++ b/web/src/lib/utils/slideshow-history.ts @@ -1,16 +1,18 @@ +import type { AssetResponseDto } from '@immich/sdk'; + export class SlideshowHistory { - private history: string[] = []; + private history: AssetResponseDto[] = []; private index = 0; - constructor(private onChange: (assetId: string) => void) {} + constructor(private onChange: (asset: AssetResponseDto) => void) {} reset() { this.history = []; this.index = 0; } - queue(assetId: string) { - this.history.push(assetId); + queue(asset: AssetResponseDto) { + this.history.push(asset); // If we were at the end of the slideshow history, move the index to the new end if (this.index === this.history.length - 2) { diff --git a/web/src/routes/(user)/albums/[albumId]/+page.svelte b/web/src/routes/(user)/albums/[albumId]/+page.svelte index d4b665bc0..5f76012dd 100644 --- a/web/src/routes/(user)/albums/[albumId]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId]/+page.svelte @@ -82,7 +82,7 @@ export let data: PageData; - let { isViewing: showAssetViewer, setAssetId } = assetViewingStore; + let { isViewing: showAssetViewer, setAsset } = assetViewingStore; let { slideshowState, slideshowNavigation } = slideshowStore; $: album = data.album; @@ -231,7 +231,7 @@ const asset = $slideshowNavigation === SlideshowNavigation.Shuffle ? await assetStore.getRandomAsset() : assetStore.assets[0]; if (asset) { - await setAssetId(asset.id); + setAsset(asset); $slideshowState = SlideshowState.PlaySlideshow; } };