From 2ed3820127df4154e989278f6eda89ff5a58162b Mon Sep 17 00:00:00 2001 From: Min Idzelis Date: Wed, 16 Apr 2025 23:00:22 +0000 Subject: [PATCH] lite --- .devcontainer/Dockerfile | 2 +- .github/pull_request_template.md | 4 +- .github/workflows/cli.yml | 2 +- .github/workflows/docker.yml | 22 +- .github/workflows/prepare-release.yml | 6 +- .github/workflows/preview-label.yaml | 2 +- .github/workflows/static_analysis.yml | 25 +- .github/workflows/test.yml | 21 +- .github/workflows/weblate-lock.yml | 7 + README.md | 8 +- cli/package-lock.json | 238 +- cli/package.json | 6 +- docker/docker-compose.dev.yml | 2 +- docs/docs/FAQ.mdx | 2 +- .../docs/administration/backup-and-restore.md | 13 +- docs/docs/features/libraries.md | 8 +- docs/docs/features/supported-formats.md | 2 +- docs/docs/install/unraid.md | 2 +- docs/package-lock.json | 12 +- docs/src/components/svg-paths.ts | 3 +- docs/src/components/version-switcher.tsx | 5 +- docs/src/pages/index.tsx | 34 +- docs/src/pages/privacy-policy.tsx | 3 - docs/src/pages/roadmap.tsx | 7 + docs/static/archived-versions.json | 4 + e2e/package-lock.json | 639 +-- e2e/package.json | 4 +- e2e/src/api/specs/library.e2e-spec.ts | 67 +- e2e/src/api/specs/person.e2e-spec.ts | 4 +- e2e/src/web/specs/shared-link.e2e-spec.ts | 2 +- i18n/af.json | 2 +- i18n/ar.json | 21 +- i18n/bg.json | 50 +- i18n/ca.json | 38 +- i18n/cs.json | 40 +- i18n/da.json | 113 +- i18n/de.json | 26 +- i18n/el.json | 15 +- i18n/en.json | 8 +- i18n/es.json | 32 +- i18n/et.json | 37 +- i18n/fa.json | 4 +- i18n/fi.json | 8 +- i18n/fr.json | 25 +- i18n/he.json | 27 +- i18n/hi.json | 6 +- i18n/hr.json | 5 +- i18n/hu.json | 26 +- i18n/id.json | 25 +- i18n/it.json | 51 +- i18n/ja.json | 4 +- i18n/ko.json | 8 +- i18n/lt.json | 7 +- i18n/lv.json | 12 +- i18n/mr.json | 63 +- i18n/ms.json | 4 +- i18n/nb_NO.json | 40 +- i18n/nl.json | 75 +- i18n/nn.json | 31 + i18n/pl.json | 37 +- i18n/pt.json | 21 +- i18n/pt_BR.json | 21 +- i18n/ro.json | 4 +- i18n/ru.json | 26 +- i18n/sk.json | 46 +- i18n/sl.json | 22 +- i18n/sr_Cyrl.json | 21 +- i18n/sr_Latn.json | 25 +- i18n/sv.json | 42 +- i18n/ta.json | 4 +- i18n/th.json | 10 +- i18n/tr.json | 19 +- i18n/uk.json | 22 +- i18n/ur.json | 4 +- i18n/vi.json | 4 +- i18n/zh_Hant.json | 23 +- i18n/zh_SIMPLIFIED.json | 31 +- localizely.yml | 4 +- machine-learning/Dockerfile | 20 +- machine-learning/README.md | 26 +- machine-learning/poetry.lock | 3738 ----------------- machine-learning/pyproject.toml | 121 +- machine-learning/uv.lock | 2648 ++++++++++++ misc/release/pump-version.sh | 2 +- mobile/.fvmrc | 2 +- mobile/.gitignore | 3 +- mobile/analysis_options.yaml | 8 +- .../android/app/src/main/AndroidManifest.xml | 4 +- .../app/alextran/immich/BackupWorker.kt | 4 +- mobile/android/fastlane/Fastfile | 4 +- mobile/assets/i18n/{ca-CA.json => ca.json} | 349 +- mobile/assets/i18n/en-US.json | 9 +- mobile/assets/i18n/ga.json | 4 +- mobile/assets/i18n/hi-IN.json | 4 +- mobile/assets/i18n/lt-LT.json | 4 +- mobile/assets/i18n/mn-MN.json | 4 +- mobile/assets/i18n/sr-Cyrl.json | 4 +- mobile/assets/i18n/sr-Latn.json | 4 +- mobile/assets/i18n/sv-FI.json | 4 +- mobile/assets/i18n/uk-UA.json | 118 +- .../lib/immich_mobile_immich_lint.dart | 4 +- mobile/immich_lint/pubspec.lock | 156 +- mobile/ios/Podfile.lock | 69 +- mobile/ios/Runner.xcodeproj/project.pbxproj | 12 +- mobile/ios/Runner/AppDelegate.swift | 6 +- mobile/ios/Runner/Info.plist | 4 +- mobile/ios/fastlane/Fastfile | 2 +- mobile/lib/constants/locales.dart | 2 +- .../lib/domain/interfaces/exif.interface.dart | 14 + .../lib/domain/interfaces/log.interface.dart | 3 +- .../domain/interfaces/sync_api.interface.dart | 7 + .../lib/domain/interfaces/user.interface.dart | 24 + .../interfaces/user_api.repository.dart | 15 + mobile/lib/domain/models/exif.model.dart | 177 + mobile/lib/domain/models/store.model.dart | 4 +- .../domain/models/sync/sync_event.model.dart | 14 + mobile/lib/domain/models/user.model.dart | 157 + mobile/lib/domain/services/log.service.dart | 10 +- mobile/lib/domain/services/store.service.dart | 4 +- .../domain/services/sync_stream.service.dart | 49 + mobile/lib/domain/services/user.service.dart | 64 + mobile/lib/entities/album.entity.dart | 2 +- mobile/lib/entities/asset.entity.dart | 27 +- mobile/lib/entities/exif_info.entity.dart | 241 -- mobile/lib/entities/user.entity.dart | 181 - .../lib/extensions/collection_extensions.dart | 6 +- .../maplibrecontroller_extensions.dart | 31 +- mobile/lib/extensions/theme_extensions.dart | 4 +- .../infrastructure/entities/exif.entity.dart | 92 + .../entities/exif.entity.g.dart} | 6 +- .../infrastructure/entities/user.entity.dart | 73 + .../entities/user.entity.g.dart | 309 +- .../repositories/api.repository.dart | 11 + .../repositories/exif.repository.dart | 50 + .../repositories/store.repository.dart | 10 +- .../repositories/sync_api.repository.dart | 112 + .../repositories/user.repository.dart | 80 + .../repositories/user_api.repository.dart | 41 + .../infrastructure/utils/exif.converter.dart | 56 + .../infrastructure/utils/user.converter.dart | 66 + mobile/lib/interfaces/album.interface.dart | 6 +- .../lib/interfaces/exif_info.interface.dart | 14 - .../lib/interfaces/folder_api.interface.dart | 6 + mobile/lib/interfaces/partner.interface.dart | 10 +- .../lib/interfaces/partner_api.interface.dart | 8 +- mobile/lib/interfaces/user.interface.dart | 27 - mobile/lib/interfaces/user_api.interface.dart | 11 - mobile/lib/mixins/error_logger.mixin.dart | 1 + .../lib/models/activities/activity.model.dart | 6 +- .../models/folder/recursive_folder.model.dart | 11 + .../lib/models/folder/root_folder.model.dart | 11 + .../search/search_curated_content.model.dart | 13 +- ...additional_shared_user_selection.page.dart | 18 +- .../lib/pages/album/album_options.page.dart | 28 +- .../pages/album/album_shared_user_icons.dart | 6 +- .../album_shared_user_selection.page.dart | 12 +- mobile/lib/pages/albums/albums.page.dart | 8 +- mobile/lib/pages/common/activities.page.dart | 8 +- mobile/lib/pages/common/app_log.page.dart | 6 +- mobile/lib/pages/editing/edit.page.dart | 2 +- .../lib/pages/library/folder/folder.page.dart | 320 ++ mobile/lib/pages/library/library.page.dart | 17 +- .../pages/library/partner/partner.page.dart | 12 +- .../library/partner/partner_detail.page.dart | 8 +- .../people/people_collection.page.dart | 66 +- .../library/shared_link/shared_link.page.dart | 3 +- .../shared_link/shared_link_edit.page.dart | 4 +- mobile/lib/pages/photos/memory.page.dart | 107 +- mobile/lib/pages/photos/photos.page.dart | 8 +- mobile/lib/pages/search/map/map.page.dart | 9 +- .../search/map/map_location_picker.page.dart | 4 +- mobile/lib/pages/search/search.page.dart | 8 +- mobile/lib/providers/activity.provider.dart | 1 + mobile/lib/providers/activity.provider.g.dart | 4 +- .../providers/activity_service.provider.dart | 3 +- .../activity_service.provider.g.dart | 6 +- .../activity_statistics.provider.dart | 1 + .../activity_statistics.provider.g.dart | 4 +- .../lib/providers/album/album.provider.dart | 8 +- .../album_sort_by_options.provider.g.dart | 2 +- .../album/current_album.provider.g.dart | 2 +- .../suggested_shared_users.provider.dart | 14 +- mobile/lib/providers/api.provider.dart | 3 +- mobile/lib/providers/api.provider.g.dart | 6 +- .../providers/app_life_cycle.provider.dart | 7 +- .../lib/providers/app_settings.provider.dart | 4 +- .../providers/app_settings.provider.g.dart | 6 +- mobile/lib/providers/asset.provider.dart | 9 +- .../asset_viewer/asset_people.provider.g.dart | 4 +- .../asset_viewer/asset_stack.provider.dart | 2 +- .../asset_viewer/asset_stack.provider.g.dart | 6 +- .../current_asset.provider.g.dart | 2 +- mobile/lib/providers/auth.provider.dart | 17 +- .../backup_verification.provider.g.dart | 2 +- mobile/lib/providers/folder.provider.dart | 62 + .../lib/providers/immich_logo_provider.dart | 3 +- .../lib/providers/immich_logo_provider.g.dart | 6 +- .../providers/infrastructure/db.provider.dart | 3 +- .../infrastructure/db.provider.g.dart | 6 +- .../infrastructure/exif.provider.dart | 11 + .../infrastructure/exif.provider.g.dart | 27 + .../infrastructure/store.provider.dart | 9 +- .../infrastructure/store.provider.g.dart | 26 +- .../infrastructure/sync_stream.provider.dart | 24 + .../infrastructure/user.provider.dart | 27 + .../infrastructure/user.provider.g.dart | 60 + .../providers/map/map_marker.provider.dart | 3 +- .../providers/map/map_marker.provider.g.dart | 6 +- .../providers/map/map_service.provider.dart | 4 +- .../providers/map/map_service.provider.g.dart | 6 +- .../providers/map/map_state.provider.g.dart | 2 +- mobile/lib/providers/partner.provider.dart | 24 +- .../search/paginated_search.provider.dart | 2 +- .../search/paginated_search.provider.g.dart | 6 +- .../lib/providers/search/people.provider.dart | 7 +- .../providers/search/people.provider.g.dart | 14 +- .../search/search_filter.provider.dart | 3 +- .../search/search_filter.provider.g.dart | 6 +- .../upload_profile_image.provider.dart | 17 +- mobile/lib/providers/user.provider.dart | 11 +- .../repositories/activity_api.repository.dart | 4 +- mobile/lib/repositories/album.repository.dart | 21 +- .../repositories/album_api.repository.dart | 11 +- .../repositories/album_media.repository.dart | 3 +- mobile/lib/repositories/asset.repository.dart | 2 +- .../repositories/asset_media.repository.dart | 7 +- mobile/lib/repositories/auth.repository.dart | 4 +- .../repositories/exif_info.repository.dart | 36 - .../repositories/folder_api.repository.dart | 43 + .../lib/repositories/partner.repository.dart | 50 +- .../repositories/partner_api.repository.dart | 15 +- .../lib/repositories/timeline.repository.dart | 2 +- mobile/lib/repositories/user.repository.dart | 73 - .../lib/repositories/user_api.repository.dart | 40 - mobile/lib/routing/router.dart | 9 +- mobile/lib/routing/router.gr.dart | 51 +- .../lib/routing/tab_navigation_observer.dart | 4 +- mobile/lib/services/album.service.dart | 27 +- mobile/lib/services/api.service.dart | 2 + mobile/lib/services/asset.service.dart | 76 +- mobile/lib/services/background.service.dart | 30 +- .../services/backup_verification.service.dart | 17 +- mobile/lib/services/entity.service.dart | 12 +- mobile/lib/services/exif.service.dart | 10 +- mobile/lib/services/folder.service.dart | 132 + mobile/lib/services/oauth.service.dart | 4 +- mobile/lib/services/partner.service.dart | 45 +- mobile/lib/services/person.service.dart | 3 +- mobile/lib/services/person.service.g.dart | 6 +- mobile/lib/services/sync.service.dart | 180 +- mobile/lib/services/timeline.service.dart | 35 +- mobile/lib/services/trash.service.dart | 25 +- mobile/lib/services/user.service.dart | 100 - mobile/lib/theme/theme_data.dart | 6 +- mobile/lib/utils/bootstrap.dart | 4 +- mobile/lib/utils/migration.dart | 4 +- mobile/lib/utils/provider_utils.dart | 4 +- mobile/lib/utils/selection_handlers.dart | 2 +- .../lib/widgets/activities/activity_tile.dart | 2 +- .../widgets/album/album_thumbnail_card.dart | 2 +- .../widgets/album/album_viewer_appbar.dart | 7 +- .../asset_grid/immich_asset_grid_view.dart | 21 +- .../widgets/asset_grid/thumbnail_image.dart | 4 +- .../asset_viewer/bottom_gallery_bar.dart | 20 +- .../asset_viewer/description_input.dart | 6 +- .../detail_panel/asset_details.dart | 6 +- .../detail_panel/asset_location.dart | 4 +- .../detail_panel/camera_info.dart | 2 +- .../asset_viewer/detail_panel/exif_map.dart | 2 +- .../detail_panel/people_info.dart | 27 +- .../widgets/asset_viewer/gallery_app_bar.dart | 41 +- .../asset_viewer/top_control_app_bar.dart | 17 + .../app_bar_dialog/app_bar_profile_info.dart | 5 +- .../app_bar_dialog/app_bar_server_info.dart | 2 +- .../widgets/common/dropdown_search_menu.dart | 2 +- mobile/lib/widgets/common/immich_app_bar.dart | 2 +- mobile/lib/widgets/common/immich_toast.dart | 2 +- .../widgets/common/scaffold_error_body.dart | 3 +- mobile/lib/widgets/common/user_avatar.dart | 6 +- .../widgets/common/user_circle_avatar.dart | 10 +- mobile/lib/widgets/map/map_thumbnail.dart | 6 +- .../widgets/memories/memory_bottom_info.dart | 2 +- mobile/lib/widgets/memories/memory_card.dart | 4 +- mobile/lib/widgets/memories/memory_lane.dart | 4 +- .../widgets/search/curated_people_row.dart | 22 +- .../search/search_filter/people_picker.dart | 2 +- .../search_filter/search_filter_chip.dart | 2 +- .../search/thumbnail_with_info_container.dart | 4 +- .../external_network_preference.dart | 4 +- .../local_network_preference.dart | 2 +- .../primary_color_setting.dart | 2 +- .../widgets/shared_link/shared_link_item.dart | 4 +- mobile/openapi/README.md | 8 +- mobile/openapi/lib/api.dart | 5 + mobile/openapi/lib/api/activities_api.dart | 16 +- mobile/openapi/lib/api/albums_api.dart | 44 +- mobile/openapi/lib/api/api_keys_api.dart | 20 +- mobile/openapi/lib/api/assets_api.dart | 68 +- .../openapi/lib/api/authentication_api.dart | 20 +- mobile/openapi/lib/api/deprecated_api.dart | 4 +- mobile/openapi/lib/api/download_api.dart | 8 +- mobile/openapi/lib/api/duplicates_api.dart | 4 +- mobile/openapi/lib/api/faces_api.dart | 16 +- mobile/openapi/lib/api/file_reports_api.dart | 12 +- mobile/openapi/lib/api/jobs_api.dart | 12 +- mobile/openapi/lib/api/libraries_api.dart | 32 +- mobile/openapi/lib/api/map_api.dart | 8 +- mobile/openapi/lib/api/memories_api.dart | 28 +- mobile/openapi/lib/api/notifications_api.dart | 8 +- mobile/openapi/lib/api/o_auth_api.dart | 20 +- mobile/openapi/lib/api/partners_api.dart | 16 +- mobile/openapi/lib/api/people_api.dart | 36 +- mobile/openapi/lib/api/search_api.dart | 32 +- mobile/openapi/lib/api/server_api.dart | 52 +- mobile/openapi/lib/api/sessions_api.dart | 12 +- mobile/openapi/lib/api/shared_links_api.dart | 32 +- mobile/openapi/lib/api/stacks_api.dart | 24 +- mobile/openapi/lib/api/sync_api.dart | 24 +- mobile/openapi/lib/api/system_config_api.dart | 16 +- .../openapi/lib/api/system_metadata_api.dart | 12 +- mobile/openapi/lib/api/tags_api.dart | 36 +- mobile/openapi/lib/api/timeline_api.dart | 143 +- mobile/openapi/lib/api/trash_api.dart | 12 +- mobile/openapi/lib/api/users_admin_api.dart | 32 +- mobile/openapi/lib/api/users_api.dart | 50 +- mobile/openapi/lib/api/view_api.dart | 8 +- mobile/openapi/lib/api_client.dart | 10 + .../lite_time_bucket_asset_response_dto.dart | 158 + .../model/lite_time_bucket_response_dto.dart | 107 + mobile/openapi/lib/model/manual_job_name.dart | 3 + .../lib/model/sync_asset_delete_v1.dart | 99 + .../openapi/lib/model/sync_asset_exif_v1.dart | 387 ++ mobile/openapi/lib/model/sync_asset_v1.dart | 279 ++ .../openapi/lib/model/sync_entity_type.dart | 18 + .../openapi/lib/model/sync_request_type.dart | 12 + mobile/pubspec.lock | 704 ++-- mobile/pubspec.yaml | 139 +- mobile/test/domain/service.mock.dart | 7 + .../domain/services/user_service_test.dart | 133 + mobile/test/fixtures/album.stub.dart | 5 +- mobile/test/fixtures/exif.stub.dart | 18 + mobile/test/fixtures/user.stub.dart | 34 +- .../repositories/exif_repository_test.dart | 50 + .../repositories/store_repository_test.dart | 4 +- .../test/infrastructure/repository.mock.dart | 7 + .../activity/activities_page_test.dart | 4 +- .../extensions/asset_extensions_test.dart | 4 +- mobile/test/modules/shared/shared_mocks.dart | 6 +- .../modules/shared/sync_service_test.dart | 49 +- mobile/test/repository.mocks.dart | 11 +- mobile/test/service.mocks.dart | 3 - mobile/test/services/album.service_test.dart | 12 +- mobile/test/services/entity.service_test.dart | 22 +- mobile/test/test_utils.dart | 4 +- open-api/bin/generate-open-api.sh | 6 +- open-api/bin/native_class.mustache | 301 ++ open-api/immich-openapi-specs.json | 428 +- open-api/templates/mobile/api.mustache | 194 + open-api/templates/mobile/api.mustache.patch | 29 + open-api/typescript-sdk/package-lock.json | 18 +- open-api/typescript-sdk/package.json | 4 +- open-api/typescript-sdk/src/fetch-client.ts | 69 +- openapi/package-lock.json | 6 + readme_i18n/README_ar_JO.md | 2 +- readme_i18n/README_ca_ES.md | 2 +- readme_i18n/README_de_DE.md | 2 +- readme_i18n/README_es_ES.md | 2 +- readme_i18n/README_fr_FR.md | 2 +- readme_i18n/README_it_IT.md | 2 +- readme_i18n/README_ja_JP.md | 2 +- readme_i18n/README_ko_KR.md | 2 +- readme_i18n/README_nl_NL.md | 2 +- readme_i18n/README_pt_BR.md | 2 +- readme_i18n/README_ru_RU.md | 2 +- readme_i18n/README_sv_SE.md | 2 +- readme_i18n/README_th_TH.md | 2 +- readme_i18n/README_tr_TR.md | 2 +- readme_i18n/README_uk_UA.md | 2 +- readme_i18n/README_vi_VN.md | 2 +- readme_i18n/README_zh_CN.md | 2 +- server/Dockerfile | 4 +- server/eslint.config.mjs | 1 + server/package-lock.json | 3450 ++++++++------- server/package.json | 12 +- server/src/app.module.ts | 17 +- server/src/bin/sync-sql.ts | 17 +- server/src/constants.ts | 4 - server/src/controllers/api-key.controller.ts | 4 +- server/src/controllers/timeline.controller.ts | 34 +- server/src/database.ts | 118 +- server/src/db.d.ts | 19 +- server/src/dtos/asset-response.dto.ts | 7 +- server/src/dtos/library.dto.ts | 4 +- server/src/dtos/person.dto.ts | 5 +- server/src/dtos/sync.dto.ts | 67 +- server/src/dtos/time-bucket.dto.ts | 60 +- server/src/dtos/user.dto.ts | 12 + server/src/entities/asset-audit.entity.ts | 19 + server/src/entities/asset.entity.ts | 123 +- server/src/entities/exif.entity.ts | 9 +- server/src/entities/index.ts | 61 - server/src/entities/library.entity.ts | 2 +- server/src/entities/move.entity.ts | 2 +- server/src/entities/partner.entity.ts | 1 + server/src/entities/person.entity.ts | 2 +- server/src/enum.ts | 19 +- .../1645130759468-CreateUserTable.ts | 1 + .../1741179334403-MoveHistoryUuidEntityId.ts | 26 + .../1741191762113-AssetAuditTable.ts | 37 + ...328985-FixAssetAndUserCascadeConditions.ts | 50 + .../1741281344519-AddExifUpdateId.ts | 25 + server/src/queries/asset.repository.sql | 8 +- server/src/queries/database.repository.sql | 21 + server/src/queries/library.repository.sql | 87 +- server/src/queries/move.repository.sql | 19 + server/src/repositories/api-key.repository.ts | 4 +- server/src/repositories/asset.repository.ts | 289 +- .../src/repositories/database.repository.ts | 176 +- .../src/repositories/download.repository.ts | 36 + server/src/repositories/index.ts | 2 + server/src/repositories/library.repository.ts | 68 +- .../src/repositories/metadata.repository.ts | 10 +- server/src/repositories/move.repository.ts | 28 +- .../notification.repository.spec.ts | 5 +- server/src/repositories/partner.repository.ts | 69 +- server/src/repositories/person.repository.ts | 19 +- server/src/repositories/queries.vb | 1 + server/src/repositories/search.repository.ts | 4 +- .../repositories/storage.repository.spec.ts | 5 +- server/src/repositories/sync.repository.ts | 107 +- server/src/repositories/user.repository.ts | 14 +- server/src/services/activity.service.spec.ts | 155 +- server/src/services/album.service.spec.ts | 5 + server/src/services/api-key.service.spec.ts | 120 +- server/src/services/api-key.service.ts | 2 +- server/src/services/asset.service.spec.ts | 83 +- server/src/services/asset.service.ts | 35 +- server/src/services/audit.service.spec.ts | 3 + server/src/services/auth.service.spec.ts | 83 +- server/src/services/auth.service.ts | 4 +- server/src/services/backup.service.spec.ts | 5 + server/src/services/base.service.ts | 2 + server/src/services/cli.service.spec.ts | 4 + server/src/services/database.service.spec.ts | 46 - server/src/services/database.service.ts | 26 - server/src/services/download.service.spec.ts | 121 +- server/src/services/download.service.ts | 108 +- server/src/services/index.ts | 4 +- server/src/services/job.service.spec.ts | 8 + server/src/services/job.service.ts | 4 + server/src/services/library.service.spec.ts | 1056 ++--- server/src/services/library.service.ts | 532 ++- server/src/services/map.service.spec.ts | 11 +- server/src/services/memory.service.spec.ts | 224 +- server/src/services/memory.service.ts | 3 +- server/src/services/metadata.service.spec.ts | 117 +- server/src/services/metadata.service.ts | 215 +- .../src/services/notification.service.spec.ts | 3 + server/src/services/partner.service.spec.ts | 88 +- server/src/services/partner.service.ts | 16 +- server/src/services/person.service.spec.ts | 18 +- server/src/services/search.service.spec.ts | 20 + server/src/services/session.service.spec.ts | 3 + .../src/services/shared-link.service.spec.ts | 14 + server/src/services/stack.service.spec.ts | 2 + .../services/storage-template.service.spec.ts | 326 +- .../src/services/storage-template.service.ts | 79 +- server/src/services/sync.service.spec.ts | 11 +- server/src/services/sync.service.ts | 100 +- server/src/services/tag.service.spec.ts | 12 + server/src/services/timeline.service.spec.ts | 1 + server/src/services/timeline.service.ts | 21 +- server/src/services/trash.service.spec.ts | 1 + server/src/services/version.service.spec.ts | 6 + server/src/types.ts | 37 +- server/src/utils/asset.util.ts | 14 + server/src/utils/database.ts | 37 +- server/src/utils/date.ts | 3 + server/src/utils/file.ts | 22 +- server/src/utils/misc.spec.ts | 18 +- server/src/utils/misc.ts | 34 + server/src/utils/pagination.ts | 1 + server/test/factory.ts | 21 +- server/test/fixtures/activity.stub.ts | 42 - server/test/fixtures/api-key.stub.ts | 20 - server/test/fixtures/asset.stub.ts | 20 +- server/test/fixtures/library.stub.ts | 77 - server/test/fixtures/memory.stub.ts | 34 - server/test/fixtures/partner.stub.ts | 23 - server/test/global-setup.js | 3 - server/test/medium/globalSetup.ts | 1 - .../test/medium/specs/audit.database.spec.ts | 74 + .../medium/specs/metadata.service.spec.ts | 20 +- server/test/medium/specs/sync.service.spec.ts | 447 +- .../repositories/activity.repository.mock.ts | 12 - .../album-user.repository.mock.ts | 11 - .../repositories/album.repository.mock.ts | 25 - .../repositories/api-key.repository.mock.ts | 14 - .../repositories/asset.repository.mock.ts | 8 + .../repositories/audit.repository.mock.ts | 10 - .../test/repositories/cron.repository.mock.ts | 10 - .../repositories/database.repository.mock.ts | 2 - .../repositories/event.repository.mock.ts | 17 - .../repositories/library.repository.mock.ts | 16 - .../repositories/logger.repository.mock.ts | 23 - .../machine-learning.repository.mock.ts | 11 - .../test/repositories/map.repository.mock.ts | 11 - .../repositories/memory.repository.mock.ts | 17 - .../test/repositories/move.repository.mock.ts | 12 - .../notification.repository.mock.ts | 11 - .../repositories/oauth.repository.mock.ts | 12 - .../repositories/partner.repository.mock.ts | 13 - .../repositories/person.repository.mock.ts | 41 - .../repositories/process.repository.mock.ts | 9 - .../repositories/search.repository.mock.ts | 24 - .../server-info.repository.mock.ts | 10 - .../repositories/session.repository.mock.ts | 14 - .../shared-link.repository.mock.ts | 14 - .../repositories/stack.repository.mock.ts | 14 - .../test/repositories/sync.repository.mock.ts | 15 - .../test/repositories/tag.repository.mock.ts | 23 - .../repositories/trash.repository.mock.ts | 12 - .../test/repositories/user.repository.mock.ts | 26 - .../version-history.repository.mock.ts | 11 - .../test/repositories/view.repository.mock.ts | 10 - server/test/small.factory.ts | 202 + server/test/utils.ts | 139 +- server/test/vitest.config.mjs | 3 + web/package-lock.json | 354 +- web/package.json | 11 +- web/src/app.css | 25 +- web/src/lib/actions/intersection-observer.ts | 12 +- web/src/lib/actions/resize-observer.ts | 2 +- .../lib/assets/immich-logo-inline-dark.svg | 63 - .../lib/assets/immich-logo-inline-light.svg | 62 - .../lib/assets/immich-logo-stacked-dark.svg | 66 - .../lib/assets/immich-logo-stacked-light.svg | 66 - web/src/lib/assets/immich-logo.json | 3 - web/src/lib/assets/immich-logo.svg | 29 - web/src/lib/assets/svg-paths.ts | 3 +- .../admin-page/restore-dialogue.svelte | 2 +- .../machine-learning-settings.svelte | 22 +- .../notification-settings.svelte | 35 +- .../template-settings.svelte | 23 +- .../components/album-page/album-viewer.svelte | 10 +- .../asset-viewer/asset-viewer.svelte | 2 +- .../editor/crop-tool/crop-preset.svelte | 6 +- .../asset-viewer/editor/editor-panel.svelte | 2 +- .../asset-viewer/photo-viewer.svelte | 2 +- .../thumbnail/__test__/thumbnail.spec.ts | 63 + .../assets/thumbnail/image-thumbnail.svelte | 17 +- .../assets/thumbnail/thumbnail.svelte | 413 +- .../assets/thumbnail/video-thumbnail.svelte | 48 +- .../faces-page/assign-face-side-panel.svelte | 2 +- .../components/faces-page/people-card.svelte | 24 +- .../faces-page/people-infinite-scroll.svelte | 2 +- .../faces-page/person-side-panel.svelte | 2 +- .../lib/components/forms/api-key-form.svelte | 6 +- .../components/forms/api-key-secret.svelte | 8 +- .../components/forms/create-user-form.svelte | 4 +- .../components/forms/edit-album-form.svelte | 13 +- .../components/forms/edit-user-form.svelte | 12 +- .../library-exclusion-pattern-form.svelte | 12 +- .../forms/library-import-path-form.svelte | 10 +- .../forms/library-import-paths-form.svelte | 40 +- .../forms/library-rename-form.svelte | 30 +- .../forms/library-user-picker-form.svelte | 14 +- .../components/layouts/AuthPageLayout.svelte | 2 +- .../ErrorLayout.svelte} | 0 .../map-page/map-settings-modal.svelte | 10 +- .../memory-page/memory-viewer.svelte | 8 +- .../photos-page/actions/archive-action.svelte | 4 +- .../actions/favorite-action.svelte | 4 +- .../photos-page/actions/restore-assets.svelte | 15 +- .../actions/select-all-assets.svelte | 2 +- .../photos-page/asset-date-group.svelte | 295 +- .../components/photos-page/asset-grid.svelte | 569 +-- .../photos-page/measure-date-group.svelte | 91 - .../components/photos-page/memory-lane.svelte | 15 +- .../components/photos-page/skeleton.svelte | 16 +- .../individual-shared-viewer.svelte | 2 +- .../shared-components/control-app-bar.svelte | 4 +- .../create-shared-link-modal.svelte | 10 +- .../dialog/confirm-dialog.svelte | 9 +- .../gallery-viewer/gallery-viewer.svelte | 227 +- .../help-and-feedback-modal.svelte | 4 +- .../shared-components/immich-logo.svelte | 20 +- .../shared-components/map/map.svelte | 4 + .../navigation-bar/navigation-bar.svelte | 55 +- .../scrubber/scrubber.svelte | 97 +- .../search-bar/search-bar.svelte | 62 +- .../search-bar/search-filter-modal.svelte | 21 +- .../settings/setting-buttons-row.svelte | 10 +- .../shared-components/show-shortcuts.svelte | 1 + .../side-bar/purchase-info.svelte | 57 +- .../shared-components/tree/breadcrumbs.svelte | 2 +- .../change-password-settings.svelte | 14 +- .../user-settings-page/device-list.svelte | 4 +- .../download-settings.svelte | 17 +- .../feature-settings.svelte | 13 +- .../notifications-settings.svelte | 4 +- .../user-settings-page/oauth-settings.svelte | 8 +- .../partner-selection-modal.svelte | 4 +- .../partner-settings.svelte | 12 +- .../user-api-key-list.svelte | 12 +- .../user-profile-settings.svelte | 13 +- .../user-purchase-settings.svelte | 10 +- .../duplicates-compare-control.svelte | 2 +- web/src/lib/constants.ts | 35 +- .../lib/stores/asset-interaction.svelte.ts | 16 +- ...set.store.spec.ts => assets-store.spec.ts} | 244 +- web/src/lib/stores/assets-store.svelte.ts | 1226 ++++++ web/src/lib/stores/assets.store.ts | 914 ---- web/src/lib/stores/timeline.store.ts | 3 - web/src/lib/utils/asset-store-task-manager.ts | 465 -- web/src/lib/utils/asset-utils.ts | 4 +- web/src/lib/utils/cancellable-task.ts | 135 + web/src/lib/utils/executor-queue.spec.ts | 12 +- web/src/lib/utils/idle-callback-support.ts | 22 - web/src/lib/utils/keyed-priority-queue.ts | 50 - web/src/lib/utils/layout-utils.ts | 123 + web/src/lib/utils/priority-queue.ts | 21 - web/src/lib/utils/timeline-util.ts | 103 +- web/src/lib/utils/tunables.ts | 46 +- .../[[assetId=id]]/+page.svelte | 285 +- .../[[assetId=id]]/+page.svelte | 29 +- .../[[assetId=id]]/+page.svelte | 11 +- .../[[assetId=id]]/+page.svelte | 16 +- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 10 +- web/src/routes/(user)/people/+page.svelte | 238 +- .../[[assetId=id]]/+page.svelte | 57 +- .../(user)/photos/[[assetId=id]]/+page.svelte | 20 +- .../[[assetId=id]]/+page.svelte | 144 +- .../[[assetId=id]]/+page.svelte | 12 +- .../[[assetId=id]]/+page.svelte | 20 +- .../[[assetId=id]]/+page.svelte | 1 - web/src/routes/+error.svelte | 4 +- web/src/routes/+layout.svelte | 10 +- web/src/routes/+page.svelte | 2 +- web/src/routes/admin/jobs-status/+page.svelte | 1 + .../admin/library-management/+page.svelte | 14 +- .../routes/admin/user-management/+page.svelte | 8 +- .../routes/auth/change-password/+page.svelte | 8 +- web/src/routes/auth/onboarding/+page.svelte | 6 +- web/src/routes/link/+page.ts | 14 +- web/static/immich-logo-no-bg.svg | 29 - web/static/immich-logo.svg | 8 - web/tsconfig.json | 2 +- web/vite.config.js | 3 + 650 files changed, 20994 insertions(+), 15747 deletions(-) delete mode 100644 machine-learning/poetry.lock create mode 100644 machine-learning/uv.lock rename mobile/assets/i18n/{ca-CA.json => ca.json} (63%) create mode 100644 mobile/lib/domain/interfaces/exif.interface.dart create mode 100644 mobile/lib/domain/interfaces/sync_api.interface.dart create mode 100644 mobile/lib/domain/interfaces/user.interface.dart create mode 100644 mobile/lib/domain/interfaces/user_api.repository.dart create mode 100644 mobile/lib/domain/models/exif.model.dart create mode 100644 mobile/lib/domain/models/sync/sync_event.model.dart create mode 100644 mobile/lib/domain/models/user.model.dart create mode 100644 mobile/lib/domain/services/sync_stream.service.dart create mode 100644 mobile/lib/domain/services/user.service.dart delete mode 100644 mobile/lib/entities/exif_info.entity.dart delete mode 100644 mobile/lib/entities/user.entity.dart create mode 100644 mobile/lib/infrastructure/entities/exif.entity.dart rename mobile/lib/{entities/exif_info.entity.g.dart => infrastructure/entities/exif.entity.g.dart} (99%) create mode 100644 mobile/lib/infrastructure/entities/user.entity.dart rename mobile/lib/{ => infrastructure}/entities/user.entity.g.dart (86%) create mode 100644 mobile/lib/infrastructure/repositories/api.repository.dart create mode 100644 mobile/lib/infrastructure/repositories/exif.repository.dart create mode 100644 mobile/lib/infrastructure/repositories/sync_api.repository.dart create mode 100644 mobile/lib/infrastructure/repositories/user.repository.dart create mode 100644 mobile/lib/infrastructure/repositories/user_api.repository.dart create mode 100644 mobile/lib/infrastructure/utils/exif.converter.dart create mode 100644 mobile/lib/infrastructure/utils/user.converter.dart delete mode 100644 mobile/lib/interfaces/exif_info.interface.dart create mode 100644 mobile/lib/interfaces/folder_api.interface.dart delete mode 100644 mobile/lib/interfaces/user.interface.dart delete mode 100644 mobile/lib/interfaces/user_api.interface.dart create mode 100644 mobile/lib/models/folder/recursive_folder.model.dart create mode 100644 mobile/lib/models/folder/root_folder.model.dart create mode 100644 mobile/lib/pages/library/folder/folder.page.dart create mode 100644 mobile/lib/providers/folder.provider.dart create mode 100644 mobile/lib/providers/infrastructure/exif.provider.dart create mode 100644 mobile/lib/providers/infrastructure/exif.provider.g.dart create mode 100644 mobile/lib/providers/infrastructure/sync_stream.provider.dart create mode 100644 mobile/lib/providers/infrastructure/user.provider.dart create mode 100644 mobile/lib/providers/infrastructure/user.provider.g.dart delete mode 100644 mobile/lib/repositories/exif_info.repository.dart create mode 100644 mobile/lib/repositories/folder_api.repository.dart delete mode 100644 mobile/lib/repositories/user.repository.dart delete mode 100644 mobile/lib/repositories/user_api.repository.dart create mode 100644 mobile/lib/services/folder.service.dart delete mode 100644 mobile/lib/services/user.service.dart create mode 100644 mobile/openapi/lib/model/lite_time_bucket_asset_response_dto.dart create mode 100644 mobile/openapi/lib/model/lite_time_bucket_response_dto.dart create mode 100644 mobile/openapi/lib/model/sync_asset_delete_v1.dart create mode 100644 mobile/openapi/lib/model/sync_asset_exif_v1.dart create mode 100644 mobile/openapi/lib/model/sync_asset_v1.dart create mode 100644 mobile/test/domain/service.mock.dart create mode 100644 mobile/test/domain/services/user_service_test.dart create mode 100644 mobile/test/fixtures/exif.stub.dart create mode 100644 mobile/test/infrastructure/repositories/exif_repository_test.dart create mode 100644 open-api/bin/native_class.mustache create mode 100644 open-api/templates/mobile/api.mustache create mode 100644 open-api/templates/mobile/api.mustache.patch create mode 100644 openapi/package-lock.json create mode 100644 server/src/entities/asset-audit.entity.ts delete mode 100644 server/src/entities/index.ts create mode 100644 server/src/migrations/1741179334403-MoveHistoryUuidEntityId.ts create mode 100644 server/src/migrations/1741191762113-AssetAuditTable.ts create mode 100644 server/src/migrations/1741280328985-FixAssetAndUserCascadeConditions.ts create mode 100644 server/src/migrations/1741281344519-AddExifUpdateId.ts create mode 100644 server/src/queries/database.repository.sql create mode 100644 server/src/repositories/download.repository.ts create mode 100644 server/src/repositories/queries.vb create mode 100644 server/src/utils/date.ts delete mode 100644 server/test/fixtures/activity.stub.ts delete mode 100644 server/test/fixtures/api-key.stub.ts delete mode 100644 server/test/fixtures/library.stub.ts delete mode 100644 server/test/fixtures/memory.stub.ts delete mode 100644 server/test/fixtures/partner.stub.ts delete mode 100644 server/test/global-setup.js create mode 100644 server/test/medium/specs/audit.database.spec.ts delete mode 100644 server/test/repositories/activity.repository.mock.ts delete mode 100644 server/test/repositories/album-user.repository.mock.ts delete mode 100644 server/test/repositories/album.repository.mock.ts delete mode 100644 server/test/repositories/api-key.repository.mock.ts delete mode 100644 server/test/repositories/audit.repository.mock.ts delete mode 100644 server/test/repositories/cron.repository.mock.ts delete mode 100644 server/test/repositories/event.repository.mock.ts delete mode 100644 server/test/repositories/library.repository.mock.ts delete mode 100644 server/test/repositories/logger.repository.mock.ts delete mode 100644 server/test/repositories/machine-learning.repository.mock.ts delete mode 100644 server/test/repositories/map.repository.mock.ts delete mode 100644 server/test/repositories/memory.repository.mock.ts delete mode 100644 server/test/repositories/move.repository.mock.ts delete mode 100644 server/test/repositories/notification.repository.mock.ts delete mode 100644 server/test/repositories/oauth.repository.mock.ts delete mode 100644 server/test/repositories/partner.repository.mock.ts delete mode 100644 server/test/repositories/person.repository.mock.ts delete mode 100644 server/test/repositories/process.repository.mock.ts delete mode 100644 server/test/repositories/search.repository.mock.ts delete mode 100644 server/test/repositories/server-info.repository.mock.ts delete mode 100644 server/test/repositories/session.repository.mock.ts delete mode 100644 server/test/repositories/shared-link.repository.mock.ts delete mode 100644 server/test/repositories/stack.repository.mock.ts delete mode 100644 server/test/repositories/sync.repository.mock.ts delete mode 100644 server/test/repositories/tag.repository.mock.ts delete mode 100644 server/test/repositories/trash.repository.mock.ts delete mode 100644 server/test/repositories/user.repository.mock.ts delete mode 100644 server/test/repositories/version-history.repository.mock.ts delete mode 100644 server/test/repositories/view.repository.mock.ts create mode 100644 server/test/small.factory.ts delete mode 100644 web/src/lib/assets/immich-logo-inline-dark.svg delete mode 100644 web/src/lib/assets/immich-logo-inline-light.svg delete mode 100644 web/src/lib/assets/immich-logo-stacked-dark.svg delete mode 100644 web/src/lib/assets/immich-logo-stacked-light.svg delete mode 100644 web/src/lib/assets/immich-logo.json delete mode 100644 web/src/lib/assets/immich-logo.svg create mode 100644 web/src/lib/components/assets/thumbnail/__test__/thumbnail.spec.ts rename web/src/lib/components/{error.svelte => layouts/ErrorLayout.svelte} (100%) delete mode 100644 web/src/lib/components/photos-page/measure-date-group.svelte rename web/src/lib/stores/{asset.store.spec.ts => assets-store.spec.ts} (60%) create mode 100644 web/src/lib/stores/assets-store.svelte.ts delete mode 100644 web/src/lib/stores/assets.store.ts delete mode 100644 web/src/lib/stores/timeline.store.ts delete mode 100644 web/src/lib/utils/asset-store-task-manager.ts create mode 100644 web/src/lib/utils/cancellable-task.ts delete mode 100644 web/src/lib/utils/idle-callback-support.ts delete mode 100644 web/src/lib/utils/keyed-priority-queue.ts create mode 100644 web/src/lib/utils/layout-utils.ts delete mode 100644 web/src/lib/utils/priority-queue.ts delete mode 100644 web/static/immich-logo-no-bg.svg delete mode 100644 web/static/immich-logo.svg diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index a107c1ac3a..9dd57a5ec8 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -4,7 +4,7 @@ FROM ${BASEIMAGE} # Flutter SDK # https://flutter.dev/docs/development/tools/sdk/releases?tab=linux ENV FLUTTER_CHANNEL="stable" -ENV FLUTTER_VERSION="3.24.5" +ENV FLUTTER_VERSION="3.29.1" ENV FLUTTER_HOME=/flutter ENV PATH=${PATH}:${FLUTTER_HOME}/bin diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 5d4290fd7b..aa756a7d08 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -32,5 +32,5 @@ The `/api/something` endpoint is now `/api/something-else` - [ ] I have confirmed that any new dependencies are strictly necessary. - [ ] I have written tests for new code (if applicable) - [ ] I have followed naming conventions/patterns in the surrounding code -- [ ] All code in `src/services` uses repositories implementations for database calls, filesystem operations, etc. -- [ ] All code in `src/repositories/` is pretty basic/simple and does not have any immich specific logic (that belongs in `src/services`) +- [ ] All code in `src/services/` uses repositories implementations for database calls, filesystem operations, etc. +- [ ] All code in `src/repositories/` is pretty basic/simple and does not have any immich specific logic (that belongs in `src/services/`) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 7cfd75c61b..1243a81105 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -56,7 +56,7 @@ jobs: uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v3.5.0 + uses: docker/setup-qemu-action@v3.6.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.10.0 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 96970fa460..12f3410310 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -255,19 +255,20 @@ jobs: flavor: | # Disable latest tag latest=false + suffix=${{ matrix.suffix }} images: | name=${{ env.GHCR_REPO }} name=${{ env.DOCKER_REPO }},enable=${{ github.event_name == 'release' }} tags: | # Tag with branch name - type=ref,event=branch,suffix=${{ matrix.suffix }} + type=ref,event=branch # Tag with pr-number - type=ref,event=pr,suffix=${{ matrix.suffix }} + type=ref,event=pr # Tag with long commit sha hash - type=sha,format=long,prefix=commit-,suffix=${{ matrix.suffix }} + type=sha,format=long,prefix=commit- # Tag with git tag on release - type=ref,event=tag,suffix=${{ matrix.suffix }} - type=raw,value=release,enable=${{ github.event_name == 'release' }},suffix=${{ matrix.suffix }} + type=ref,event=tag + type=raw,value=release,enable=${{ github.event_name == 'release' }} - name: Create manifest list and push working-directory: ${{ runner.temp }}/digests @@ -408,19 +409,20 @@ jobs: flavor: | # Disable latest tag latest=false + suffix=${{ matrix.suffix }} images: | name=${{ env.GHCR_REPO }} name=${{ env.DOCKER_REPO }},enable=${{ github.event_name == 'release' }} tags: | # Tag with branch name - type=ref,event=branch,suffix=${{ matrix.suffix }} + type=ref,event=branch # Tag with pr-number - type=ref,event=pr,suffix=${{ matrix.suffix }} + type=ref,event=pr # Tag with long commit sha hash - type=sha,format=long,prefix=commit-,suffix=${{ matrix.suffix }} + type=sha,format=long,prefix=commit- # Tag with git tag on release - type=ref,event=tag,suffix=${{ matrix.suffix }} - type=raw,value=release,enable=${{ github.event_name == 'release' }},suffix=${{ matrix.suffix }} + type=ref,event=tag + type=raw,value=release,enable=${{ github.event_name == 'release' }} - name: Create manifest list and push working-directory: ${{ runner.temp }}/digests diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 9be52f90f0..df4856b1a1 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -41,8 +41,8 @@ jobs: with: token: ${{ steps.generate-token.outputs.token }} - - name: Install Poetry - run: pipx install poetry + - name: Install uv + uses: astral-sh/setup-uv@v5 - name: Bump version run: misc/release/pump-version.sh -s "${{ inputs.serverBump }}" -m "${{ inputs.mobileBump }}" @@ -74,7 +74,7 @@ jobs: with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - + - name: Checkout uses: actions/checkout@v4 with: diff --git a/.github/workflows/preview-label.yaml b/.github/workflows/preview-label.yaml index 6468d05e80..1c324ab49f 100644 --- a/.github/workflows/preview-label.yaml +++ b/.github/workflows/preview-label.yaml @@ -2,7 +2,7 @@ name: Preview label on: pull_request: - types: [labeled] + types: [labeled, closed] jobs: comment-status: diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 4d8d40a47b..1e2020a19d 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -50,6 +50,26 @@ jobs: run: dart pub get working-directory: ./mobile + - name: Run Build Runner + run: make build + working-directory: ./mobile + + - name: Find file changes + uses: tj-actions/verify-changed-files@v20 + id: verify-changed-files + with: + files: | + mobile/**/*.g.dart + mobile/**/*.gr.dart + mobile/**/*.drift.dart + + - name: Verify files have not changed + if: steps.verify-changed-files.outputs.files_changed == 'true' + run: | + echo "ERROR: Generated files not up to date! Run make_build inside the mobile directory" + echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}" + exit 1 + - name: Run dart analyze run: dart analyze --fatal-infos working-directory: ./mobile @@ -61,8 +81,3 @@ jobs: - name: Run dart custom_lint run: dart run custom_lint working-directory: ./mobile - - # Enable after riverpod generator migration is completed - # - name: Run dart custom lint - # run: dart run custom_lint - # working-directory: ./mobile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 476ef9f354..99f41697d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -380,27 +380,28 @@ jobs: working-directory: ./machine-learning steps: - uses: actions/checkout@v4 - - name: Install poetry - run: pipx install poetry + - name: Install uv + uses: astral-sh/setup-uv@v5 - uses: actions/setup-python@v5 - with: - python-version: 3.11 - cache: 'poetry' + # TODO: add caching when supported (https://github.com/actions/setup-python/pull/818) + # with: + # python-version: 3.11 + # cache: 'uv' - name: Install dependencies run: | - poetry install --with dev --with cpu + uv sync --extra cpu - name: Lint with ruff run: | - poetry run ruff check --output-format=github app export + uv run ruff check --output-format=github app export - name: Check black formatting run: | - poetry run black --check app export + uv run black --check app export - name: Run mypy type checking run: | - poetry run mypy --install-types --non-interactive --strict app/ + uv run mypy --strict app/ - name: Run tests and coverage run: | - poetry run pytest app --cov=app --cov-report term-missing + uv run pytest app --cov=app --cov-report term-missing shellcheck: name: ShellCheck diff --git a/.github/workflows/weblate-lock.yml b/.github/workflows/weblate-lock.yml index 29a30640bd..4189e51919 100644 --- a/.github/workflows/weblate-lock.yml +++ b/.github/workflows/weblate-lock.yml @@ -18,8 +18,15 @@ jobs: filters: | i18n: - 'i18n/!(en)**\.json' + - name: Debug + run: | + echo "Should run: ${{ steps.found_paths.outputs.i18n == 'true' && github.head_ref != 'chore/translations'}}" + echo "Found i18n paths: ${{ steps.found_paths.outputs.i18n }}" + echo "Head ref: ${{ github.head_ref }}" + enforce-lock: name: Check Weblate Lock + needs: [ pre-job ] runs-on: ubuntu-latest if: ${{ needs.pre-job.outputs.should_run == 'true' }} steps: diff --git a/README.md b/README.md index 7b037ba1e7..fea9801d41 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@

-
+
License: AGPLv3 Discord -
-
+
+

@@ -63,7 +63,7 @@ Access the demo [here](https://demo.immich.app). The demo is running on a Free-tier Oracle VM in Amsterdam with a 2.4Ghz quad-core ARM64 CPU and 24GB RAM. -For the mobile app, you can use `https://demo.immich.app/api` for the `Server Endpoint URL` +For the mobile app, you can use `https://demo.immich.app` for the `Server Endpoint URL` ### Login credentials diff --git a/cli/package-lock.json b/cli/package-lock.json index 8d8c19cd35..3b16a3bf18 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "@immich/cli", - "version": "2.2.52", + "version": "2.2.53", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@immich/cli", - "version": "2.2.52", + "version": "2.2.53", "license": "GNU Affero General Public License version 3", "dependencies": { "chokidar": "^4.0.3", @@ -27,7 +27,7 @@ "@types/lodash-es": "^4.17.12", "@types/micromatch": "^4.0.9", "@types/mock-fs": "^4.13.1", - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", "@vitest/coverage-v8": "^3.0.0", @@ -55,14 +55,14 @@ }, "../open-api/typescript-sdk": { "name": "@immich/sdk", - "version": "1.128.0", + "version": "1.129.0", "dev": true, "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "typescript": "^5.3.3" } }, @@ -1502,9 +1502,9 @@ } }, "node_modules/@types/node": { - "version": "22.13.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.8.tgz", - "integrity": "sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==", + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", "dev": true, "license": "MIT", "dependencies": { @@ -1518,17 +1518,17 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz", - "integrity": "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz", + "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/type-utils": "8.25.0", - "@typescript-eslint/utils": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/type-utils": "8.26.0", + "@typescript-eslint/utils": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1544,20 +1544,20 @@ "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.25.0.tgz", - "integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz", + "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4" }, "engines": { @@ -1569,18 +1569,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz", - "integrity": "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", + "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0" + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1591,14 +1591,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz", - "integrity": "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz", + "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/utils": "8.25.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/utils": "8.26.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -1611,13 +1611,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.25.0.tgz", - "integrity": "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", + "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", "dev": true, "license": "MIT", "engines": { @@ -1629,14 +1629,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz", - "integrity": "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", + "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1652,20 +1652,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.25.0.tgz", - "integrity": "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz", + "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0" + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1676,17 +1676,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz", - "integrity": "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", + "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/types": "8.26.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1711,9 +1711,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.7.tgz", - "integrity": "sha512-Av8WgBJLTrfLOer0uy3CxjlVuWK4CzcLBndW1Nm2vI+3hZ2ozHututkfc7Blu1u6waeQ7J8gzPK/AsBRnWA5mQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.8.tgz", + "integrity": "sha512-y7SAKsQirsEJ2F8bulBck4DoluhI2EEgTimHd6EEUgJBGKy9tC25cpywh1MH4FvDGoG2Unt7+asVd1kj4qOSAw==", "dev": true, "license": "MIT", "dependencies": { @@ -1734,8 +1734,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.0.7", - "vitest": "3.0.7" + "@vitest/browser": "3.0.8", + "vitest": "3.0.8" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1744,14 +1744,14 @@ } }, "node_modules/@vitest/expect": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.7.tgz", - "integrity": "sha512-QP25f+YJhzPfHrHfYHtvRn+uvkCFCqFtW9CktfBxmB+25QqWsx7VB2As6f4GmwllHLDhXNHvqedwhvMmSnNmjw==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.8.tgz", + "integrity": "sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.7", - "@vitest/utils": "3.0.7", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -1760,13 +1760,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.7.tgz", - "integrity": "sha512-qui+3BLz9Eonx4EAuR/i+QlCX6AUZ35taDQgwGkK/Tw6/WgwodSrjN1X2xf69IA/643ZX5zNKIn2svvtZDrs4w==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.8.tgz", + "integrity": "sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.7", + "@vitest/spy": "3.0.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -1787,9 +1787,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.7.tgz", - "integrity": "sha512-CiRY0BViD/V8uwuEzz9Yapyao+M9M008/9oMOSQydwbwb+CMokEq3XVaF3XK/VWaOK0Jm9z7ENhybg70Gtxsmg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", + "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", "dev": true, "license": "MIT", "dependencies": { @@ -1800,13 +1800,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.7.tgz", - "integrity": "sha512-WeEl38Z0S2ZcuRTeyYqaZtm4e26tq6ZFqh5y8YD9YxfWuu0OFiGFUbnxNynwLjNRHPsXyee2M9tV7YxOTPZl2g==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.8.tgz", + "integrity": "sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.0.7", + "@vitest/utils": "3.0.8", "pathe": "^2.0.3" }, "funding": { @@ -1814,13 +1814,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.7.tgz", - "integrity": "sha512-eqTUryJWQN0Rtf5yqCGTQWsCFOQe4eNz5Twsu21xYEcnFJtMU5XvmG0vgebhdLlrHQTSq5p8vWHJIeJQV8ovsA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.8.tgz", + "integrity": "sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.7", + "@vitest/pretty-format": "3.0.8", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -1829,9 +1829,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.7.tgz", - "integrity": "sha512-4T4WcsibB0B6hrKdAZTM37ekuyFZt2cGbEGd2+L0P8ov15J1/HUsUaqkXEQPNAWr4BtPPe1gI+FYfMHhEKfR8w==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.8.tgz", + "integrity": "sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1842,13 +1842,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.7.tgz", - "integrity": "sha512-xePVpCRfooFX3rANQjwoditoXgWb1MaFbzmGuPP59MK6i13mrnDw/yEIyJudLeW6/38mCNcwCiJIGmpDPibAIg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.8.tgz", + "integrity": "sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.7", + "@vitest/pretty-format": "3.0.8", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -2429,13 +2429,13 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.2.tgz", - "integrity": "sha512-1105/17ZIMjmCOJOPNfVdbXafLCLj3hPmkmB7dLgt7XsQ/zkxSuDerE/xgO3RxoHysR1N1whmquY0lSn2O0VLg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz", + "integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==", "dev": true, "license": "MIT", "bin": { - "eslint-config-prettier": "build/bin/cli.js" + "eslint-config-prettier": "bin/cli.js" }, "peerDependencies": { "eslint": ">=7.0.0" @@ -3617,9 +3617,9 @@ } }, "node_modules/prettier": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", - "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", "bin": { @@ -4278,9 +4278,9 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4349,9 +4349,9 @@ } }, "node_modules/vite": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", - "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz", + "integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==", "dev": true, "license": "MIT", "dependencies": { @@ -4421,9 +4421,9 @@ } }, "node_modules/vite-node": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.7.tgz", - "integrity": "sha512-2fX0QwX4GkkkpULXdT1Pf4q0tC1i1lFOyseKoonavXUNlQ77KpW2XqBGGNIm/J4Ows4KxgGJzDguYVPKwG/n5A==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.8.tgz", + "integrity": "sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg==", "dev": true, "license": "MIT", "dependencies": { @@ -4464,19 +4464,19 @@ } }, "node_modules/vitest": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.7.tgz", - "integrity": "sha512-IP7gPK3LS3Fvn44x30X1dM9vtawm0aesAa2yBIZ9vQf+qB69NXC5776+Qmcr7ohUXIQuLhk7xQR0aSUIDPqavg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.8.tgz", + "integrity": "sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.0.7", - "@vitest/mocker": "3.0.7", - "@vitest/pretty-format": "^3.0.7", - "@vitest/runner": "3.0.7", - "@vitest/snapshot": "3.0.7", - "@vitest/spy": "3.0.7", - "@vitest/utils": "3.0.7", + "@vitest/expect": "3.0.8", + "@vitest/mocker": "3.0.8", + "@vitest/pretty-format": "^3.0.8", + "@vitest/runner": "3.0.8", + "@vitest/snapshot": "3.0.8", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.1.0", @@ -4488,7 +4488,7 @@ "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.0.7", + "vite-node": "3.0.8", "why-is-node-running": "^2.3.0" }, "bin": { @@ -4504,8 +4504,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.0.7", - "@vitest/ui": "3.0.7", + "@vitest/browser": "3.0.8", + "@vitest/ui": "3.0.8", "happy-dom": "*", "jsdom": "*" }, @@ -4534,9 +4534,9 @@ } }, "node_modules/vitest-fetch-mock": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/vitest-fetch-mock/-/vitest-fetch-mock-0.4.4.tgz", - "integrity": "sha512-i2RNEAKBgnLWwj5DVz8ouzaHaPVg1xaYgAUmU5p+baJ149upnO+yJLPchAiY9ij8hf0PDkJVVke1pftBxmT05g==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/vitest-fetch-mock/-/vitest-fetch-mock-0.4.5.tgz", + "integrity": "sha512-nhWdCQIGtaSEUVl96pMm0WggyDGPDv5FUy/Q9Hx3cs2RGmh3Q/uRsLClGbdG3kXBkJ3br5yTUjB2MeW25TwdOA==", "dev": true, "license": "MIT", "engines": { diff --git a/cli/package.json b/cli/package.json index 193b678338..334bcc0b0c 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@immich/cli", - "version": "2.2.52", + "version": "2.2.53", "description": "Command Line Interface (CLI) for Immich", "type": "module", "exports": "./dist/index.js", @@ -21,7 +21,7 @@ "@types/lodash-es": "^4.17.12", "@types/micromatch": "^4.0.9", "@types/mock-fs": "^4.13.1", - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", "@vitest/coverage-v8": "^3.0.0", @@ -72,4 +72,4 @@ "volta": { "node": "22.14.0" } -} \ No newline at end of file +} diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 9df5be3901..f2f814fbd0 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -25,7 +25,7 @@ services: context: ../ dockerfile: server/Dockerfile target: dev - restart: always + restart: unless-stopped volumes: - ../server:/usr/src/app - ../open-api:/usr/src/open-api diff --git a/docs/docs/FAQ.mdx b/docs/docs/FAQ.mdx index 23b2b9b30f..96ac03c9dc 100644 --- a/docs/docs/FAQ.mdx +++ b/docs/docs/FAQ.mdx @@ -117,7 +117,7 @@ See [Backup and Restore](/docs/administration/backup-and-restore.md). ### Does Immich support reading existing face tag metadata? -No, it currently does not. There is an [open feature request on GitHub](https://github.com/immich-app/immich/discussions/4348). +Yes, it creates new faces and persons from the imported asset metadata. For details see the [feature request #4348](https://github.com/immich-app/immich/discussions/4348) and [PR #6455](https://github.com/immich-app/immich/pull/6455). ### Does Immich support the filtering of NSFW images? diff --git a/docs/docs/administration/backup-and-restore.md b/docs/docs/administration/backup-and-restore.md index 4eb4d3a8bb..817a7dca6d 100644 --- a/docs/docs/administration/backup-and-restore.md +++ b/docs/docs/administration/backup-and-restore.md @@ -30,6 +30,13 @@ As mentioned above, you should make your own backup of these together with the a You can adjust the schedule and amount of kept backups in the [admin settings](http://my.immich.app/admin/system-settings?isOpen=backup). By default, Immich will keep the last 14 backups and create a new backup every day at 2:00 AM. +#### Trigger Backup + +You are able to trigger a backup in the [admin job status page](http://my.immich.app/admin/jobs-status). +Visit the page, open the "Create job" modal from the top right, select "Backup Database" and click "Confirm". +A job will run and trigger a backup, you can verify this worked correctly by checking the logs or the backup folder. +This backup will count towards the last X backups that will be kept based on your settings. + #### Restoring We hope to make restoring simpler in future versions, for now you can find the backups in the `UPLOAD_LOCATION/backups` folder on your host. @@ -53,7 +60,7 @@ docker compose create # Create Docker containers for Immich apps witho docker start immich_postgres # Start Postgres server sleep 10 # Wait for Postgres server to start up # Check the database user if you deviated from the default -gunzip < "/path/to/backup/dump.sql.gz" \ +gunzip --stdout "/path/to/backup/dump.sql.gz" \ | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \ | docker exec -i immich_postgres psql --dbname=postgres --username= # Restore Backup docker compose up -d # Start remainder of Immich apps @@ -76,8 +83,8 @@ docker compose create # Create Docker containers for docker start immich_postgres # Start Postgres server sleep 10 # Wait for Postgres server to start up docker exec -it immich_postgres bash # Enter the Docker shell and run the following command -# Check the database user if you deviated from the default. If your backup ends in `.gz`, replace `cat` with `gunzip` -cat < "/dump.sql" | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | psql --dbname=postgres --username= +# Check the database user if you deviated from the default. If your backup ends in `.gz`, replace `cat` with `gunzip --stdout` +cat "/dump.sql" | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | psql --dbname=postgres --username= exit # Exit the Docker shell docker compose up -d # Start remainder of Immich apps ``` diff --git a/docs/docs/features/libraries.md b/docs/docs/features/libraries.md index 3d4ab6a892..c09fd5043c 100644 --- a/docs/docs/features/libraries.md +++ b/docs/docs/features/libraries.md @@ -37,7 +37,7 @@ To validate that Immich can reach your external library, start a shell inside th ### Exclusion Patterns -By default, all files in the import paths will be added to the library. If there are files that should not be added, exclusion patterns can be used to exclude them. Exclusion patterns are glob patterns are matched against the full file path. If a file matches an exclusion pattern, it will not be added to the library. Exclusion patterns can be added in the Scan Settings page for each library. Under the hood, Immich uses the [glob](https://www.npmjs.com/package/glob) package to match patterns, so please refer to [their documentation](https://github.com/isaacs/node-glob#glob-primer) to see what patterns are supported. +By default, all files in the import paths will be added to the library. If there are files that should not be added, exclusion patterns can be used to exclude them. Exclusion patterns are glob patterns are matched against the full file path. If a file matches an exclusion pattern, it will not be added to the library. Exclusion patterns can be added in the Scan Settings page for each library. Some basic examples: @@ -48,7 +48,11 @@ Some basic examples: Special characters such as @ should be escaped, for instance: -- `**/\@eadir/**` will exclude all files in any directory named `@eadir` +- `**/\@eaDir/**` will exclude all files in any directory named `@eaDir` + +:::info +Internally, Immich uses the [glob](https://www.npmjs.com/package/glob) package to process exclusion patterns, and sometimes those patterns are translated into [Postgres LIKE patterns](https://www.postgresql.org/docs/current/functions-matching.html). The intention is to support basic folder exclusions but we recommend against advanced usage since those can't reliably be translated to the Postgres syntax. Please refer to the [glob documentation](https://github.com/isaacs/node-glob#glob-primer) for a basic overview on glob patterns. +::: ### Automatic watching (EXPERIMENTAL) diff --git a/docs/docs/features/supported-formats.md b/docs/docs/features/supported-formats.md index 0881d844ac..e6fb2c8f00 100644 --- a/docs/docs/features/supported-formats.md +++ b/docs/docs/features/supported-formats.md @@ -18,7 +18,7 @@ For the full list, refer to the [Immich source code](https://github.com/immich-a | `JPEG 2000` | `.jp2` | :white_check_mark: | | | `JPEG` | `.webp` `.jpg` `.jpe` `.insp` | :white_check_mark: | | | `JPEG XL` | `.jxl` | :white_check_mark: | | -| `PNG` | `.webp` | :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: | | diff --git a/docs/docs/install/unraid.md b/docs/docs/install/unraid.md index 776eef7eb9..731f53bb00 100644 --- a/docs/docs/install/unraid.md +++ b/docs/docs/install/unraid.md @@ -77,7 +77,7 @@ alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich" 7. Paste the entire contents of the [Immich example.env](https://github.com/immich-app/immich/releases/latest/download/example.env) file into the Unraid editor, then **before saving** edit the following: - `UPLOAD_LOCATION`: Create a folder in your Images Unraid share and place the **absolute** location here > For example my _"images"_ share has a folder within it called _"immich"_. If I browse to this directory in the terminal and type `pwd` the output is `/mnt/user/images/immich`. This is the exact value I need to enter as my `UPLOAD_LOCATION` - - `DB_DATA_LOCATION`: Change this to use an Unraid share (preferably a cache pool, e.g. `/mnt/user/appdata`). If left at default it will try to use Unraid's `/boot/config/plugins/compose.manager/projects/[stack_name]/postgres` folder which it doesn't have permissions to, resulting in this container continuously restarting. + - `DB_DATA_LOCATION`: Change this to use an Unraid share (preferably a cache pool, e.g. `/mnt/user/appdata/postgresql/data`). This uses the `appdata` share. Do also create the `postgresql` folder, by running `mkdir /mnt/user/{share_location}/postgresql/data`. If left at default it will try to use Unraid's `/boot/config/plugins/compose.manager/projects/[stack_name]/postgres` folder which it doesn't have permissions to, resulting in this container continuously restarting. 0 && ( ({ label, to: new URL(location.pathname + location.search + location.hash, url).href, target: '_self', + className: label === activeLabel ? 'dropdown__link--active menu__link--active' : '', // workaround because React Router `` only supports using URL path for checking if active: https://v5.reactrouter.com/web/api/NavLink/isactive-func }))} /> ) diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx index 1d73ed36f8..2ffe1debc7 100644 --- a/docs/src/pages/index.tsx +++ b/docs/src/pages/index.tsx @@ -1,12 +1,10 @@ import React from 'react'; import Link from '@docusaurus/Link'; import Layout from '@theme/Layout'; -import { useColorMode } from '@docusaurus/theme-common'; -import { discordPath } from '@site/src/components/svg-paths'; +import { discordPath, discordViewBox } from '@site/src/components/svg-paths'; +import ThemedImage from '@theme/ThemedImage'; import Icon from '@mdi/react'; function HomepageHeader() { - const { isDarkTheme } = useColorMode(); - return (

@@ -14,8 +12,8 @@ function HomepageHeader() {
- Immich logo @@ -35,7 +33,6 @@ function HomepageHeader() { sacrificing your privacy.

-
-
- + Join our Discord
- screenshots -

- - Immich logo -

Download the mobile app

@@ -97,9 +94,8 @@ function HomepageHeader() {

- - app qr code
diff --git a/docs/src/pages/roadmap.tsx b/docs/src/pages/roadmap.tsx index 46997fdfb7..b7ded8e8c9 100644 --- a/docs/src/pages/roadmap.tsx +++ b/docs/src/pages/roadmap.tsx @@ -242,6 +242,13 @@ const roadmap: Item[] = [ ]; const milestones: Item[] = [ + { + icon: mdiStar, + iconColor: 'gold', + title: '60,000 Stars', + description: 'Reached 60K Stars on GitHub!', + getDateLabel: withLanguage(new Date(2025, 2, 4)), + }, withRelease({ icon: mdiLinkEdit, iconColor: 'crimson', diff --git a/docs/static/archived-versions.json b/docs/static/archived-versions.json index 13ab48d766..b476c8f8de 100644 --- a/docs/static/archived-versions.json +++ b/docs/static/archived-versions.json @@ -1,4 +1,8 @@ [ + { + "label": "v1.129.0", + "url": "https://v1.129.0.archive.immich.app" + }, { "label": "v1.128.0", "url": "https://v1.128.0.archive.immich.app" diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 5f0ea7ac47..2288a2b23b 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -1,12 +1,12 @@ { "name": "immich-e2e", - "version": "1.128.0", + "version": "1.129.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "immich-e2e", - "version": "1.128.0", + "version": "1.129.0", "license": "GNU Affero General Public License version 3", "devDependencies": { "@eslint/eslintrc": "^3.1.0", @@ -15,7 +15,7 @@ "@immich/sdk": "file:../open-api/typescript-sdk", "@playwright/test": "^1.44.1", "@types/luxon": "^3.4.2", - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "@types/oidc-provider": "^8.5.1", "@types/pg": "^8.11.0", "@types/pngjs": "^6.0.4", @@ -45,13 +45,15 @@ }, "../cli": { "name": "@immich/cli", - "version": "2.2.52", + "version": "2.2.53", "dev": true, "license": "GNU Affero General Public License version 3", "dependencies": { + "chokidar": "^4.0.3", "fast-glob": "^3.3.2", "fastq": "^1.17.1", - "lodash-es": "^4.17.21" + "lodash-es": "^4.17.21", + "micromatch": "^4.0.8" }, "bin": { "immich": "dist/index.js" @@ -63,8 +65,9 @@ "@types/byte-size": "^8.1.0", "@types/cli-progress": "^3.11.0", "@types/lodash-es": "^4.17.12", + "@types/micromatch": "^4.0.9", "@types/mock-fs": "^4.13.1", - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", "@vitest/coverage-v8": "^3.0.0", @@ -92,14 +95,14 @@ }, "../open-api/typescript-sdk": { "name": "@immich/sdk", - "version": "1.128.0", + "version": "1.129.0", "dev": true, "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "typescript": "^5.3.3" } }, @@ -361,9 +364,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", "cpu": [ "ppc64" ], @@ -378,9 +381,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", "cpu": [ "arm" ], @@ -395,9 +398,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", "cpu": [ "arm64" ], @@ -412,9 +415,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", "cpu": [ "x64" ], @@ -429,9 +432,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", "cpu": [ "arm64" ], @@ -446,9 +449,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", "cpu": [ "x64" ], @@ -463,9 +466,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", "cpu": [ "arm64" ], @@ -480,9 +483,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", "cpu": [ "x64" ], @@ -497,9 +500,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", "cpu": [ "arm" ], @@ -514,9 +517,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", "cpu": [ "arm64" ], @@ -531,9 +534,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", "cpu": [ "ia32" ], @@ -548,9 +551,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", "cpu": [ "loong64" ], @@ -565,9 +568,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", "cpu": [ "mips64el" ], @@ -582,9 +585,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", "cpu": [ "ppc64" ], @@ -599,9 +602,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", "cpu": [ "riscv64" ], @@ -616,9 +619,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", "cpu": [ "s390x" ], @@ -633,9 +636,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", "cpu": [ "x64" ], @@ -650,9 +653,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", "cpu": [ "arm64" ], @@ -667,9 +670,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", "cpu": [ "x64" ], @@ -684,9 +687,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", "cpu": [ "arm64" ], @@ -701,9 +704,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", "cpu": [ "x64" ], @@ -718,9 +721,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", "cpu": [ "x64" ], @@ -735,9 +738,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", "cpu": [ "arm64" ], @@ -752,9 +755,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", "cpu": [ "ia32" ], @@ -769,9 +772,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", "cpu": [ "x64" ], @@ -1245,13 +1248,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.50.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz", - "integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==", + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.0.tgz", + "integrity": "sha512-dJ0dMbZeHhI+wb77+ljx/FeC8VBP6j/rj9OAojO08JI80wTZy6vRk9KvHKiDCUh4iMpEiseMgqRBIeW+eKX6RA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.50.1" + "playwright": "1.51.0" }, "bin": { "playwright": "cli.js" @@ -1261,9 +1264,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz", - "integrity": "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz", + "integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==", "cpu": [ "arm" ], @@ -1275,9 +1278,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.9.tgz", - "integrity": "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz", + "integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==", "cpu": [ "arm64" ], @@ -1289,9 +1292,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", - "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz", + "integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==", "cpu": [ "arm64" ], @@ -1303,9 +1306,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", - "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz", + "integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==", "cpu": [ "x64" ], @@ -1317,9 +1320,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.9.tgz", - "integrity": "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz", + "integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==", "cpu": [ "arm64" ], @@ -1331,9 +1334,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.9.tgz", - "integrity": "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz", + "integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==", "cpu": [ "x64" ], @@ -1345,9 +1348,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.9.tgz", - "integrity": "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz", + "integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==", "cpu": [ "arm" ], @@ -1359,9 +1362,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.9.tgz", - "integrity": "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz", + "integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==", "cpu": [ "arm" ], @@ -1373,9 +1376,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", - "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz", + "integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==", "cpu": [ "arm64" ], @@ -1387,9 +1390,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", - "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz", + "integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==", "cpu": [ "arm64" ], @@ -1401,9 +1404,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.9.tgz", - "integrity": "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz", + "integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==", "cpu": [ "loong64" ], @@ -1415,9 +1418,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.9.tgz", - "integrity": "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz", + "integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==", "cpu": [ "ppc64" ], @@ -1429,9 +1432,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.9.tgz", - "integrity": "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz", + "integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==", "cpu": [ "riscv64" ], @@ -1443,9 +1446,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.9.tgz", - "integrity": "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz", + "integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==", "cpu": [ "s390x" ], @@ -1457,9 +1460,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", - "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz", + "integrity": "sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==", "cpu": [ "x64" ], @@ -1471,9 +1474,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", - "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz", + "integrity": "sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==", "cpu": [ "x64" ], @@ -1485,9 +1488,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", - "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz", + "integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==", "cpu": [ "arm64" ], @@ -1499,9 +1502,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.9.tgz", - "integrity": "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz", + "integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==", "cpu": [ "ia32" ], @@ -1513,9 +1516,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", - "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz", + "integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==", "cpu": [ "x64" ], @@ -1714,9 +1717,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.13.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.8.tgz", - "integrity": "sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==", + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", "dev": true, "license": "MIT", "dependencies": { @@ -1879,17 +1882,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz", - "integrity": "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz", + "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/type-utils": "8.25.0", - "@typescript-eslint/utils": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/type-utils": "8.26.0", + "@typescript-eslint/utils": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1905,20 +1908,20 @@ "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.25.0.tgz", - "integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz", + "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4" }, "engines": { @@ -1930,18 +1933,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz", - "integrity": "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", + "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0" + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1952,14 +1955,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz", - "integrity": "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz", + "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/utils": "8.25.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/utils": "8.26.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -1972,13 +1975,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.25.0.tgz", - "integrity": "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", + "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", "dev": true, "license": "MIT", "engines": { @@ -1990,14 +1993,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz", - "integrity": "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", + "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2013,7 +2016,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -2043,16 +2046,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.25.0.tgz", - "integrity": "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz", + "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0" + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2063,17 +2066,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz", - "integrity": "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", + "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/types": "8.26.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2098,9 +2101,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.7.tgz", - "integrity": "sha512-Av8WgBJLTrfLOer0uy3CxjlVuWK4CzcLBndW1Nm2vI+3hZ2ozHututkfc7Blu1u6waeQ7J8gzPK/AsBRnWA5mQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.8.tgz", + "integrity": "sha512-y7SAKsQirsEJ2F8bulBck4DoluhI2EEgTimHd6EEUgJBGKy9tC25cpywh1MH4FvDGoG2Unt7+asVd1kj4qOSAw==", "dev": true, "license": "MIT", "dependencies": { @@ -2121,8 +2124,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.0.7", - "vitest": "3.0.7" + "@vitest/browser": "3.0.8", + "vitest": "3.0.8" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2149,14 +2152,14 @@ } }, "node_modules/@vitest/expect": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.7.tgz", - "integrity": "sha512-QP25f+YJhzPfHrHfYHtvRn+uvkCFCqFtW9CktfBxmB+25QqWsx7VB2As6f4GmwllHLDhXNHvqedwhvMmSnNmjw==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.8.tgz", + "integrity": "sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.7", - "@vitest/utils": "3.0.7", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -2165,13 +2168,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.7.tgz", - "integrity": "sha512-qui+3BLz9Eonx4EAuR/i+QlCX6AUZ35taDQgwGkK/Tw6/WgwodSrjN1X2xf69IA/643ZX5zNKIn2svvtZDrs4w==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.8.tgz", + "integrity": "sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.7", + "@vitest/spy": "3.0.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -2192,9 +2195,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.7.tgz", - "integrity": "sha512-CiRY0BViD/V8uwuEzz9Yapyao+M9M008/9oMOSQydwbwb+CMokEq3XVaF3XK/VWaOK0Jm9z7ENhybg70Gtxsmg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", + "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", "dev": true, "license": "MIT", "dependencies": { @@ -2205,13 +2208,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.7.tgz", - "integrity": "sha512-WeEl38Z0S2ZcuRTeyYqaZtm4e26tq6ZFqh5y8YD9YxfWuu0OFiGFUbnxNynwLjNRHPsXyee2M9tV7YxOTPZl2g==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.8.tgz", + "integrity": "sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.0.7", + "@vitest/utils": "3.0.8", "pathe": "^2.0.3" }, "funding": { @@ -2219,13 +2222,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.7.tgz", - "integrity": "sha512-eqTUryJWQN0Rtf5yqCGTQWsCFOQe4eNz5Twsu21xYEcnFJtMU5XvmG0vgebhdLlrHQTSq5p8vWHJIeJQV8ovsA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.8.tgz", + "integrity": "sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.7", + "@vitest/pretty-format": "3.0.8", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -2234,9 +2237,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.7.tgz", - "integrity": "sha512-4T4WcsibB0B6hrKdAZTM37ekuyFZt2cGbEGd2+L0P8ov15J1/HUsUaqkXEQPNAWr4BtPPe1gI+FYfMHhEKfR8w==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.8.tgz", + "integrity": "sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2247,13 +2250,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.7.tgz", - "integrity": "sha512-xePVpCRfooFX3rANQjwoditoXgWb1MaFbzmGuPP59MK6i13mrnDw/yEIyJudLeW6/38mCNcwCiJIGmpDPibAIg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.8.tgz", + "integrity": "sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.7", + "@vitest/pretty-format": "3.0.8", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -3085,9 +3088,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3098,31 +3101,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" } }, "node_modules/escalade": { @@ -3214,13 +3217,13 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.2.tgz", - "integrity": "sha512-1105/17ZIMjmCOJOPNfVdbXafLCLj3hPmkmB7dLgt7XsQ/zkxSuDerE/xgO3RxoHysR1N1whmquY0lSn2O0VLg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz", + "integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==", "dev": true, "license": "MIT", "bin": { - "eslint-config-prettier": "build/bin/cli.js" + "eslint-config-prettier": "bin/cli.js" }, "peerDependencies": { "eslint": ">=7.0.0" @@ -4935,9 +4938,9 @@ "dev": true }, "node_modules/oidc-provider": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-8.8.0.tgz", - "integrity": "sha512-5b4QncVOVsU8BLpD0ofQBRq2aX9Juhc0wFbaZSQbAmgN1jVfCZfYt3GEPPmJ8Tc/mvfX735PNH/LnuyWzMn9tQ==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-8.8.1.tgz", + "integrity": "sha512-qVChpayTwojUREJxLkFofUSK8kiSRIdzPrVSsoGibqRHl/YO60ege94OZS8vh7zaK+zxcG/Gu8UMaYB5ulohCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5330,13 +5333,13 @@ } }, "node_modules/playwright": { - "version": "1.50.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz", - "integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==", + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.0.tgz", + "integrity": "sha512-442pTfGM0xxfCYxuBa/Pu6B2OqxqqaYq39JS8QDMGThUvIOCd6s0ANDog3uwA0cHavVlnTQzGCN7Id2YekDSXA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.50.1" + "playwright-core": "1.51.0" }, "bin": { "playwright": "cli.js" @@ -5349,9 +5352,9 @@ } }, "node_modules/playwright-core": { - "version": "1.50.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz", - "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==", + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.0.tgz", + "integrity": "sha512-x47yPE3Zwhlil7wlNU/iktF7t2r/URR3VLbH6EknJd/04Qc/PSJ0EY3CMXipmglLG+zyRxW6HNo2EGbKLHPWMg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5409,9 +5412,9 @@ } }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz", + "integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==", "dev": true, "funding": [ { @@ -5482,9 +5485,9 @@ } }, "node_modules/prettier": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", - "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", "bin": { @@ -5819,9 +5822,9 @@ } }, "node_modules/rollup": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz", - "integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz", + "integrity": "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==", "dev": true, "license": "MIT", "dependencies": { @@ -5835,25 +5838,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.34.9", - "@rollup/rollup-android-arm64": "4.34.9", - "@rollup/rollup-darwin-arm64": "4.34.9", - "@rollup/rollup-darwin-x64": "4.34.9", - "@rollup/rollup-freebsd-arm64": "4.34.9", - "@rollup/rollup-freebsd-x64": "4.34.9", - "@rollup/rollup-linux-arm-gnueabihf": "4.34.9", - "@rollup/rollup-linux-arm-musleabihf": "4.34.9", - "@rollup/rollup-linux-arm64-gnu": "4.34.9", - "@rollup/rollup-linux-arm64-musl": "4.34.9", - "@rollup/rollup-linux-loongarch64-gnu": "4.34.9", - "@rollup/rollup-linux-powerpc64le-gnu": "4.34.9", - "@rollup/rollup-linux-riscv64-gnu": "4.34.9", - "@rollup/rollup-linux-s390x-gnu": "4.34.9", - "@rollup/rollup-linux-x64-gnu": "4.34.9", - "@rollup/rollup-linux-x64-musl": "4.34.9", - "@rollup/rollup-win32-arm64-msvc": "4.34.9", - "@rollup/rollup-win32-ia32-msvc": "4.34.9", - "@rollup/rollup-win32-x64-msvc": "4.34.9", + "@rollup/rollup-android-arm-eabi": "4.35.0", + "@rollup/rollup-android-arm64": "4.35.0", + "@rollup/rollup-darwin-arm64": "4.35.0", + "@rollup/rollup-darwin-x64": "4.35.0", + "@rollup/rollup-freebsd-arm64": "4.35.0", + "@rollup/rollup-freebsd-x64": "4.35.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", + "@rollup/rollup-linux-arm-musleabihf": "4.35.0", + "@rollup/rollup-linux-arm64-gnu": "4.35.0", + "@rollup/rollup-linux-arm64-musl": "4.35.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", + "@rollup/rollup-linux-riscv64-gnu": "4.35.0", + "@rollup/rollup-linux-s390x-gnu": "4.35.0", + "@rollup/rollup-linux-x64-gnu": "4.35.0", + "@rollup/rollup-linux-x64-musl": "4.35.0", + "@rollup/rollup-win32-arm64-msvc": "4.35.0", + "@rollup/rollup-win32-ia32-msvc": "4.35.0", + "@rollup/rollup-win32-x64-msvc": "4.35.0", "fsevents": "~2.3.2" } }, @@ -6481,9 +6484,9 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -6590,9 +6593,9 @@ } }, "node_modules/vite": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", - "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz", + "integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6662,9 +6665,9 @@ } }, "node_modules/vite-node": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.7.tgz", - "integrity": "sha512-2fX0QwX4GkkkpULXdT1Pf4q0tC1i1lFOyseKoonavXUNlQ77KpW2XqBGGNIm/J4Ows4KxgGJzDguYVPKwG/n5A==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.8.tgz", + "integrity": "sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg==", "dev": true, "license": "MIT", "dependencies": { @@ -6718,19 +6721,19 @@ } }, "node_modules/vitest": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.7.tgz", - "integrity": "sha512-IP7gPK3LS3Fvn44x30X1dM9vtawm0aesAa2yBIZ9vQf+qB69NXC5776+Qmcr7ohUXIQuLhk7xQR0aSUIDPqavg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.8.tgz", + "integrity": "sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.0.7", - "@vitest/mocker": "3.0.7", - "@vitest/pretty-format": "^3.0.7", - "@vitest/runner": "3.0.7", - "@vitest/snapshot": "3.0.7", - "@vitest/spy": "3.0.7", - "@vitest/utils": "3.0.7", + "@vitest/expect": "3.0.8", + "@vitest/mocker": "3.0.8", + "@vitest/pretty-format": "^3.0.8", + "@vitest/runner": "3.0.8", + "@vitest/snapshot": "3.0.8", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.1.0", @@ -6742,7 +6745,7 @@ "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.0.7", + "vite-node": "3.0.8", "why-is-node-running": "^2.3.0" }, "bin": { @@ -6758,8 +6761,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.0.7", - "@vitest/ui": "3.0.7", + "@vitest/browser": "3.0.8", + "@vitest/ui": "3.0.8", "happy-dom": "*", "jsdom": "*" }, diff --git a/e2e/package.json b/e2e/package.json index 78fb76538e..e7dacf3c26 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,6 +1,6 @@ { "name": "immich-e2e", - "version": "1.128.0", + "version": "1.129.0", "description": "", "main": "index.js", "type": "module", @@ -25,7 +25,7 @@ "@immich/sdk": "file:../open-api/typescript-sdk", "@playwright/test": "^1.44.1", "@types/luxon": "^3.4.2", - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "@types/oidc-provider": "^8.5.1", "@types/pg": "^8.11.0", "@types/pngjs": "^6.0.4", diff --git a/e2e/src/api/specs/library.e2e-spec.ts b/e2e/src/api/specs/library.e2e-spec.ts index 4b340a2da5..7560672727 100644 --- a/e2e/src/api/specs/library.e2e-spec.ts +++ b/e2e/src/api/specs/library.e2e-spec.ts @@ -490,7 +490,7 @@ describe('/libraries', () => { utils.removeImageFile(`${testAssetDir}/temp/reimport/asset.jpg`); }); - it('should not reimport unmodified files', async () => { + it('should not reimport a file with unchanged timestamp', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, importPaths: [`${testAssetDirInternal}/temp/reimport`], @@ -933,6 +933,8 @@ describe('/libraries', () => { const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + expect(assets.count).toBe(1); + utils.renameImageFile(`${testAssetDir}/temp/offline/offline.png`, `${testAssetDir}/temp/offline.png`); await utils.scan(admin.accessToken, library.id); @@ -963,6 +965,58 @@ describe('/libraries', () => { } }); + it('should set a trashed offline asset to online but keep it in trash', async () => { + utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`); + + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/offline`], + }); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.count).toBe(1); + + await utils.deleteAssets(admin.accessToken, [assets.items[0].id]); + + { + const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + + expect(trashedAsset.isTrashed).toBe(true); + } + + utils.renameImageFile(`${testAssetDir}/temp/offline/offline.png`, `${testAssetDir}/temp/offline.png`); + + await utils.scan(admin.accessToken, library.id); + + const offlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + expect(offlineAsset.isTrashed).toBe(true); + expect(offlineAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); + expect(offlineAsset.isOffline).toBe(true); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true }); + expect(assets.count).toBe(1); + } + + utils.renameImageFile(`${testAssetDir}/temp/offline.png`, `${testAssetDir}/temp/offline/offline.png`); + + await utils.scan(admin.accessToken, library.id); + + const backOnlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + + expect(backOnlineAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); + expect(backOnlineAsset.isOffline).toBe(false); + expect(backOnlineAsset.isTrashed).toBe(true); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true }); + expect(assets.count).toBe(1); + } + }); + it('should not set an offline asset to online if its file exists, is not covered by an exclusion pattern, but is outside of all import paths', async () => { utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`); @@ -1024,16 +1078,17 @@ describe('/libraries', () => { await utils.scan(admin.accessToken, library.id); - const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + { + const { assets: assetsBefore } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + expect(assetsBefore.count).toBe(1); + } utils.renameImageFile(`${testAssetDir}/temp/offline/offline.png`, `${testAssetDir}/temp/offline.png`); await utils.scan(admin.accessToken, library.id); - { - const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true }); - expect(assets.count).toBe(1); - } + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true }); + expect(assets.count).toBe(1); const offlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); diff --git a/e2e/src/api/specs/person.e2e-spec.ts b/e2e/src/api/specs/person.e2e-spec.ts index 1a589da1f7..6e7eba74ba 100644 --- a/e2e/src/api/specs/person.e2e-spec.ts +++ b/e2e/src/api/specs/person.e2e-spec.ts @@ -201,7 +201,7 @@ describe('/people', () => { expect(body).toMatchObject({ id: expect.any(String), name: 'New Person', - birthDate: '1990-01-01T00:00:00.000Z', + birthDate: '1990-01-01', }); }); @@ -262,7 +262,7 @@ describe('/people', () => { .set('Authorization', `Bearer ${admin.accessToken}`) .send({ birthDate: '1990-01-01' }); expect(status).toBe(200); - expect(body).toMatchObject({ birthDate: '1990-01-01T00:00:00.000Z' }); + expect(body).toMatchObject({ birthDate: '1990-01-01' }); }); it('should clear a date of birth', async () => { diff --git a/e2e/src/web/specs/shared-link.e2e-spec.ts b/e2e/src/web/specs/shared-link.e2e-spec.ts index ed81db4ef5..9313526dab 100644 --- a/e2e/src/web/specs/shared-link.e2e-spec.ts +++ b/e2e/src/web/specs/shared-link.e2e-spec.ts @@ -45,7 +45,7 @@ test.describe('Shared Links', () => { await page.goto(`/share/${sharedLink.key}`); await page.getByRole('heading', { name: 'Test Album' }).waitFor(); await page.locator(`[data-asset-id="${asset.id}"]`).hover(); - await page.waitForSelector('#asset-group-by-date svg'); + await page.waitForSelector('[data-group] svg'); await page.getByRole('checkbox').click(); await page.getByRole('button', { name: 'Download' }).click(); await page.getByText('DOWNLOADING', { exact: true }).waitFor(); diff --git a/i18n/af.json b/i18n/af.json index 92286a7f04..349626306f 100644 --- a/i18n/af.json +++ b/i18n/af.json @@ -57,7 +57,7 @@ "exclusion_pattern_description": "Met uitsluitingspatrone kan jy lêers en vouers ignoreer wanneer jy jou biblioteek skandeer. Dit is nuttig as jy vouers het wat lêers bevat wat jy nie wil invoer nie, soos RAW-lêers.", "external_library_created_at": "Eksterne biblioteek (geskep op {date})", "external_library_management": "Eksterne Biblioteek-opsies", - "face_detection": "Gesigsopsporing", + "face_detection": "Gesig deteksie", "failed_job_command": "Opdrag {command} het misluk vir werk: {job}", "force_delete_user_warning": "WAARSKUWING: Dit sal onmiddellik die gebruiker en alle bates verwyder. Dit kan nie ontdoen word nie en die lêers kan nie herstel word nie.", "forcing_refresh_library_files": "Forseer herlaai van alle biblioteeklêers", diff --git a/i18n/ar.json b/i18n/ar.json index b94a9bf08a..66e4045606 100644 --- a/i18n/ar.json +++ b/i18n/ar.json @@ -41,6 +41,7 @@ "backup_settings": "إعدادات النسخ الاحتياطي", "backup_settings_description": "إدارة إعدادات النسخ الاحتياطي لقاعدة البيانات", "check_all": "اختر الكل", + "cleanup": "تنظيف", "cleared_jobs": "تم إخلاء مهام: {job}", "config_set_by_file": "الإعدادات حاليًا معينة عن طريق ملف الاعدادات", "confirm_delete_library": "هل أنت متأكد أنك تريد حذف مكتبة {library}؟", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "تفعيل فحص المكتبة الدوري", "library_settings": "المكتبة الخارجية", "library_settings_description": "إدارة إعدادات المكتبة الخارجية", - "library_tasks_description": "قم بتنفيذ مهام المكتبة", + "library_tasks_description": "مسح المكتبات الخارجية للعثور على الأصول الجديدة و/أو المتغيرة", "library_watching_enable_description": "راقب المكتبات الخارجية لتتبع تغييرات الملفات", "library_watching_settings": "مراقبة المكتبات (تجريبي)", "library_watching_settings_description": "راقب تلقائيًا التغييرات في الملفات", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "البحث عن الصور بشكل دلالي باستخدام تضمينات CLIP", "machine_learning_smart_search_enabled": "تفعيل البحث الذكي", "machine_learning_smart_search_enabled_description": "إذا تم تعطيله، فلن يتم ترميز الصور للبحث الذكي.", - "machine_learning_url_description": "عنوان URL لخادم التعلم الآلي. إذا تم توفير أكثر من عنوان URL، فسيتم محاولة الوصول إلى كل خادم على حدة حتى يستجيب أحد الخوادم بنجاح، بالترتيب من الأول إلى الأخير.", + "machine_learning_url_description": "عنوان URL لخادم التعلم الآلي. إذا تم توفير أكثر من عنوان URL واحد، سيتم محاولة الاتصال بكل خادم على حدة حتى يستجيب أحدهم بنجاح، بدءًا من الأول إلى الأخير. سيتم تجاهل الخوادم التي لا تستجيب مؤقتًا حتى تعود للعمل.", "manage_concurrency": "إدارة التزامن", "manage_log_settings": "إدارة إعدادات السجلات", "map_dark_style": "النمط الداكن", @@ -147,6 +148,8 @@ "map_settings": "الخريطة", "map_settings_description": "إدارة إعدادات الخريطة", "map_style_description": "عنوان URL لسمة الخريطة style.json", + "memory_cleanup_job": "تنظيف الذاكرة", + "memory_generate_job": "توليد الذاكرة", "metadata_extraction_job": "استخراج البيانات الوصفية", "metadata_extraction_job_description": "استخراج معلومات البيانات الوصفية من كل أصل، مثل إحداثيات الموقع, الوجوه والدقة", "metadata_faces_import_setting": "تمكين استيراد الوجه", @@ -391,6 +394,7 @@ "allow_edits": "إسمح بالتعديل", "allow_public_user_to_download": "السماح لأي مستخدم عام بالتنزيل", "allow_public_user_to_upload": "السماح للمستخدم العام بالرفع", + "alt_text_qr_code": "صورة رمز الاستجابة السريعة (QR)", "anti_clockwise": "عكس اتجاه عقارب الساعة", "api_key": "مفتاح واجهة برمجة التطبيقات", "api_key_description": "سيتم عرض هذه القيمة مرة واحدة فقط. يرجى التأكد من نسخها قبل إغلاق النافذة.", @@ -481,6 +485,7 @@ "comments_are_disabled": "التعليقات معطلة", "confirm": "تأكيد", "confirm_admin_password": "تأكيد كلمة مرور المسؤول", + "confirm_delete_face": "هل أنت متأكد من حذف وجه {name} من الأصول؟", "confirm_delete_shared_link": "هل أنت متأكد أنك تريد حذف هذا الرابط المشترك؟", "confirm_keep_this_delete_others": "سيتم حذف جميع الأصول الأخرى في المجموعة باستثناء هذا الأصل. هل أنت متأكد من أنك تريد المتابعة؟", "confirm_password": "تأكيد كلمة المرور", @@ -533,6 +538,7 @@ "delete_album": "حذف الألبوم", "delete_api_key_prompt": "هل أنت متأكد أنك تريد حذف مفتاح API هذا؟", "delete_duplicates_confirmation": "هل أنت متأكد أنك تريد حذف هذه التكرارات نهائيًا؟", + "delete_face": "حذف الوجه", "delete_key": "حذف المفتاح", "delete_library": "حذف المكتبة", "delete_link": "حذف الرابط", @@ -600,6 +606,7 @@ "enabled": "مفعل", "end_date": "تاريخ الإنتهاء", "error": "خطأ", + "error_delete_face": "حدث خطأ في حذف الوجه من الأصول", "error_loading_image": "حدث خطأ أثناء تحميل الصورة", "error_title": "خطأ - حدث خللٌ ما", "errors": { @@ -769,7 +776,7 @@ "group_country": "مجموعة البلد", "group_no": "بدون تجميع", "group_owner": "تجميع حسب المالك", - "group_places_by": "تجميع الأماكن حسب", + "group_places_by": "تجميع الأماكن حسب...", "group_year": "تجميع حسب السنة", "has_quota": "محدد بحصة", "hi_user": "مرحبا {name} ({email})", @@ -884,6 +891,7 @@ "month": "شهر", "more": "المزيد", "moved_to_trash": "تم النقل إلى سلة المهملات", + "mute_memories": "كتم الذكريات", "my_albums": "ألبوماتي", "name": "الاسم", "name_or_nickname": "الاسم أو اللقب", @@ -1076,6 +1084,8 @@ "removed_from_archive": "تمت إزالتها من الأرشيف", "removed_from_favorites": "تمت الإزالة من المفضلة", "removed_from_favorites_count": "{count, plural, other {أُزيلت #}} من التفضيلات", + "removed_memory": "الذاكرة المحذوفة", + "removed_photo_from_memory": "تم إزالة الصورة من الذاكرة", "removed_tagged_assets": "تمت إزالة العلامة من {count, plural, one {# الأصل} other {# الأصول}}", "rename": "إعادة تسمية", "repair": "إصلاح", @@ -1084,6 +1094,7 @@ "repository": "المستودع", "require_password": "يتطلب كلمة المرور", "require_user_to_change_password_on_first_login": "مطالبة المستخدم بتغيير كلمة المرور عند تسجيل الدخول لأول مرة", + "rescan": "إعادة المسح", "reset": "إعادة ضبط", "reset_password": "إعادة تعيين كلمة المرور", "reset_people_visibility": "إعادة ضبط ظهور الأشخاص", @@ -1127,6 +1138,7 @@ "search_options": "خيارات البحث", "search_people": "البحث عن الأشخاص", "search_places": "البحث عن الأماكن", + "search_rating": "البحث حسب التقييم...", "search_settings": "إعدادات البحث", "search_state": "البحث حسب الولاية...", "search_tags": "البحث عن العلامات...", @@ -1250,6 +1262,7 @@ "tag_created": "تم إنشاء العلامة: {tag}", "tag_feature_description": "تصفح الصور ومقاطع الفيديو المجمعة حسب مواضيع العلامات المنطقية", "tag_not_found_question": "لا يمكن العثور على علامة؟ قم بإنشاء علامة جديدة.", + "tag_people": "علِّم الأشخاص", "tag_updated": "تم تحديث العلامة: {tag}", "tagged_assets": "تم وضع علامة {count, plural, one {# asset} other {# assets}}", "tags": "العلامات", @@ -1290,6 +1303,7 @@ "unlink_motion_video": "إلغاء ربط فيديو الحركة", "unlink_oauth": "إلغاء ربط OAuth", "unlinked_oauth_account": "تم إلغاء ربط حساب OAuth", + "unmute_memories": "تشغيل الصوت للذكريات", "unnamed_album": "ألبوم بلا إسم", "unnamed_album_delete_confirmation": "هل أنت متأكد أنك تريد حذف هذا الألبوم؟", "unnamed_share": "مشاركة بلا إسم", @@ -1343,6 +1357,7 @@ "view_all": "عرض الكل", "view_all_users": "عرض كافة المستخدمين", "view_in_timeline": "عرض في الجدول الزمني", + "view_link": "عرض الرابط", "view_links": "عرض الروابط", "view_name": "عرض", "view_next_asset": "عرض المحتوى التالي", diff --git a/i18n/bg.json b/i18n/bg.json index 47c1a82508..72948d8d50 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -20,7 +20,7 @@ "add_partner": "Добавете партньор", "add_path": "Добави път", "add_photos": "Добавете снимки", - "add_to": "Добави към...", + "add_to": "Добави към…", "add_to_album": "Добави към албум", "add_to_shared_album": "Добави към споделен албум", "add_url": "Добави URL", @@ -41,6 +41,7 @@ "backup_settings": "Настройка на резервни копия", "backup_settings_description": "Управление на настройките за резервно копие на базата данни", "check_all": "Провери всичко", + "cleanup": "Почистване", "cleared_jobs": "Изчистени задачи от тип: {job}", "config_set_by_file": "Конфигурацията е зададена от файл", "confirm_delete_library": "Сигурни ли сте че искате да изтриете библиотеката - {library} ?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Включване на периодичното сканиране на библиотеката", "library_settings": "Външна библиотека", "library_settings_description": "Управление на настройките за външна библиотека", - "library_tasks_description": "Извършване на задачи за библиотеката", + "library_tasks_description": "Сканирайте външни библиотеки за нови и/или променени активи", "library_watching_enable_description": "Наблюдаване за промяна на файловете във външната библиотека", "library_watching_settings": "Наблюдаване на библиотеката (ЕКСПЕРИМЕНТАЛНО)", "library_watching_settings_description": "Автоматично наблюдавай за променени файлове", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Семантично търсене на изображения с помощта на CLIP вграждания", "machine_learning_smart_search_enabled": "Включване на Интелигентно Търсене", "machine_learning_smart_search_enabled_description": "Ако е деактивирано, изображенията няма да бъдат кодирани за Интелигентно Търсене.", - "machine_learning_url_description": "URL на сървъра за машинно обучение. Ако са предоставени повече от един URL, всеки сървър ще бъде опитан един по един, докато един не отговори успешно, в реда от първия до последния.", + "machine_learning_url_description": "URL на сървъра за машинно обучение. Ако са предоставени повече от един URL, всеки сървър ще бъде опитан един по един, докато един отговори успешно, в реда от първия до последния. Сървъри, които не отговорят, ще бъдат временно игнорирани, докато не се върнат онлайн.", "manage_concurrency": "Управление на паралелност", "manage_log_settings": "Управление на настройките на записване", "map_dark_style": "Тъмен стил", @@ -147,6 +148,8 @@ "map_settings": "Карта", "map_settings_description": "Управление на настройките на картата", "map_style_description": "URL адрес към файл \"style.json\" за задаване на стил на картата", + "memory_cleanup_job": "Почистване на паметта", + "memory_generate_job": "Генериране на паметта", "metadata_extraction_job": "Извличане на метаданни", "metadata_extraction_job_description": "Извличане на метаданни от всеки от елемент, като GPS локация, лица и резолюция на файловете", "metadata_faces_import_setting": "Включи импорт на лице", @@ -219,7 +222,7 @@ "reset_settings_to_default": "Възстановяване на настройките по подразбиране", "reset_settings_to_recent_saved": "Възстановяване на настройките до последните запазени настройки", "scanning_library": "Сканиране на библиотеката", - "search_jobs": "Търсене на задачи...", + "search_jobs": "Търсене на задачи…", "send_welcome_email": "Изпращане на имейл за добре дошли", "server_external_domain_settings": "Външен домейн", "server_external_domain_settings_description": "Домейн за публични споделени връзки, включително http(s)://", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Максимални B-фрейма", "transcoding_max_b_frames_description": "По-високите стойности подобряват ефективността на компресията, но забавят разкодирането. Може да не е съвместим с хардуерното ускорение на по-стари устройства. 0 деактивира B-фрейма, докато -1 задава тази стойност автоматично.", "transcoding_max_bitrate": "Максимален битрейт", - "transcoding_max_bitrate_description": "Задаването на максимален битрейт може да направи размерите на файловете по-предвидими при незначителни разлики за качеството. При 720p типичните стойности са 2600k за VP9 или HEVC или 4500k за H.264. Деактивирано, ако е зададено на 0.", + "transcoding_max_bitrate_description": "Задаването на максимален битрейт може да направи размерите на файловете по-предвидими при незначителни разлики за качеството. При 720p типичните стойности са 2600 kbit/s за VP9 или HEVC или 4500 kbit/s за H.264. Деактивирано, ако е зададено на 0.", "transcoding_max_keyframe_interval": "Максимален интервал между ключовите кадри", "transcoding_max_keyframe_interval_description": "Задава максималното разстояние между ключовите кадри. По-ниските стойности влошават ефективността на компресията, но подобряват времето за търсене и могат да подобрят качеството в сцени с бързо движение. 0 задава тази стойност автоматично.", "transcoding_optimal_description": "Видеоклипове с по-висока от целевата разделителна способност или не в приетия формат", @@ -391,6 +394,7 @@ "allow_edits": "Позволяване на редакции", "allow_public_user_to_download": "Позволете на публичен потребител да може да изтегля", "allow_public_user_to_upload": "Позволете на публичния потребител да може да качва", + "alt_text_qr_code": "Изображение на QR код", "anti_clockwise": "Обратно на часовниковата стрелка", "api_key": "API ключ", "api_key_description": "Тази стойност ще бъде показана само веднъж. Моля, не забравяйте да го копирате, преди да затворите прозореца.", @@ -406,17 +410,17 @@ "are_these_the_same_person": "Това едно и също лице ли е?", "are_you_sure_to_do_this": "Сигурни ли сте, че искате да направите това?", "asset_added_to_album": "Добавено в албум", - "asset_adding_to_album": "Добавяне в албум...", + "asset_adding_to_album": "Добавяне в албум…", "asset_description_updated": "Описанието на елемента е обновено", "asset_filename_is_offline": "Активът {filename} е офлайн", "asset_has_unassigned_faces": "Елементът има незададени лица", - "asset_hashing": "Хеширане...", + "asset_hashing": "Хеширане…", "asset_offline": "Елементът е офлайн", "asset_offline_description": "Този външен актив вече не се намира на диска. Моля, свържете се с администратора на Immich за помощ.", "asset_skipped": "Пропуснато", "asset_skipped_in_trash": "В кошчето", "asset_uploaded": "Качено", - "asset_uploading": "Качване...", + "asset_uploading": "Качване…", "assets": "Елементи", "assets_added_count": "Добавено {count, plural, one {# asset} other {# assets}}", "assets_added_to_album_count": "Добавен(и) са {count, plural, one {# актив} other {# актива}} в албума", @@ -481,6 +485,7 @@ "comments_are_disabled": "Коментарите са деактивирани", "confirm": "Потвърди", "confirm_admin_password": "Потвърждаване на паролата на администратора", + "confirm_delete_face": "Сигурни ли сте, че искате да изтриете лицето на {name} от актива?", "confirm_delete_shared_link": "Сигурни ли сте, че искате да изтриете тази споделена връзка?", "confirm_keep_this_delete_others": "Всички останали файлове в стека ще бъдат изтрити, с изключение на този файл. Сигурни ли сте, че искате да продължите?", "confirm_password": "Потвърдете паролата", @@ -533,6 +538,7 @@ "delete_album": "Изтрий албум", "delete_api_key_prompt": "Сигурни ли сте, че искате да изтриете този API ключ?", "delete_duplicates_confirmation": "Сигурни ли сте, че искате да изтриете окончателно тези дубликати?", + "delete_face": "Изтрий лице", "delete_key": "Изтрий ключ", "delete_library": "Изтрий библиотека", "delete_link": "Изтрий линк", @@ -600,6 +606,7 @@ "enabled": "Включено", "end_date": "Крайна дата", "error": "Грешка", + "error_delete_face": "Грешка при изтриване на лице от актива", "error_loading_image": "Грешка при зареждане на изображението", "error_title": "Грешка - нещо се обърка", "errors": { @@ -766,8 +773,10 @@ "go_to_folder": "Отиди в папката", "go_to_search": "Преминаване към търсене", "group_albums_by": "Групирай албум по...", + "group_country": "Групирай по държава", "group_no": "Няма група", "group_owner": "Групиране по собственик", + "group_places_by": "Групирай места по…", "group_year": "Групиране по година", "has_quota": "Лимит", "hi_user": "Здравей, {name} {email}", @@ -800,6 +809,7 @@ "include_shared_albums": "Включване на споделени албуми", "include_shared_partner_assets": "Включване на споделените с партньор елементи", "individual_share": "Индивидуално споделяне", + "individual_shares": "Индивидуални споделяния", "info": "Информация", "interval": { "day_at_onepm": "Всеки ден в 13:00", @@ -822,6 +832,7 @@ "latest_version": "Последна версия", "latitude": "Ширина", "leave": "Излез", + "lens_model": "Модел леща", "let_others_respond": "Позволете на другите да отговорят", "level": "Ниво", "library": "Библиотека", @@ -880,6 +891,7 @@ "month": "Месец", "more": "Още", "moved_to_trash": "Преместено в кошчето", + "mute_memories": "Изключване на звука на спомените", "my_albums": "Мои албуми", "name": "Име", "name_or_nickname": "Име или прякор", @@ -984,6 +996,7 @@ "pick_a_location": "Избери локация", "place": "Местоположение", "places": "Местоположения", + "places_count": "{count, plural, one {{count, number} Място} other {{count, number} Места}}", "play": "Възпроизвеждане", "play_memories": "Възпроизвеждане на спомени", "play_motion_photo": "Възпроизведи Motion Photo", @@ -1067,10 +1080,12 @@ "remove_from_shared_link": "Премахни от споделения линк", "remove_url": "Премахни URL", "remove_user": "Премахни потребителя", - "removed_api_key": "Премахни API Key: {name}", - "removed_from_archive": "Премахни от Архива", - "removed_from_favorites": "Премахнато от Любими", + "removed_api_key": "Премахат API ключ: {name}", + "removed_from_archive": "Премахни от архива", + "removed_from_favorites": "Премахнато от любими", "removed_from_favorites_count": "{count, plural, other {Премахнати #}} от Любими", + "removed_memory": "Премахнат спомен", + "removed_photo_from_memory": "Премахната снимка от спомен", "removed_tagged_assets": "Премахнат е етикетът от {count, plural, one {# елемент} other {# елемента}}", "rename": "Преименувай", "repair": "Поправи", @@ -1079,6 +1094,7 @@ "repository": "Хранилище", "require_password": "Изискай парола", "require_user_to_change_password_on_first_login": "Изисквай потребителят да промени паролата си при първото влизане", + "rescan": "Пресканиране", "reset": "Нулиране", "reset_password": "Нулиране на паролата", "reset_people_visibility": "Нулиране на видимостта на хората", @@ -1107,18 +1123,22 @@ "search": "Търсене", "search_albums": "Търси албуми", "search_by_context": "Търси по контекст", + "search_by_description": "Търси по описание", + "search_by_description_example": "Разходка в Сапа", "search_by_filename": "Търси по име на файла или разширение", "search_by_filename_example": "например IMG_1234.JPG или PNG", "search_camera_make": "Търси производител на камерата...", "search_camera_model": "Търси модел на камерата...", "search_city": "Търси град...", "search_country": "Търси държава...", + "search_for": "Търси за", "search_for_existing_person": "Търси съществуващ човек", "search_no_people": "Няма хора", "search_no_people_named": "Няма хора на име \"{name}\"", "search_options": "Опции за търсене", "search_people": "Търсете на хора", "search_places": "Търсене на места", + "search_rating": "Търси по рейтинг…", "search_settings": "Търсене на настройки", "search_state": "Търсене на щат...", "search_tags": "Търсене на етикети...", @@ -1165,6 +1185,7 @@ "shared_from_partner": "Снимки от {partner}", "shared_link_options": "Опции за споделена връзка", "shared_links": "Споделени връзки", + "shared_links_description": "Сподели снимки и видеа с линк", "shared_photos_and_videos_count": "{assetCount, plural, other {# споделени снимки и видеа.}}", "shared_with_partner": "Споделено с {partner}", "sharing": "Споделени", @@ -1187,6 +1208,7 @@ "show_person_options": "Показване на опции за лица", "show_progress_bar": "Показване на прогрес бара", "show_search_options": "Показване на опциите за търсене", + "show_shared_links": "Покажи споделени линкове", "show_slideshow_transition": "Покажи прехода на слайдшоуто", "show_supporter_badge": "Значка поддръжник", "show_supporter_badge_description": "Покажи значка поддръжник", @@ -1240,6 +1262,7 @@ "tag_created": "Създаден етикет: {tag}", "tag_feature_description": "Разглеждане на снимки и видеоклипове, групирани по теми с логически тагове", "tag_not_found_question": "Не можете да намерите етикет? Създайте такъв тук", + "tag_people": "Отбележи Хора", "tag_updated": "Актуализиран етикет: {tag}", "tagged_assets": "Тагнати {count, plural, one {# елемент} other {# елементи}}", "tags": "Етикет", @@ -1274,11 +1297,13 @@ "unfavorite": "Премахване от любимите", "unhide_person": "Покажи отново човека", "unknown": "Неизвестно", + "unknown_country": "Непозната Държава", "unknown_year": "Неизвестна година", "unlimited": "Неограничено", "unlink_motion_video": "Премахни връзката с видео", "unlink_oauth": "Премахни OAuth", "unlinked_oauth_account": "Премахни OAuth акаунт", + "unmute_memories": "Включване на звука на спомените", "unnamed_album": "Албум без име", "unnamed_album_delete_confirmation": "Сигурни ли сте, че искате да изтриете този албум?", "unnamed_share": "Споделяне без име", @@ -1332,6 +1357,7 @@ "view_all": "Преглед на всички", "view_all_users": "Преглед на всички потребители", "view_in_timeline": "Покажи във времева линия", + "view_link": "Преглед на връзката", "view_links": "Преглед на връзките", "view_name": "Прегледай", "view_next_asset": "Преглед на следващия файл", @@ -1348,4 +1374,4 @@ "yes": "Да", "you_dont_have_any_shared_links": "Нямате споделени връзки", "zoom_image": "Увеличаване на изображението" -} +} \ No newline at end of file diff --git a/i18n/ca.json b/i18n/ca.json index ff513d9683..77cb4a584e 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -41,6 +41,7 @@ "backup_settings": "Ajustes de les còpies de seguretat", "backup_settings_description": "Gestionar la configuració de la còpia de seguretat de la base de dades", "check_all": "Marca-ho tot", + "cleanup": "Neteja", "cleared_jobs": "Tasques esborrades per a: {job}", "config_set_by_file": "La configuració està definida per un fitxer de configuració", "confirm_delete_library": "Esteu segurs que voleu eliminar la llibreria {library}?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Habilita l'escaneig periòdic de biblioteques", "library_settings": "Llibreria externes", "library_settings_description": "Gestiona la configuració de les llibreries externes", - "library_tasks_description": "Realitza tasques de la bilbioteca", + "library_tasks_description": "Escaneja biblioteques externes per arxius nous o canviats", "library_watching_enable_description": "Consultar llibreries externes per detectar canvis en fitxers", "library_watching_settings": "Monitoratge de la llibreria (EXPERIMENTAL)", "library_watching_settings_description": "Monitorització automàtica de fitxers modificats", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Cerca imatges semànticament emprant incrustacions CLIP", "machine_learning_smart_search_enabled": "Activa la cerca intel·ligent", "machine_learning_smart_search_enabled_description": "Si està deshabilitat les imatges no es codificaran per la cerca intel·ligent.", - "machine_learning_url_description": "La URL del servidor d'aprenentatge automàtic. Si es proporciona més d'una URL, s'intentarà accedir a cada servidor en ordre fins que un d'ells respongui correctament.", + "machine_learning_url_description": "L'URL del servidor d'aprenentatge automàtic. Si es proporciona més d'un URL, s'intentarà accedir a cada servidor en ordre fins que un d'ells respongui correctament.", "manage_concurrency": "Gestiona la concurrència", "manage_log_settings": "Gestiona la configuració del registre", "map_dark_style": "Tema fosc", @@ -147,6 +148,8 @@ "map_settings": "Mapa", "map_settings_description": "Gestiona la configuració del mapa", "map_style_description": "URL a un tema del mapa style.json", + "memory_cleanup_job": "Neteja de memòries", + "memory_generate_job": "Generació de memòries", "metadata_extraction_job": "Extreure metadades", "metadata_extraction_job_description": "Extreu la informació de metadades de cada element, com per exemple el GPS i la resolució", "metadata_faces_import_setting": "Activar la importació de cares", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Activa la verificació de hash. No la desactiveu a menys que estigueu segurs de les implicacions", "storage_template_migration": "Migració de plantilles d'emmagatzematge", "storage_template_migration_description": "Aplica la {template} actual als elements pujats prèviament", - "storage_template_migration_info": "Els canvis de plantilla només s'aplicaran a nous elements. Per aplicar la plantilla rectroactivament a elements pujats prèviament, executeu la {job}.", + "storage_template_migration_info": "Les extensions es convertiran a minúscules. Els canvis de plantilla només s'aplicaran a nous elements. Per aplicar la plantilla rectroactivament a elements pujats prèviament, executeu la {job}.", "storage_template_migration_job": "Tasca de migració de la plantilla d'emmagatzematge", "storage_template_more_details": "Per obtenir més detalls sobre aquesta funció, consulteu la Storage Template i les seves implications", "storage_template_onboarding_description": "Quan està activada, aquesta funció organitzarà automàticament els fitxers en funció d'una plantilla definida per l'usuari. A causa de problemes d'estabilitat, la funció s'ha desactivat de manera predeterminada. Per obtenir més informació, consulteu la documentation.", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Nombre màxim de B-frames", "transcoding_max_b_frames_description": "Els valors més alts milloren l'eficiència de la compressió, però alenteixen la codificació. És possible que no sigui compatible amb l'acceleració de maquinari en dispositius antics. 0 desactiva els B-frames, mentre que -1 estableix aquest valor automàticament.", "transcoding_max_bitrate": "Taxa de bits màxima", - "transcoding_max_bitrate_description": "Establir una taxa de bits màxima pot fer que les mides dels fitxers siguin més previsibles amb un cost menor per a la qualitat. A 720p, els valors típics són 2600k per a VP9 o HEVC, o 4500k per a H.264. Desactivat si s'estableix a 0.", + "transcoding_max_bitrate_description": "Establir una taxa de bits màxima pot fer que les mides dels fitxers siguin més previsibles amb un cost menor per a la qualitat. A 720p, els valors típics són 2600 kbit/s per a VP9 o HEVC, o 4500 kbit/s per a H.264. Desactivat si s'estableix a 0.", "transcoding_max_keyframe_interval": "Interval màxim de fotogrames clau", "transcoding_max_keyframe_interval_description": "Estableix la distància màxima entre fotogrames clau. Els valors més baixos empitjoren l'eficiència de la compressió, però milloren els temps de cerca i poden millorar la qualitat en escenes amb moviment ràpid. 0 estableix aquest valor automàticament.", "transcoding_optimal_description": "Vídeos superiors a la resolució objectiu o que no tenen un format acceptat", @@ -391,6 +394,7 @@ "allow_edits": "Permet editar", "allow_public_user_to_download": "Permet que l'usuari públic pugui descarregar", "allow_public_user_to_upload": "Permet que l'usuari públic pugui carregar", + "alt_text_qr_code": "Codi QR", "anti_clockwise": "En sentit antihorari", "api_key": "Clau API", "api_key_description": "Aquest valor només es mostrarà una vegada. Assegureu-vos de copiar-lo abans de tancar la finestra.", @@ -410,7 +414,7 @@ "asset_description_updated": "La descripció del recurs s'ha actualitzat", "asset_filename_is_offline": "L'element {filename} està fora de línia", "asset_has_unassigned_faces": "L'element té cares no assignades", - "asset_hashing": "Hashing…", + "asset_hashing": "Hasheant…", "asset_offline": "Element fora de línia", "asset_offline_description": "Aquest recurs extern ja no es troba al disc. Poseu-vos en contacte amb el vostre administrador d'Immich per obtenir ajuda.", "asset_skipped": "Saltat", @@ -464,7 +468,7 @@ "check_logs": "Comprovar els registres", "choose_matching_people_to_merge": "Trieu les persones que coincideixin per combinar-les", "city": "Ciutat", - "clear": "Neteja", + "clear": "Buida", "clear_all": "Neteja-ho tot", "clear_all_recent_searches": "Esborra totes les cerques recents", "clear_message": "Neteja el missatge", @@ -481,6 +485,7 @@ "comments_are_disabled": "Els comentaris estan desactivats", "confirm": "Confirmar", "confirm_admin_password": "Confirmeu la contrasenya d'administrador", + "confirm_delete_face": "Estàs segur que vols eliminar la cara de {name} de les cares reconegudes?", "confirm_delete_shared_link": "Esteu segurs que voleu eliminar aquest enllaç compartit?", "confirm_keep_this_delete_others": "Excepte aquest element, tots els altres de la pila se suprimiran. Esteu segur que voleu continuar?", "confirm_password": "Confirmació de contrasenya", @@ -533,6 +538,7 @@ "delete_album": "Esborra l'àlbum", "delete_api_key_prompt": "Esteu segurs que voleu eliminar aquesta clau API?", "delete_duplicates_confirmation": "Esteu segurs que voleu eliminar aquests duplicats permanentment?", + "delete_face": "Esborrar cara", "delete_key": "Suprimeix la clau", "delete_library": "Suprimeix la Llibreria", "delete_link": "Esborra l'enllaç", @@ -600,6 +606,7 @@ "enabled": "Activat", "end_date": "Data final", "error": "Error", + "error_delete_face": "Error esborrant cara de les cares reconegudes", "error_loading_image": "Error carregant la imatge", "error_title": "Error - Quelcom ha anat malament", "errors": { @@ -766,8 +773,10 @@ "go_to_folder": "Anar al directori", "go_to_search": "Vés a cercar", "group_albums_by": "Agrupa àlbums per...", + "group_country": "Agrupar per país", "group_no": "Cap agrupació", "group_owner": "Agrupar per propietari", + "group_places_by": "Agrupar llocs per...", "group_year": "Agrupar per any", "has_quota": "Quota", "hi_user": "Hola {name} ({email})", @@ -800,6 +809,7 @@ "include_shared_albums": "Inclou àlbums compartits", "include_shared_partner_assets": "Incloure elements dels companys", "individual_share": "Compartit individualment", + "individual_shares": "Espais individuals", "info": "Informació", "interval": { "day_at_onepm": "Cada dia a les 13h", @@ -881,6 +891,7 @@ "month": "Mes", "more": "Més", "moved_to_trash": "S'ha mogut a la paperera", + "mute_memories": "Silenciar records", "my_albums": "Els meus àlbums", "name": "Nom", "name_or_nickname": "Nom o sobrenom", @@ -985,6 +996,7 @@ "pick_a_location": "Triar una ubicació", "place": "Lloc", "places": "Llocs", + "places_count": "{count, plural, one {{count, number} Lloc} other {{count, number} Llocs}}", "play": "Reprodueix", "play_memories": "Reproduir records", "play_motion_photo": "Reproduir Fotos en Moviment", @@ -1072,6 +1084,8 @@ "removed_from_archive": "Eliminat de l'arxiu", "removed_from_favorites": "Eliminat dels preferits", "removed_from_favorites_count": "{count, plural, other {# eliminats}} dels preferits", + "removed_memory": "Eliminat memòria", + "removed_photo_from_memory": "Eliminat foto de memòria", "removed_tagged_assets": "Etiqueta eliminada de {count, plural, one {# actiu} other {# actius}}", "rename": "Canviar nom", "repair": "Reparació", @@ -1080,6 +1094,7 @@ "repository": "Repositori", "require_password": "Requereix contrasenya", "require_user_to_change_password_on_first_login": "Requerir que l'usuari canviï la contrasenya en el primer inici de sessió", + "rescan": "Tornar a escanejar", "reset": "Restablir", "reset_password": "Restablir contrasenya", "reset_people_visibility": "Restablir la visibilitat de les persones", @@ -1108,6 +1123,8 @@ "search": "Cerca", "search_albums": "Buscar àlbums", "search_by_context": "Buscar per context", + "search_by_description": "Cercar per descripció", + "search_by_description_example": "Jornada de senderisme a Sapa", "search_by_filename": "Cerca per nom de fitxer o extensió", "search_by_filename_example": "per exemple IMG_1234.JPG o PNG", "search_camera_make": "Buscar per fabricant de càmara...", @@ -1121,6 +1138,7 @@ "search_options": "Opcions de cerca", "search_people": "Buscar persones", "search_places": "Buscar llocs", + "search_rating": "Buscar per qualificació...", "search_settings": "Configuració de cerca", "search_state": "Buscar per regió...", "search_tags": "Cercant etiquetes...", @@ -1167,6 +1185,7 @@ "shared_from_partner": "Fotos de {partner}", "shared_link_options": "Opcions d'enllaços compartits", "shared_links": "Enllaços compartits", + "shared_links_description": "Comparteix fotos i vídeos amb un enllaç", "shared_photos_and_videos_count": "{assetCount, plural, other {# fotos i vídeos compartits.}}", "shared_with_partner": "Compartit amb {partner}", "sharing": "Compartit", @@ -1189,6 +1208,7 @@ "show_person_options": "Mostra opcions de la persona", "show_progress_bar": "Mostra barra de progrés", "show_search_options": "Mostra opcions de cerca", + "show_shared_links": "Mostra els enllaços compartits", "show_slideshow_transition": "Mostra la transició de la presentació de diapositives", "show_supporter_badge": "Insígnia de contribuent", "show_supporter_badge_description": "Mostra una insígnia de contributor", @@ -1242,6 +1262,7 @@ "tag_created": "Etiqueta creada: {tag}", "tag_feature_description": "Exploreu fotos i vídeos agrupats per temes d'etiquetes lògiques", "tag_not_found_question": "No trobeu una etiqueta? Crear una nova etiqueta", + "tag_people": "Etiquetar personas", "tag_updated": "Etiqueta actualizada: {tag}", "tagged_assets": "{count, plural, one {#Etiquetat} other {#Etiquetats}} {count, plural, one {# actiu} other {# actius}}", "tags": "Etiquetes", @@ -1276,11 +1297,13 @@ "unfavorite": "Reverteix preferit", "unhide_person": "Mostra persona", "unknown": "Desconegut", + "unknown_country": "País Desconegut", "unknown_year": "Any desconegut", "unlimited": "Il·limitat", "unlink_motion_video": "Desvincular vídeo en moviment", "unlink_oauth": "Desvincula OAuth", "unlinked_oauth_account": "Compte Oauth desvinculat", + "unmute_memories": "Activar el so dels records", "unnamed_album": "Àlbum sense nom", "unnamed_album_delete_confirmation": "Segur que voleu esborrar aquest àlbum?", "unnamed_share": "Compartit sense nom", @@ -1334,6 +1357,7 @@ "view_all": "Veure tot", "view_all_users": "Mostra tot els usuaris", "view_in_timeline": "Mostrar a la línia de temps", + "view_link": "Veure enllaç", "view_links": "Mostra enllaços", "view_name": "Veure", "view_next_asset": "Mostra el següent element", @@ -1350,4 +1374,4 @@ "yes": "Sí", "you_dont_have_any_shared_links": "No tens cap enllaç compartit", "zoom_image": "Ampliar Imatge" -} +} \ No newline at end of file diff --git a/i18n/cs.json b/i18n/cs.json index 9551ba0b9e..f43be9ee9f 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -41,6 +41,7 @@ "backup_settings": "Nastavení zálohování", "backup_settings_description": "Správa nastavení zálohování databáze", "check_all": "Vše zkontrolovat", + "cleanup": "Vyčištění", "cleared_jobs": "Hotové úlohy pro: {job}", "config_set_by_file": "Konfigurace je aktuálně prováděna konfiguračním souborem", "confirm_delete_library": "Opravdu chcete odstranit knihovnu {library}?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Povolit pravidelné prohledávání knihovny", "library_settings": "Externí knihovna", "library_settings_description": "Správa nastavení externí knihovny", - "library_tasks_description": "Provádění úkolů v knihovně", + "library_tasks_description": "Vyhledávání nových nebo změněných položek v externích knihovnách", "library_watching_enable_description": "Sledovat změny souborů v externích knihovnách", "library_watching_settings": "Sledování knihovny (EXPERIMENTÁLNÍ)", "library_watching_settings_description": "Automatické sledování změněných souborů", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Sémantické vyhledávání obrázků pomocí CLIP embeddings", "machine_learning_smart_search_enabled": "Povolit chytré vyhledávání", "machine_learning_smart_search_enabled_description": "Pokud je vypnuto, obrázky nebudou kódovány pro inteligentní vyhledávání.", - "machine_learning_url_description": "URL serveru strojového učení. Pokud je zadáno více URL adres, budou jednotlivé servery zkoušeny postupně, dokud jeden z nich neodpoví úspěšně, a to v pořadí od prvního k poslednímu.", + "machine_learning_url_description": "URL serveru strojového učení. Pokud je zadáno více URL adres, budou jednotlivé servery zkoušeny postupně, dokud jeden z nich neodpoví úspěšně, a to v pořadí od prvního k poslednímu. Servery, které neodpoví, budou dočasně ignorovány, dokud nebudou opět online.", "manage_concurrency": "Správa souběžnosti", "manage_log_settings": "Správa nastavení protokolu", "map_dark_style": "Tmavý motiv", @@ -147,6 +148,8 @@ "map_settings": "Mapa", "map_settings_description": "Správa nastavení mapy", "map_style_description": "URL na style.json motivu", + "memory_cleanup_job": "Promazání vzpomínek", + "memory_generate_job": "Vytvoření vzpomínek", "metadata_extraction_job": "Extrakce metadat", "metadata_extraction_job_description": "Získání informací o metadatech z každého snímku, jako je GPS, obličeje a rozlišení", "metadata_faces_import_setting": "Povolit import obličeje", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Povolí ověřování hashe, nevypínejte ji, pokud si nejste jisti důsledky", "storage_template_migration": "Migrace šablony úložiště", "storage_template_migration_description": "Použít aktuální {template} na dříve nahrané položky", - "storage_template_migration_info": "Změny šablon se uplatní pouze u nových položek. Chcete-li šablonu zpětně použít na dříve nahrané položky, spusťte {job}.", + "storage_template_migration_info": "Šablona úložiště převede všechny přípony na malá písmena. Změny šablon se uplatní pouze u nových položek. Chcete-li šablonu zpětně použít na dříve nahrané položky, spusťte {job}.", "storage_template_migration_job": "Úloha migrace šablony úložiště", "storage_template_more_details": "Další podrobnosti o této funkci naleznete v sekci Šablona úložiště včetně jejích důsledků", "storage_template_onboarding_description": "Je-li tato funkce povolena, automaticky uspořádá soubory na základě uživatelem definované šablony. Z důvodu problémů se stabilitou byla tato funkce ve výchozím nastavení vypnuta. Další informace naleznete v dokumentaci.", @@ -288,7 +291,7 @@ "transcoding_constant_quality_mode_description": "ICQ je lepší než CQP, ale některá zařízení pro hardwarovou akceleraci tento režim nepodporují. Nastavením této volby se při použití kódování založeného na kvalitě upřednostní zadaný režim. Ignorováno NVENC, protože nepodporuje ICQ.", "transcoding_constant_rate_factor": "Faktor konstantní rychlosti (-crf)", "transcoding_constant_rate_factor_description": "Úroveň kvality videa. Typické hodnoty jsou 23 pro H.264, 28 pro HEVC, 31 pro VP9 a 35 pro AV1. Nižší je lepší, ale vytváří větší soubory.", - "transcoding_disabled_description": "Nepřekódovávejte žádná videa, u některých klientů může dojít k znemožnění přehrávání", + "transcoding_disabled_description": "Nepřekódovávat žádná videa, u některých klientů může dojít k znemožnění přehrávání", "transcoding_encoding_options": "Možnosti kódování", "transcoding_encoding_options_description": "Nastavte kodeky, rozlišení, kvalitu a další možnosti pro kódovaná videa", "transcoding_hardware_acceleration": "Hardwarová akcelerace", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Maximální počet B-snímků", "transcoding_max_b_frames_description": "Vyšší hodnoty zvyšují účinnost komprese, ale zpomalují kódování. Nemusí být kompatibilní s hardwarovou akcelerací na starších zařízeních. Hodnota 0 zakáže B-snímky, zatímco -1 tuto hodnotu nastaví automaticky.", "transcoding_max_bitrate": "Maximální datový tok", - "transcoding_max_bitrate_description": "Nastavení maximálního datového toku může zvýšit předvídatelnost velikosti souborů za cenu menší újmy na kvalitě. Při rozlišení 720p jsou typické hodnoty 2600k pro VP9 nebo HEVC nebo 4500k pro H.264. Je zakázáno, pokud je nastavena hodnota 0.", + "transcoding_max_bitrate_description": "Nastavení maximálního datového toku může zvýšit předvídatelnost velikosti souborů za cenu menší újmy na kvalitě. Při rozlišení 720p jsou typické hodnoty 2600 kbit/s pro VP9 nebo HEVC nebo 4500 kbit/s pro H.264. Je zakázáno, pokud je nastavena hodnota 0.", "transcoding_max_keyframe_interval": "Maximální interval klíčových snímků", "transcoding_max_keyframe_interval_description": "Nastavuje maximální vzdálenost mezi klíčovými snímky. Nižší hodnoty zhoršují účinnost komprese, ale zlepšují rychlost při přeskakování a mohou zlepšit kvalitu ve scénách s rychlým pohybem. Hodnota 0 nastavuje tuto hodnotu automaticky.", "transcoding_optimal_description": "Videa s vyšším než cílovým rozlišením nebo videa, která nejsou v akceptovaném formátu", @@ -391,6 +394,7 @@ "allow_edits": "Povolit úpravy", "allow_public_user_to_download": "Povolit veřejnosti stahovat", "allow_public_user_to_upload": "Povolit veřejnosti nahrávat", + "alt_text_qr_code": "Obrázek QR kódu", "anti_clockwise": "Proti směru hodinových ručiček", "api_key": "API klíč", "api_key_description": "Tato hodnota se zobrazí pouze jednou. Před zavřením okna ji nezapomeňte zkopírovat.", @@ -464,11 +468,11 @@ "check_logs": "Zkontrolujte protokoly", "choose_matching_people_to_merge": "Zvolte odpovídající osoby ke sloučení", "city": "Město", - "clear": "Vyčistit", + "clear": "Vymazat", "clear_all": "Vymazat vše", "clear_all_recent_searches": "Vymazat všechna nedávná vyhledávání", - "clear_message": "Vyčistit zprávu", - "clear_value": "Vyčistit hodnotu", + "clear_message": "Vymazat zprávu", + "clear_value": "Vymazat hodnotu", "clockwise": "Po směru hodinových ručiček", "close": "Zavřít", "collapse": "Sbalit", @@ -481,6 +485,7 @@ "comments_are_disabled": "Komentáře jsou vypnuty", "confirm": "Potvrdit", "confirm_admin_password": "Potvrzení hesla správce", + "confirm_delete_face": "Opravdu chcete z položky odstranit obličej osoby {name}?", "confirm_delete_shared_link": "Opravdu chcete odstranit tento sdílený odkaz?", "confirm_keep_this_delete_others": "Všechny ostatní položky v tomto uskupení mimo této budou odstraněny. Opravdu chcete pokračovat?", "confirm_password": "Potvrzení hesla", @@ -533,6 +538,7 @@ "delete_album": "Smazat album", "delete_api_key_prompt": "Opravdu chcete tento API klíč odstranit?", "delete_duplicates_confirmation": "Opravdu chcete tyto duplicity trvale odstranit?", + "delete_face": "Odstranit obličej", "delete_key": "Smazat klíč", "delete_library": "Smazat knihovnu", "delete_link": "Smazat odkaz", @@ -600,6 +606,7 @@ "enabled": "Povoleno", "end_date": "Konečné datum", "error": "Chyba", + "error_delete_face": "Chyba při odstraňování obličeje z položky", "error_loading_image": "Chyba při načítání obrázku", "error_title": "Chyba - Něco se pokazilo", "errors": { @@ -751,8 +758,8 @@ "features_setting_description": "Správa funkcí aplikace", "file_name": "Název souboru", "file_name_or_extension": "Název nebo přípona souboru", - "filename": "Filename", - "filetype": "Filetype", + "filename": "Název souboru", + "filetype": "Typ souboru", "filter_people": "Filtrovat lidi", "find_them_fast": "Najděte je rychle vyhledáním jejich jména", "fix_incorrect_match": "Opravit nesprávnou shodu", @@ -884,6 +891,7 @@ "month": "Měsíc", "more": "Více", "moved_to_trash": "Přesunuto do koše", + "mute_memories": "Ztlumit vzpomínky", "my_albums": "Moje alba", "name": "Jméno", "name_or_nickname": "Jméno nebo přezdívka", @@ -979,6 +987,7 @@ "permanently_deleted_asset": "Položka trvale odstraněna", "permanently_deleted_assets_count": "{count, plural, one {Položka trvale vymazána} other {Položky trvale vymazány}}", "person": "Osoba", + "person_birthdate": "Narozen/a {date}", "person_hidden": "{name}{hidden, select, true { (skryto)} other {}}", "photo_shared_all_users": "Vypadá to, že jste fotky sdíleli se všemi uživateli, nebo nemáte žádného uživatele, se kterým byste je mohli sdílet.", "photos": "Fotky", @@ -1076,6 +1085,8 @@ "removed_from_archive": "Odstraněno z archivu", "removed_from_favorites": "Odstraněno z oblíbených", "removed_from_favorites_count": "{count, plural, one {Odstraněn #} few {Odstraněny #} other {Odstraněno #}} z oblíbených", + "removed_memory": "Vzpomínka odstraněna", + "removed_photo_from_memory": "Fotografie odstraněna ze vzpomínky", "removed_tagged_assets": "Odstraněná značka z {count, plural, one {# položky} other {# položek}}", "rename": "Přejmenovat", "repair": "Opravy", @@ -1084,6 +1095,7 @@ "repository": "Repozitář", "require_password": "Požadovat heslo", "require_user_to_change_password_on_first_login": "Požadovat, aby si uživatel při prvním přihlášení změnil heslo", + "rescan": "Znovu prohledat", "reset": "Výchozí", "reset_password": "Obnovit heslo", "reset_people_visibility": "Obnovit viditelnost lidí", @@ -1127,6 +1139,7 @@ "search_options": "Možnosti vyhledávání", "search_people": "Vyhledat lidi", "search_places": "Vyhledat místa", + "search_rating": "Vyhledávání podle hodnocení...", "search_settings": "Hledat nastavení", "search_state": "Vyhledat stát...", "search_tags": "Vyhledávat značky...", @@ -1250,6 +1263,7 @@ "tag_created": "Vytvořena značka: {tag}", "tag_feature_description": "Procházení fotografií a videí seskupených podle témat logických značek", "tag_not_found_question": "Nemůžete najít značku? Vytvořte novou.", + "tag_people": "Označit lidi", "tag_updated": "Aktualizována značka: {tag}", "tagged_assets": "Přiřazena značka {count, plural, one {# položce} other {# položkám}}", "tags": "Značky", @@ -1290,6 +1304,7 @@ "unlink_motion_video": "Odpojit pohyblivé video", "unlink_oauth": "Zrušit OAuth propojení", "unlinked_oauth_account": "OAuth účet odpojen", + "unmute_memories": "Zrušit ztlumení vzpomínek", "unnamed_album": "Nepojmenované album", "unnamed_album_delete_confirmation": "Opravdu chcete toto album smazat?", "unnamed_share": "Nepojmenované sdílení", @@ -1343,6 +1358,7 @@ "view_all": "Zobrazit vše", "view_all_users": "Zobrazit všechny uživatele", "view_in_timeline": "Zobrazit na časové ose", + "view_link": "Zobrazit odkaz", "view_links": "Zobrazit odkazy", "view_name": "Zobrazit", "view_next_asset": "Zobrazit další položku", @@ -1355,8 +1371,8 @@ "welcome": "Vítejte", "welcome_to_immich": "Vítejte v Immichi", "year": "Rok", - "years_ago": "Před {years, plural, one {# rokem} other {# lety}}", + "years_ago": "Před {years, plural, one {rokem} other {# lety}}", "yes": "Ano", "you_dont_have_any_shared_links": "Nemáte žádné sdílené odkazy", "zoom_image": "Zvětšit obrázek" -} +} \ No newline at end of file diff --git a/i18n/da.json b/i18n/da.json index ec05232e6f..0e7ce712e8 100644 --- a/i18n/da.json +++ b/i18n/da.json @@ -147,6 +147,8 @@ "map_settings": "Kort", "map_settings_description": "Administrer kortindstillinger", "map_style_description": "URL til en style.json for et korttema", + "memory_cleanup_job": "Mindeoprydning", + "memory_generate_job": "Mindegeneration", "metadata_extraction_job": "Udtræk metadata", "metadata_extraction_job_description": "Udtræk metadataoplysninger fra hvert Billede/Video, såsom GPS og opløsning", "metadata_faces_import_setting": "Aktivér for at importere ansigter", @@ -299,7 +301,7 @@ "transcoding_max_b_frames": "Maksimum B-frames", "transcoding_max_b_frames_description": "Højere værdier forbedrer kompressionseffektivitet, men kan gøre indkodning langsommere. Er måske ikke kompatibelt med hardware-acceleration på ældre enheder. 0 slår B-frames fra, mens -1 sætter denne værdi automatisk.", "transcoding_max_bitrate": "Maksimal bitrate", - "transcoding_max_bitrate_description": "At sætte en maksmimal bitrate kan gøre filstørrelserne mere forudsigelige med et lille tab i kvalitet. Ved 720p er almindelige værdier 2600k for VP9 eller HEVC, eller 4500K for H.264. Slået fra hvis sat til 0.", + "transcoding_max_bitrate_description": "At sætte en maksmimal bitrate kan gøre filstørrelserne mere forudsigelige med et lille tab i kvalitet. Ved 720p er almindelige værdier 2600 kbit/s for VP9 eller HEVC, eller 4500 kbit/s for H.264. Slået fra hvis sat til 0.", "transcoding_max_keyframe_interval": "Maksimal keyframe-interval", "transcoding_max_keyframe_interval_description": "Sætter den maksimale frameafstand mellem keyframes. Lavere værdier forringer kompressionseffektiviteten, men forbedrer søgetider og kan forbedre kvaliteten i scener med hurtig bevægelse. 0 sætter denne værdi automatisk.", "transcoding_optimal_description": "Videoer højere end målopløsningen eller ikke i et godkendt format", @@ -481,6 +483,7 @@ "comments_are_disabled": "Kommentarer er slået fra", "confirm": "Bekræft", "confirm_admin_password": "Bekræft administratoradgangskode", + "confirm_delete_face": "Er du sikker på, du vil slette {name}s ansigt fra denne mediefil?", "confirm_delete_shared_link": "Er du sikker på, at du vil slette dette delte link?", "confirm_keep_this_delete_others": "Alle andre aktiver i stakken vil blive slettet undtagen dette aktiv. Er du sikker på, at du vil fortsætte?", "confirm_password": "Bekræft adgangskode", @@ -504,7 +507,7 @@ "create_library": "Opret bibliotek", "create_link": "Opret link", "create_link_to_share": "Opret link for at dele", - "create_link_to_share_description": "Lad alle med linket se de(t) valgte billede(r)", + "create_link_to_share_description": "Tillad alle med linket at se de(t) valgte billede(r)", "create_new_person": "Opret ny person", "create_new_person_hint": "Tildel valgte aktiver til en ny person", "create_new_user": "Opret ny bruger", @@ -519,20 +522,21 @@ "date_after": "Dato efter", "date_and_time": "Dato og klokkeslæt", "date_before": "Dato før", - "date_of_birth_saved": "Fødselsdatoen blev gemt", + "date_of_birth_saved": "Fødselsdatoen blev gemt korrekt", "date_range": "Datointerval", "day": "Dag", - "deduplicate_all": "Dedupliker alle", + "deduplicate_all": "Kopier alle", "deduplication_criteria_1": "Billedstørrelse i bytes", "deduplication_criteria_2": "Antal EXIF-data", "deduplication_info": "Deduplikerings info", "deduplication_info_description": "For automatisk at forudvælge emner og fjerne dubletter i bulk ser vi på:", "default_locale": "Standardlokalitet", - "default_locale_description": "Formatér datoer og tal", + "default_locale_description": "Formatér datoer og tal baseret på din browsers regions indstillinger", "delete": "Slet", "delete_album": "Slet album", "delete_api_key_prompt": "Er du sikker på, at du vil slette denne API-nøgle?", "delete_duplicates_confirmation": "Er du sikker på, at du vil slette disse dubletter permanent?", + "delete_face": "Slet ansigt", "delete_key": "Slet nøgle", "delete_library": "Slet bibliotek", "delete_link": "Slet link", @@ -565,7 +569,7 @@ "download_settings": "Download", "download_settings_description": "Administrer indstillinger relateret til mediefil-downloads", "downloading": "Downloader", - "downloading_asset_filename": "Downloader aktiv {filename}", + "downloading_asset_filename": "Downloader mediefil {filename}", "drop_files_to_upload": "Slip filer hvor som helst for at uploade dem", "duplicates": "Duplikater", "duplicates_description": "Løs hver gruppe ved at angive, hvilke, hvis nogen, er dubletter", @@ -600,6 +604,7 @@ "enabled": "Aktiveret", "end_date": "Slutdato", "error": "Fejl", + "error_delete_face": "Fejl ved sletning af ansigt fra mediefil", "error_loading_image": "Fejl ved indlæsning af billede", "error_title": "Fejl - Noget gik galt", "errors": { @@ -607,11 +612,11 @@ "cannot_navigate_previous_asset": "Kan ikke navigere til forrige mediefil", "cant_apply_changes": "Ændringerne kan ikke anvendes", "cant_change_activity": "Kan ikke {enabled, select, true {disable} other {enable}} aktivitet", - "cant_change_asset_favorite": "Kan ikke ændre favorit til aktiv", + "cant_change_asset_favorite": "Kan ikke ændre favorit til mediefil", "cant_change_metadata_assets_count": "Kan ikke ændre metadata for {count, plural, one {# objekt} other {# objekter}}", "cant_get_faces": "Kan ikke hente ansigter", "cant_get_number_of_comments": "Kan ikke få antallet af kommentarer", - "cant_search_people": "Kan ikke søge efter folk", + "cant_search_people": "Kan ikke søge efter personer", "cant_search_places": "Kan ikke søge efter steder", "cleared_jobs": "Ryddede opgaver for: {job}", "error_adding_assets_to_album": "Fejl i tilføjelse af mediefiler til album", @@ -620,20 +625,20 @@ "error_downloading": "Fejl i download af {filename}", "error_hiding_buy_button": "Fejl i skjulning af køb-knap", "error_removing_assets_from_album": "Fejl i fjernelse af mediefiler fra album. Tjek konsol for flere detaljer", - "error_selecting_all_assets": "Fejl ved valg af alle aktiver", + "error_selecting_all_assets": "Fejl ved valg af alle mediefiler", "exclusion_pattern_already_exists": "Denne udelukkelsesmønster findes allerede.", "failed_job_command": "Kommando {command} slog fejl for opgave: {job}", "failed_to_create_album": "Oprettelse af album mislykkedes", "failed_to_create_shared_link": "Oprettelse af delt link mislykkedes", "failed_to_edit_shared_link": "Redigering af delt link mislykkedes", - "failed_to_get_people": "Det lykkedes ikke at hente folk", - "failed_to_keep_this_delete_others": "Kunne ikke beholde dette aktiv og slette de andre aktiver", + "failed_to_get_people": "Det lykkedes ikke at hente personer", + "failed_to_keep_this_delete_others": "Kunne ikke beholde denne mediefil og slette de andre mediefiler", "failed_to_load_asset": "Indlæsning af mediefil mislykkedes", "failed_to_load_assets": "Indlæsning af mediefiler mislykkedes", "failed_to_load_people": "Indlæsning af personer mislykkedes", "failed_to_remove_product_key": "Fjernelse af produktnøgle mislykkedes", - "failed_to_stack_assets": "Det lykkedes ikke at stable aktiver", - "failed_to_unstack_assets": "Det lykkedes ikke at fjerne stablen af aktiver", + "failed_to_stack_assets": "Det lykkedes ikke at stable mediefiler", + "failed_to_unstack_assets": "Det lykkedes ikke at fjerne gruperingen af mediefiler", "import_path_already_exists": "Denne importsti findes allerede.", "incorrect_email_or_password": "Forkert email eller kodeord", "paths_validation_failed": "{paths, plural, one {# sti} other {# stier}} slog fejl ved validering", @@ -641,7 +646,7 @@ "quota_higher_than_disk_size": "Du har sat en kvote der er større end disken", "repair_unable_to_check_items": "Kunne ikke tjekke {count, select, one {element} other {elementer}}", "unable_to_add_album_users": "Ikke i stand til at tilføje brugere til album", - "unable_to_add_assets_to_shared_link": "Kan ikke tilføje aktiver til delt link", + "unable_to_add_assets_to_shared_link": "Kan ikke tilføje mediefiler til det delte link", "unable_to_add_comment": "Ikke i stand til at tilføje kommentar", "unable_to_add_exclusion_pattern": "Kunne ikke tilføje udelukkelsesmønster", "unable_to_add_import_path": "Kunne ikke tilføje importsti", @@ -651,7 +656,7 @@ "unable_to_archive_unarchive": "Ude af stand til at {archived, select, true {arkivere} other {fjerne fra arkiv}}", "unable_to_change_album_user_role": "Ikke i stand til at ændre albumbrugerens rolle", "unable_to_change_date": "Ikke i stand til at ændre dato", - "unable_to_change_favorite": "Kan ikke ændre favorit for aktiv", + "unable_to_change_favorite": "Kan ikke ændre favorit for mediefil", "unable_to_change_location": "Ikke i stand til at ændre sted", "unable_to_change_password": "Kunne ikke ændre adgangskode", "unable_to_change_visibility": "Kan ikke ændre synligheden for {count, plural, one {# person} other {# personer}}", @@ -698,16 +703,16 @@ "unable_to_remove_deleted_assets": "Kunne ikke fjerne offlinefiler", "unable_to_remove_library": "Ikke i stand til at fjerne bibliotek", "unable_to_remove_partner": "Ikke i stand til at fjerne partner", - "unable_to_remove_reaction": "Ikke i stand til at reaktion", + "unable_to_remove_reaction": "Ikke i stand til at fjerne reaktion", "unable_to_repair_items": "Ikke i stand til at reparere ting", "unable_to_reset_password": "Ikke i stand til at nulstille adgangskode", "unable_to_resolve_duplicate": "Kunne ikke opklare duplikat", - "unable_to_restore_assets": "Kunne ikke genoprette medier", - "unable_to_restore_trash": "Ikke i stand til at genoprette skrald", - "unable_to_restore_user": "Ikke i stand til at genoprette bruger", + "unable_to_restore_assets": "Kunne ikke gendanne medierfil", + "unable_to_restore_trash": "Ikke i stand til at gendanne fra skraldespanden", + "unable_to_restore_user": "Ikke i stand til at gendanne bruger", "unable_to_save_album": "Ikke i stand til at gemme album", "unable_to_save_api_key": "Kunne ikke gemme API-nøgle", - "unable_to_save_date_of_birth": "Kan ikke gemme fødselsdatoen", + "unable_to_save_date_of_birth": "Kunne ikke gemme fødselsdatoen", "unable_to_save_name": "Ikke i stand til at gemme navn", "unable_to_save_profile": "Ikke i stand til at gemme profil", "unable_to_save_settings": "Ikke i stand til at gemme indstillinger", @@ -729,7 +734,7 @@ "unable_to_upload_file": "Filen kunne ikke uploades" }, "exif": "Exif", - "exit_slideshow": "Forlad slideshow", + "exit_slideshow": "Afslut slideshow", "expand_all": "Udvid alle", "expire_after": "Udløb efter", "expired": "Udløbet", @@ -742,7 +747,7 @@ "external": "Ekstern", "external_libraries": "Eksterne biblioteker", "face_unassigned": "Ikke tildelt", - "failed_to_load_assets": "Kunne ikke indlæse aktiver", + "failed_to_load_assets": "Kunne ikke indlæse mediefiler", "favorite": "Favorit", "favorite_or_unfavorite_photo": "Tilføj eller fjern fra yndlingsbilleder", "favorites": "Favoritter", @@ -774,10 +779,10 @@ "has_quota": "Har kvote", "hi_user": "Hej {name} ({email})", "hide_all_people": "Skjul alle personer", - "hide_gallery": "Gem galleri", + "hide_gallery": "Skjul galleri", "hide_named_person": "Skjul person {name}", - "hide_password": "Gem adgangskode", - "hide_person": "Gem person", + "hide_password": "Skjul adgangskode", + "hide_person": "Skjul person", "hide_unnamed_people": "Skjul unavngivne personer", "host": "Host", "hour": "Time", @@ -817,7 +822,7 @@ "keep": "Behold", "keep_all": "Behold alle", "keep_this_delete_others": "Behold dette, slet andre", - "kept_this_deleted_others": "Beholdt dette aktiv og slettede {count, plural, one {# aktiv} other {# aktiver}}", + "kept_this_deleted_others": "Beholdt denne mediefil og slettede {count, plural, one {# aktiv} other {# aktiver}}", "keyboard_shortcuts": "Tastaturgenveje", "language": "Sprog", "language_setting_description": "Vælg dit foretrukne sprog", @@ -837,8 +842,8 @@ "link_to_oauth": "Link til OAuth", "linked_oauth_account": "Tilsluttet OAuth-konto", "list": "Liste", - "loading": "Loader", - "loading_search_results_failed": "At loade søgeresultater slog fejl", + "loading": "Indlæser", + "loading_search_results_failed": "Indlæsning af søgeresultater fejlede", "log_out": "Log ud", "log_out_all_devices": "Log ud af alle enheder", "logged_out_all_devices": "Logget ud af alle enheder", @@ -874,7 +879,7 @@ "merge": "Sammenflet", "merge_people": "Sammenflet personer", "merge_people_limit": "Du kan kun flette op til 5 ansigter ad gangen", - "merge_people_prompt": "Vil du slå disse mennesker sammen? Denne handling er uigenkaldelig.", + "merge_people_prompt": "Vil du flette disse mennesker sammen? Denne handling er uigenkaldelig.", "merge_people_successfully": "Personer sammenflettet med succes", "merged_people_count": "{count, plural, one {# person} other {# personer}} lagt sammen", "minimize": "Minimér", @@ -901,7 +906,7 @@ "no_albums_message": "Opret et album for at organisere dine billeder og videoer", "no_albums_with_name_yet": "Det ser ud til, at du ikke har noget album med dette navn endnu.", "no_albums_yet": "Det ser ud til, at du ikke har nogen album endnu.", - "no_archived_assets_message": "Arkivér billeder og fotos for at gemme dem væk fra dit Billed-view", + "no_archived_assets_message": "Arkivér billeder og videoer for at gemme dem væk fra din Billede oversigt", "no_assets_message": "KLIK FOR AT UPLOADE DIT FØRSTE BILLEDE", "no_duplicates_found": "Ingen duplikater fundet.", "no_exif_info_available": "Ingen tilgængelig exif information", @@ -948,7 +953,7 @@ "owner": "Ejer", "partner": "Partner", "partner_can_access": "{partner} kan tilgå", - "partner_can_access_assets": "Alle dine billeder og videoer, bortset fra dem i Arkiveret og Slettet", + "partner_can_access_assets": "Alle dine billeder og videoer, bortset fra dem i Arkivet og Slettet", "partner_can_access_location": "Stedet, hvor dine billeder blev taget", "partner_sharing": "Partnerdeling", "partners": "Partnere", @@ -992,7 +997,7 @@ "play": "Afspil", "play_memories": "Afspil minder", "play_motion_photo": "Afspil bevægelsesbillede", - "play_or_pause_video": "Afspil eller paus video", + "play_or_pause_video": "Afspil eller pause video", "port": "Port", "preset": "Forudindstilling", "preview": "Forhåndsvisning", @@ -1046,7 +1051,7 @@ "reassign": "Gentildel", "reassigned_assets_to_existing_person": "{count, plural, one {# mediefil} other {# mediefiler}} er blevet gentildelt til {name, select, null {en eksisterende person} other {{name}}}", "reassigned_assets_to_new_person": "Gentildelt {count, plural, one {# aktiv} other {# aktiver}} til en ny person", - "reassing_hint": "Tildel valgte aktiver til en eksisterende person", + "reassing_hint": "Tildel valgte mediefiler til en eksisterende person", "recent": "For nylig", "recent-albums": "Seneste albums", "recent_searches": "Seneste søgninger", @@ -1064,9 +1069,9 @@ "remove": "Fjern", "remove_assets_album_confirmation": "Er du sikker på, at du vil fjerne {count, plural, one {# aktiv} other {# aktiver}} fra albummet?", "remove_assets_shared_link_confirmation": "Er du sikker på, at du vil fjerne {count, plural, one {# aktiv} other {# aktiver}} fra dette delte link?", - "remove_assets_title": "Fjern aktiver?", + "remove_assets_title": "Fjern mediefiler?", "remove_custom_date_range": "Fjern tilpasset datointerval", - "remove_deleted_assets": "Fjern fra offlinefiler", + "remove_deleted_assets": "Fjern slettede mediefiler", "remove_from_album": "Fjern fra album", "remove_from_favorites": "Fjern fra favoritter", "remove_from_shared_link": "Fjern fra delt link", @@ -1076,14 +1081,17 @@ "removed_from_archive": "Fjernet fra arkiv", "removed_from_favorites": "Fjernet fra favoritter", "removed_from_favorites_count": "{count, plural, other {Fjernede #}} fra favoritter", + "removed_memory": "Fjernede minde", + "removed_photo_from_memory": "Fjernede foto fra minde", "removed_tagged_assets": "Fjernede tag fra {count, plural, one {# aktiv} other {# aktiver}}", "rename": "Omdøb", "repair": "Reparér", - "repair_no_results_message": "Utrackede og manglende filer vil blive vist her", + "repair_no_results_message": "Usporede og manglende filer vil blive vist her", "replace_with_upload": "Erstat med upload", "repository": "Depot", "require_password": "Kræv adgangskode", "require_user_to_change_password_on_first_login": "Kræv at bruger skifter adgangskode ved første login", + "rescan": "Genopfrisk", "reset": "Nulstil", "reset_password": "Nulstil adgangskode", "reset_people_visibility": "Nulstil personsynlighed", @@ -1093,19 +1101,19 @@ "restore": "Gendan", "restore_all": "Gendan alle", "restore_user": "Gendan bruger", - "restored_asset": "Gendannet aktiv", + "restored_asset": "Gendannet mediefilen", "resume": "Genoptag", "retry_upload": "Forsøg upload igen", "review_duplicates": "Gennemgå dubletter", "role": "Rolle", - "role_editor": "Editor", + "role_editor": "Redaktør", "role_viewer": "Seer", "save": "Gem", "saved_api_key": "Gemt API-nøgle", "saved_profile": "Gemte profil", "saved_settings": "Gemte indstillinger", "say_something": "Skriv noget", - "scan_all_libraries": "Skan gennem alle biblioteker", + "scan_all_libraries": "Skan alle biblioteker", "scan_library": "Skan", "scan_settings": "Skanningsindstillinger", "scanning_for_album": "Skanner efter albummer...", @@ -1127,8 +1135,9 @@ "search_options": "Søgemuligheder", "search_people": "Søg i personer", "search_places": "Søg i steder", + "search_rating": "Søg efter vurdering...", "search_settings": "søgeindstillinger", - "search_state": "Søg efter stat...", + "search_state": "Søg efter lansdel...", "search_tags": "Søg tags...", "search_timezone": "Søg i tidszone...", "search_type": "Søg efter type", @@ -1183,15 +1192,15 @@ "show_album_options": "Vis albumindstillinger", "show_albums": "Vis albummer", "show_all_people": "Vis alle personer", - "show_and_hide_people": "Vis & gem personer", + "show_and_hide_people": "Vis & skjul personer", "show_file_location": "Vis filplacering", "show_gallery": "Vis galleri", - "show_hidden_people": "Vis gemte personer", + "show_hidden_people": "Vis skjulte personer", "show_in_timeline": "Vis på tidslinje", "show_in_timeline_setting_description": "Vis billeder og videoer fra denne bruger på din tidslinje", "show_keyboard_shortcuts": "Vis tastaturgenveje", "show_metadata": "Vis metadata", - "show_or_hide_info": "Vis eller gem info", + "show_or_hide_info": "Vis eller skjul info", "show_password": "Vis adgangskode", "show_person_options": "Vis personindstillinger", "show_progress_bar": "Vis statuslinje", @@ -1216,7 +1225,7 @@ "sort_items": "Antal genstande", "sort_modified": "Ændret dato", "sort_oldest": "Ældste foto", - "sort_people_by_similarity": "Sorter folk efter lighed", + "sort_people_by_similarity": "Sorter efter personer der ligner hinanden", "sort_recent": "Seneste foto", "sort_title": "Titel", "source": "Kilde", @@ -1242,14 +1251,15 @@ "sunrise_on_the_beach": "Solopgang på stranden", "support": "Support", "support_and_feedback": "Support & Feedback", - "support_third_party_description": "Din Immich-installation blev pakket af en tredjepart. Problemer, du oplever, kan være forårsaget af denne pakke, så rejs venligst problemer med dem i første omgang ved at bruge nedenstående links.", + "support_third_party_description": "Din Immich-installation blev sammensat af en tredjepart. Problemer, du oplever, kan være forårsaget af denne udvikler, så rejs venligst problemer med dem i første omgang ved at bruge nedenstående links.", "swap_merge_direction": "Byt retning for sammenfletning", "sync": "Synkronisér", "tag": "Tag", - "tag_assets": "Tag aktiver", + "tag_assets": "Tag mediefiler", "tag_created": "Oprettet tag: {tag}", "tag_feature_description": "Gennemse billeder og videoer grupperet efter logiske tag-emner", "tag_not_found_question": "Kan du ikke finde et tag? Opret et nyt tag.", + "tag_people": "Tag personer", "tag_updated": "Opdateret tag: {tag}", "tagged_assets": "Tagget {count, plural, one {# aktiv} other {# aktiver}}", "tags": "Tags", @@ -1275,14 +1285,14 @@ "trash": "Papirkurv", "trash_all": "Smid alle ud", "trash_count": "Slet {count, number}", - "trash_delete_asset": "Papirkurv/slet aktiv", + "trash_delete_asset": "Flyt mediefil til Papirkurv", "trash_no_results_message": "Billeder og videoer markeret til sletning vil blive vist her.", "trashed_items_will_be_permanently_deleted_after": "Mediefiler i skraldespanden vil blive slettet permanent efter {days, plural, one {# dag} other {# dage}}.", "type": "Type", "unarchive": "Afakivér", "unarchived_count": "{count, plural, other {Uarkiveret #}}", "unfavorite": "Fjern favorit", - "unhide_person": "Hold op med at gemme person væk", + "unhide_person": "Stop med at skjule person", "unknown": "Ukendt", "unknown_country": "Ukendt land", "unknown_year": "Ukendt år", @@ -1303,7 +1313,7 @@ "up_next": "Næste", "updated_password": "Opdaterede adgangskode", "upload": "Upload", - "upload_concurrency": "Uploadsamtidighed", + "upload_concurrency": "Upload samtidighed", "upload_errors": "Upload afsluttet med {count, plural, one {# fejl} other {# fejl}}. Opdater siden for at se nye uploadaktiver.", "upload_progress": "Resterende {remaining, number} - Behandlet {processed, number}/{total, number}", "upload_skipped_duplicates": "Sprang over {count, plural, one {# duplet aktiv} other {# duplikerede aktiver}}", @@ -1321,7 +1331,7 @@ "user_purchase_settings_description": "Administrer dit køb", "user_role_set": "Indstil {user} som {role}", "user_usage_detail": "Detaljer om brugers forbrug", - "user_usage_stats": "Konto anvendelsesstatistik", + "user_usage_stats": "Kontoens anvendelsesstatistik", "user_usage_stats_description": "Vis konto anvendelsesstatistik", "username": "Brugernavn", "users": "Brugere", @@ -1343,6 +1353,7 @@ "view_all": "Se alle", "view_all_users": "Se alle brugere", "view_in_timeline": "Se på tidslinjen", + "view_link": "Vis Link", "view_links": "Vis links", "view_name": "Se", "view_next_asset": "Se næste medie", @@ -1359,4 +1370,4 @@ "yes": "Ja", "you_dont_have_any_shared_links": "Du har ikke nogen delte links", "zoom_image": "Zoom billede" -} +} \ No newline at end of file diff --git a/i18n/de.json b/i18n/de.json index e1efb48ba7..dd8d9ed9a6 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -41,6 +41,7 @@ "backup_settings": "Datensicherungs-Einstellungen", "backup_settings_description": "Datensicherungs-Einstellungen verwalten", "check_all": "Alle überprüfen", + "cleanup": "Aufräumen", "cleared_jobs": "Folgende Aufgaben zurückgesetzt: {job}", "config_set_by_file": "Ist derzeit in einer Konfigurationsdatei festgelegt", "confirm_delete_library": "Bist du sicher, dass du die Bibliothek {library} löschen willst?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Regelmäßiges Scannen der Bibliothek aktivieren", "library_settings": "Externe Bibliothek", "library_settings_description": "Einstellungen externer Bibliotheken verwalten", - "library_tasks_description": "Diese Aufgabe aktualisiert und überprüft die Bibliotheken", + "library_tasks_description": "Überprüfe externe Bibliotheken auf neue oder veränderte Medien", "library_watching_enable_description": "Überwache externe Bibliotheken auf Dateiänderungen", "library_watching_settings": "Bibliotheksüberwachung (EXPERIMENTELL)", "library_watching_settings_description": "Automatisch auf geänderte Dateien prüfen", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Semantische Bildsuche mittels CLIP-Einbettungen", "machine_learning_smart_search_enabled": "Intelligente Suche aktivieren", "machine_learning_smart_search_enabled_description": "Ist diese Option deaktiviert, werden die Bilder nicht für die intelligente Suche verwendet.", - "machine_learning_url_description": "Die URL des Servers für maschinelles Lernen. Wenn mehr als eine URL angegeben wird, wird jeder Server einzeln ausprobiert, bis einer erfolgreich antwortet, und zwar in der Reihenfolge vom ersten bis zum letzten.", + "machine_learning_url_description": "Die URL des Servers für maschinelles Lernen. Wenn mehr als eine URL angegeben wird, wird jeder Server einzeln ausprobiert, bis einer erfolgreich antwortet, und zwar in der Reihenfolge vom ersten bis zum letzten. Server die nicht antworten werden temporär ignoriert, bis sie wieder verfügbar sind.", "manage_concurrency": "Gleichzeitige Ausführungen verwalten", "manage_log_settings": "Log-Einstellungen verwalten", "map_dark_style": "Dunkler Stil", @@ -147,6 +148,8 @@ "map_settings": "Karte", "map_settings_description": "Karten- und GPS-Einstellungen verwalten", "map_style_description": "URL zu einem style.json Karten-Theme", + "memory_cleanup_job": "Erinnerungen aufräumen", + "memory_generate_job": "Erinnerungen Generierung", "metadata_extraction_job": "Metadaten extrahieren", "metadata_extraction_job_description": "Extrahieren von Metadaten, wie zum Beispiel GPS, Gesichtern und Auflösung aus jeder Datei", "metadata_faces_import_setting": "Import von Gesichtern aktivieren", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Aktiviert die Hash-Verifizierung. Deaktiviere diese Option nur, wenn du dir über die damit verbundenen Auswirkungen im Klaren bist", "storage_template_migration": "Migration von Speichervorlagen", "storage_template_migration_description": "Diese Aufgabe wendet die aktuelle {template} auf zuvor hochgeladene Dateien an", - "storage_template_migration_info": "Vorlagenänderungen gelten nur für neue Dateien. Um die Vorlage rückwirkend auf bereits hochgeladene Assets anzuwenden, führe den {job} aus.", + "storage_template_migration_info": "Die Vorlage wird alle Dateierweiterungen in Kleinbuchstaben umwandeln. Vorlagenänderungen gelten nur für neue Dateien. Um die Vorlage rückwirkend auf bereits hochgeladene Assets anzuwenden, führe den {job} aus.", "storage_template_migration_job": "Speichervorlagenmigrations-Aufgabe", "storage_template_more_details": "Weitere Details zu dieser Funktion findest du unter Speichervorlage und dessen Implikationen", "storage_template_onboarding_description": "Wenn aktiviert, sortiert diese Funktion Dateien automatisch basierend auf einer benutzerdefinierten Vorlage. Aufgrund von Stabilitätsproblemen ist die Funktion standardmäßig deaktiviert. Weitere Informationen findest du in der Dokumentation.", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Maximale B-Frames", "transcoding_max_b_frames_description": "Höhere Werte verbessern die Komprimierungseffizienz, verlangsamen aber die Kodierung. Ist möglicherweise nicht mit der Hardware-Beschleunigung älterer Geräte kompatibel. 0 deaktiviert die B-Frames, während -1 diesen Wert automatisch setzt.", "transcoding_max_bitrate": "Maximale Bitrate", - "transcoding_max_bitrate_description": "Die Festlegung einer maximalen Bitrate kann die Dateigrößen vorhersagbarer machen, ohne dass die Qualität darunter leidet. Bei 720p sind typische Werte 2600k für VP9 oder HEVC oder 4500k für H.264. Deaktiviert, wenn der Wert auf 0 gesetzt ist.", + "transcoding_max_bitrate_description": "Die Festlegung einer maximalen Bitrate kann die Dateigrößen vorhersagbarer machen, ohne dass die Qualität darunter leidet. Bei 720p sind typische Werte 2600 kbit/s für VP9 oder HEVC oder 4500 kbit/s für H.264. Deaktiviert, wenn der Wert auf 0 gesetzt ist.", "transcoding_max_keyframe_interval": "Maximales Keyframe-Intervall", "transcoding_max_keyframe_interval_description": "Legt den maximalen Frame-Abstand zwischen Keyframes fest. Niedrigere Werte verschlechtern die Komprimierungseffizienz, verbessern aber die Suchzeiten und können die Qualität in Szenen mit schnellen Bewegungen verbessern. Bei 0 wird dieser Wert automatisch eingestellt.", "transcoding_optimal_description": "Videos mit einer höheren Auflösung als der Zielauflösung oder in einem nicht akzeptierten Format", @@ -391,6 +394,7 @@ "allow_edits": "Bearbeiten erlauben", "allow_public_user_to_download": "Erlaube öffentlichen Benutzern, herunterzuladen", "allow_public_user_to_upload": "Erlaube öffentlichen Benutzern, hochzuladen", + "alt_text_qr_code": "QR-Code Bild", "anti_clockwise": "Gegen den Uhrzeigersinn", "api_key": "API-Schlüssel", "api_key_description": "Dieser Wert wird nur einmal angezeigt. Bitte kopiere ihn, bevor du das Fenster schließt.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Kommentare sind deaktiviert", "confirm": "Bestätigen", "confirm_admin_password": "Administrator Passwort bestätigen", + "confirm_delete_face": "Bist du sicher dass du das Gesicht von {name} aus der Datei entfernen willst?", "confirm_delete_shared_link": "Bist du sicher, dass du diesen geteilten Link löschen willst?", "confirm_keep_this_delete_others": "Alle anderen Dateien im Stapel bis auf diese werden gelöscht. Bist du sicher, dass du fortfahren möchten?", "confirm_password": "Passwort bestätigen", @@ -533,6 +538,7 @@ "delete_album": "Album löschen", "delete_api_key_prompt": "Bist du sicher, dass du diesen API-Schlüssel löschen willst?", "delete_duplicates_confirmation": "Bist du sicher, dass du diese Duplikate endgültig löschen willst?", + "delete_face": "Gesicht löschen", "delete_key": "Schlüssel löschen", "delete_library": "Bibliothek löschen", "delete_link": "Link löschen", @@ -600,6 +606,7 @@ "enabled": "Aktiviert", "end_date": "Enddatum", "error": "Fehler", + "error_delete_face": "Fehler beim Löschen des Gesichts", "error_loading_image": "Fehler beim Laden des Bildes", "error_title": "Fehler - Etwas ist schief gelaufen", "errors": { @@ -884,6 +891,7 @@ "month": "Monat", "more": "Mehr", "moved_to_trash": "In den Papierkorb verschoben", + "mute_memories": "Erinnerungen stumm schalten", "my_albums": "Meine Alben", "name": "Name", "name_or_nickname": "Name oder Nickname", @@ -979,6 +987,7 @@ "permanently_deleted_asset": "Endgültig gelöschtes Objekt", "permanently_deleted_assets_count": "{count, plural, one {# Datei} other {# Dateien}} endgültig gelöscht", "person": "Person", + "person_birthdate": "Geboren am {date}", "person_hidden": "{name}{hidden, select, true { (verborgen)} other {}}", "photo_shared_all_users": "Es sieht so aus, als hättest du deine Fotos mit allen Benutzern geteilt oder du hast keine Benutzer, mit denen du teilen kannst.", "photos": "Fotos", @@ -1076,6 +1085,8 @@ "removed_from_archive": "Aus dem Archiv entfernt", "removed_from_favorites": "Aus den Favoriten entfernt", "removed_from_favorites_count": "{count, plural, other {#}} aus den Favoriten entfernt", + "removed_memory": "Erinnerung entfernt", + "removed_photo_from_memory": "Foto aus Erinnerung entfernt", "removed_tagged_assets": "Tag von {count, plural, one {# Datei} other {# Dateien}} entfernt", "rename": "Umbenennen", "repair": "Reparatur", @@ -1084,6 +1095,7 @@ "repository": "Repository", "require_password": "Passwort erforderlich", "require_user_to_change_password_on_first_login": "Benutzer muss das Passwort beim ersten Login ändern", + "rescan": "Erneut scannen", "reset": "Zurücksetzen", "reset_password": "Passwort zurücksetzen", "reset_people_visibility": "Sichtbarkeit von Personen zurücksetzen", @@ -1127,6 +1139,7 @@ "search_options": "Suchoptionen", "search_people": "Suche nach Personen", "search_places": "Suche nach Orten", + "search_rating": "Suche nach Bewertung...", "search_settings": "Suche nach Einstellungen", "search_state": "Suche nach Bundesland / Provinz...", "search_tags": "Sache nach Tags...", @@ -1250,6 +1263,7 @@ "tag_created": "Tag erstellt: {tag}", "tag_feature_description": "Durchsuchen von Fotos und Videos, gruppiert nach logischen Tag-Themen", "tag_not_found_question": "Kein Tag zu finden? Erstelle einen neuen Tag.", + "tag_people": "Personen taggen", "tag_updated": "Tag aktualisiert: {tag}", "tagged_assets": "{count, plural, one {# Datei} other {# Dateien}} getagged", "tags": "Tags", @@ -1290,6 +1304,7 @@ "unlink_motion_video": "Verknüpfung zum Bewegungsvideo aufheben", "unlink_oauth": "OAuth entfernen", "unlinked_oauth_account": "OAuth-Konto entfernt", + "unmute_memories": "Stummschaltung für Erinnerungen aufheben", "unnamed_album": "Unbenanntes Album", "unnamed_album_delete_confirmation": "Bist du sicher, dass du dieses Album löschen willst?", "unnamed_share": "Unbenannte Freigabe", @@ -1343,6 +1358,7 @@ "view_all": "Alles anzeigen", "view_all_users": "Alle Nutzer anzeigen", "view_in_timeline": "In Zeitleiste anzeigen", + "view_link": "Link anzeigen", "view_links": "Links anzeigen", "view_name": "Ansicht", "view_next_asset": "Nächste Datei anzeigen", @@ -1359,4 +1375,4 @@ "yes": "Ja", "you_dont_have_any_shared_links": "Du hast keine geteilten Links", "zoom_image": "Bild vergrößern" -} +} \ No newline at end of file diff --git a/i18n/el.json b/i18n/el.json index 0371e76285..6c2bee5c79 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -131,7 +131,7 @@ "machine_learning_smart_search_description": "Αναζητήστε εικόνες σημασιολογικά χρησιμοποιώντας ενσωματώσεις CLIP", "machine_learning_smart_search_enabled": "Ενεργοποίηση έξυπνης αναζήτησης", "machine_learning_smart_search_enabled_description": "Αν απενεργοποιηθεί, οι εικόνες δεν θα κωδικοποιούνται για έξυπνη αναζήτηση.", - "machine_learning_url_description": "Η διεύθυνση URL του διακομιστή μηχανικής μάθησης. Αν παρέχονται περισσότερες από μία διευθύνσεις, τότε θα γίνει προσπάθεια σύνδεσης σε κάθε μια διαδοχικά από την πρώτη μέχρι την τελευταία, έως ότου κάποια να είναι επιτυχής.", + "machine_learning_url_description": "Η διεύθυνση URL του διακομιστή μηχανικής μάθησης. Αν δοθούν περισσότερες από μία διευθύνσεις URL, κάθε διακομιστής θα δοκιμάζεται διαδοχικά μέχρι να ανταποκριθεί ένας με επιτυχία, με τη σειρά από την πρώτη έως την τελευταία. Οι διακομιστές που δεν ανταποκρίνονται θα αγνοούνται προσωρινά μέχρι να επανέλθουν σε λειτουργία.", "manage_concurrency": "Διαχείριση ταυτόχρονη εκτέλεσης", "manage_log_settings": "Διαχείριση ρυθμίσεων αρχείου καταγραφής", "map_dark_style": "Σκούρο Θέμα", @@ -147,6 +147,8 @@ "map_settings": "Χάρτης", "map_settings_description": "Διαχείριση ρυθμίσεων χάρτη", "map_style_description": "URL προς αρχείο θέματος του χάρτη style.json", + "memory_cleanup_job": "Καθαρισμός μνήμης", + "memory_generate_job": "Δημιουργία μνήμης", "metadata_extraction_job": "Εξαγωγή μεταδεδομένων", "metadata_extraction_job_description": "Εξαγωγή μεταδεδομένων από κάθε αρχείο, όπως τοποθεσία, πρόσωπα και ανάλυση", "metadata_faces_import_setting": "Ενεργοποίηση εισαγωγής προσώπων", @@ -299,7 +301,7 @@ "transcoding_max_b_frames": "Μέγιστος αριθμός B-frames(Bidirectional Predictive Frames)", "transcoding_max_b_frames_description": "Οι υψηλότερες τιμές βελτιώνουν την αποδοτικότητα της συμπίεσης, αλλά επιβραδύνουν την κωδικοποίηση. Ενδέχεται να μην είναι συμβατές με την επιτάχυνση υλικού σε παλαιότερες συσκευές. Η τιμή 0 απενεργοποιεί τα B-frames, ενώ η -1, τη ρυθμίζει αυτόματα.", "transcoding_max_bitrate": "Μέγιστος ρυθμός μετάδοσης (bitrate)", - "transcoding_max_bitrate_description": "Η ρύθμιση ενός μέγιστου ρυθμού μετάδοσης(bitrate) μπορεί να κάνει το μέγεθος των αρχείων πιο προβλέψιμο, αλλά με ένα μικρό κόστος στην ποιότητα. Στην ανάλυση των 720p, οι τυπικές τιμές είναι 2600k για VP9 ή HEVC, ή 4500k για H.264. Απενεργοποιείται εάν οριστεί σε 0.", + "transcoding_max_bitrate_description": "Η ρύθμιση ενός μέγιστου ρυθμού μετάδοσης(bitrate) μπορεί να κάνει το μέγεθος των αρχείων πιο προβλέψιμο, αλλά με ένα μικρό κόστος στην ποιότητα. Στην ανάλυση των 720p, οι τυπικές τιμές είναι 2600 kbit/s για VP9 ή HEVC, ή 4500 kbit/s για H.264. Απενεργοποιείται εάν οριστεί σε 0.", "transcoding_max_keyframe_interval": "Μέγιστο χρονικό διάστημα μεταξύ των καρέ αναφοράς (keyframe)", "transcoding_max_keyframe_interval_description": "Ορίζει το μέγιστο διάστημα μεταξύ των καρέ αναφοράς. Χαμηλότερες τιμές μειώνουν την αποδοτικότητα συμπίεσης, αλλά βελτιώνουν τον χρόνο αναζήτησης και μπορεί να βελτιώσουν την ποιότητα σε σκηνές με γρήγορη κίνηση. Η τιμή 0 ρυθμίζει αυτό το διάστημα αυτόματα.", "transcoding_optimal_description": "Βίντεο με ανώτερη ανάλυση από την επιθυμητή ή σε μη αποδεκτή μορφή", @@ -481,6 +483,7 @@ "comments_are_disabled": "Τα σχόλια είναι απενεργοποιημένα", "confirm": "Επιβεβαίωση", "confirm_admin_password": "Επιβεβαίωση κωδικού Διαχειριστή", + "confirm_delete_face": "Είστε σίγουροι ότι θέλετε να διαγράψετε το πρόσωπο του/της {name} από το στοιχείο;", "confirm_delete_shared_link": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτόν τον κοινόχρηστο σύνδεσμο;", "confirm_keep_this_delete_others": "Όλα τα άλλα στοιχεία της στοίβας θα διαγραφούν, εκτός από αυτό το στοιχείο. Είστε σίγουροι ότι θέλετε να συνεχίσετε;", "confirm_password": "Επιβεβαίωση κωδικού", @@ -533,6 +536,7 @@ "delete_album": "Διαγραφή άλμπουμ", "delete_api_key_prompt": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό κλειδί API;", "delete_duplicates_confirmation": "Είστε σίγουροι ότι επιθυμείτε τη μόνιμη διαγραφή αυτών των διπλότυπων;", + "delete_face": "Διαγραφή προσώπου", "delete_key": "Διαγραφή κλειδιού", "delete_library": "Διαγραφή Βιβλιοθήκης", "delete_link": "Διαγραφή συνδέσμου", @@ -600,6 +604,7 @@ "enabled": "Ενεργοποιημένο", "end_date": "Τελική ημερομηνία", "error": "Σφάλμα", + "error_delete_face": "Σφάλμα διαγραφής προσώπου από το στοιχείο", "error_loading_image": "Σφάλμα κατά τη φόρτωση της εικόνας", "error_title": "Σφάλμα - Κάτι πήγε στραβά", "errors": { @@ -1076,6 +1081,8 @@ "removed_from_archive": "Αφαιρέθηκε/καν από το Αρχείο", "removed_from_favorites": "Αφαιρέθηκε από τα αγαπημένα", "removed_from_favorites_count": "Αφαιρέθηκαν {count, plural, other {#}} από τα αγαπημένα", + "removed_memory": "Διαγραμμένη μνήμη", + "removed_photo_from_memory": "Διαγραμμένη φωτογραφία από τη μνήμη", "removed_tagged_assets": "Αφαιρέθηκε η ετικέτα από {count, plural, one {# στοιχείο} other {# στοιχεία}}", "rename": "Μετονομασία", "repair": "Επισκευή", @@ -1127,6 +1134,7 @@ "search_options": "Επιλογές αναζήτησης", "search_people": "Αναζήτηση ατόμων", "search_places": "Αναζήτηση τοποθεσιών", + "search_rating": "Αναζήτηση κατά βαθμολογία...", "search_settings": "Ρυθμίσεις αναζήτησης", "search_state": "Αναζήτηση νομού...", "search_tags": "Αναζήτηση ετικετών...", @@ -1250,6 +1258,7 @@ "tag_created": "Δημιουργήθηκε ετικέτα: {tag}", "tag_feature_description": "Περιήγηση σε φωτογραφίες και βίντεο που είναι οργανωμένα σύμφωνα με λογικά θέματα ετικετών", "tag_not_found_question": "Δεν μπορείτε να βρείτε μια ετικέτα; Δημιουργήστε μια νέα ετικέτα.", + "tag_people": "Επισήμανση ατόμων", "tag_updated": "Ενημερώθηκε η ετικέτα: {tag}", "tagged_assets": "Ετικετοποιημένο/α {count, plural, one {# στοιχείο} other {# στοιχεία}}", "tags": "Ετικέτες", @@ -1359,4 +1368,4 @@ "yes": "Ναι", "you_dont_have_any_shared_links": "Δεν έχετε κοινόχρηστους συνδέσμους", "zoom_image": "Ζουμ Εικόνας" -} +} \ No newline at end of file diff --git a/i18n/en.json b/i18n/en.json index b2923c8942..e8726b3d20 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -240,7 +240,7 @@ "storage_template_hash_verification_enabled_description": "Enables hash verification, don't disable this unless you're certain of the implications", "storage_template_migration": "Storage template migration", "storage_template_migration_description": "Apply the current {template} to previously uploaded assets", - "storage_template_migration_info": "Template changes will only apply to new assets. To retroactively apply the template to previously uploaded assets, run the {job}.", + "storage_template_migration_info": "The storage template will convert all extensions to lowercase. Template changes will only apply to new assets. To retroactively apply the template to previously uploaded assets, run the {job}.", "storage_template_migration_job": "Storage Template Migration Job", "storage_template_more_details": "For more details about this feature, refer to the Storage Template and its implications", "storage_template_onboarding_description": "When enabled, this feature will auto-organize files based on a user-defined template. Due to stability issues the feature has been turned off by default. For more information, please see the documentation.", @@ -299,7 +299,7 @@ "transcoding_max_b_frames": "Maximum B-frames", "transcoding_max_b_frames_description": "Higher values improve compression efficiency, but slow down encoding. May not be compatible with hardware acceleration on older devices. 0 disables B-frames, while -1 sets this value automatically.", "transcoding_max_bitrate": "Maximum bitrate", - "transcoding_max_bitrate_description": "Setting a max bitrate can make file sizes more predictable at a minor cost to quality. At 720p, typical values are 2600k for VP9 or HEVC, or 4500k for H.264. Disabled if set to 0.", + "transcoding_max_bitrate_description": "Setting a max bitrate can make file sizes more predictable at a minor cost to quality. At 720p, typical values are 2600 kbit/s for VP9 or HEVC, or 4500 kbit/s for H.264. Disabled if set to 0.", "transcoding_max_keyframe_interval": "Maximum keyframe interval", "transcoding_max_keyframe_interval_description": "Sets the maximum frame distance between keyframes. Lower values worsen compression efficiency, but improve seek times and may improve quality in scenes with fast movement. 0 sets this value automatically.", "transcoding_optimal_description": "Videos higher than target resolution or not in an accepted format", @@ -987,6 +987,7 @@ "permanently_deleted_asset": "Permanently deleted asset", "permanently_deleted_assets_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}", "person": "Person", + "person_birthdate": "Born on {date}", "person_hidden": "{name}{hidden, select, true { (hidden)} other {}}", "photo_shared_all_users": "Looks like you shared your photos with all users or you don't have any user to share with.", "photos": "Photos", @@ -1149,6 +1150,7 @@ "second": "Second", "see_all_people": "See all people", "select_album_cover": "Select album cover", + "select": "Select", "select_all": "Select all", "select_all_duplicates": "Select all duplicates", "select_avatar_color": "Select avatar color", @@ -1374,4 +1376,4 @@ "yes": "Yes", "you_dont_have_any_shared_links": "You don't have any shared links", "zoom_image": "Zoom Image" -} +} \ No newline at end of file diff --git a/i18n/es.json b/i18n/es.json index c33f8fd1c6..4885a1d1b2 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -7,7 +7,7 @@ "actions": "Acciones", "active": "Activo", "activity": "Actividad", - "activity_changed": "La actividad está {enabled, select, true {activada} other {desactivada}}", + "activity_changed": "La actividad está {enabled, select, true {habilitada} other {deshabilitada}}", "add": "Agregar", "add_a_description": "Agregar descripción", "add_a_location": "Agregar ubicación", @@ -24,7 +24,7 @@ "add_to_album": "Incluir en álbum", "add_to_shared_album": "Incluir en álbum compartido", "add_url": "Añadir URL", - "added_to_archive": "Archivado", + "added_to_archive": "Agregado al Archivado", "added_to_favorites": "Agregado a favoritos", "added_to_favorites_count": "Agregado {count, number} a favoritos", "admin": { @@ -41,6 +41,7 @@ "backup_settings": "Ajustes de respaldo", "backup_settings_description": "Administrar configuración de respaldo de base de datos", "check_all": "Verificar todo", + "cleanup": "Limpieza", "cleared_jobs": "Trabajos borrados para: {job}", "config_set_by_file": "La configuración está definida por un archivo de configuración", "confirm_delete_library": "¿Estás seguro de que quieres eliminar la biblioteca {library}?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Activar el escaneo periódico de la biblioteca", "library_settings": "Biblioteca externa", "library_settings_description": "Administrar configuración biblioteca externa", - "library_tasks_description": "Realizar tareas de biblioteca", + "library_tasks_description": "Buscar archivos nuevos o modificados en bibliotecas externas", "library_watching_enable_description": "Vigilar las bibliotecas externas para detectar cambios en los archivos", "library_watching_settings": "Vigilancia de la biblioteca (EXPERIMENTAL)", "library_watching_settings_description": "Vigilar automaticamente en busca de archivos modificados", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Busque imágenes semánticamente utilizando incrustaciones CLIP (Contrastive Language-Image Pre-Training)", "machine_learning_smart_search_enabled": "Habilitar búsqueda inteligente", "machine_learning_smart_search_enabled_description": "Al desactivarlo las imágenes no se procesarán para usar la búsqueda inteligente.", - "machine_learning_url_description": "La URL del servidor de aprendizaje automático. Si se proporciona más de una URL se intentará acceder a cada servidor sucesivamente hasta que uno responda correctamente en el orden especificado.", + "machine_learning_url_description": "La URL del servidor de aprendizaje automático. Si se proporciona más de una URL se intentará acceder a cada servidor sucesivamente hasta que uno responda correctamente en el orden especificado. Los servidores que no respondan serán ignorados temporalmente hasta que vuelvan a estar en línea.", "manage_concurrency": "Ajustes de concurrencia", "manage_log_settings": "Administrar la configuración de los registros", "map_dark_style": "Estilo oscuro", @@ -147,6 +148,8 @@ "map_settings": "Mapa", "map_settings_description": "Administrar la configuración del mapa", "map_style_description": "Dirección URL a un tema de mapa (style.json)", + "memory_cleanup_job": "Limpieza de memoria", + "memory_generate_job": "Generación de memoria", "metadata_extraction_job": "Extracción de metadatos", "metadata_extraction_job_description": "Extraer información de metadatos de cada activo, como GPS, caras y resolución", "metadata_faces_import_setting": "Activar importación de caras", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Habilita la verificación de hash, no la desactive a menos que esté seguro de las implicaciones", "storage_template_migration": "Migración de plantillas de almacenamiento", "storage_template_migration_description": "Aplicar la {template} actual a los elementos subidos previamente", - "storage_template_migration_info": "Los cambios en las plantillas solo se aplican a los elementos nuevos. Para aplicarlos retroactivamente a los elementos subidos previamente ejecute la {job}.", + "storage_template_migration_info": "La plantilla de almacenamiento convertirá todas las extensiones a minúscula. Los cambios en las plantillas solo se aplican a los elementos nuevos. Para aplicarlos retroactivamente a los elementos subidos previamente ejecute la {job}.", "storage_template_migration_job": "Migración de la plantilla de almacenamiento", "storage_template_more_details": "Para obtener más detalles sobre esta función, consulte la Plantilla de almacenamiento y sus implicaciones", "storage_template_onboarding_description": "Cuando está habilitada, esta función organizará automáticamente los archivos según una plantilla definida por el usuario. Debido a problemas de estabilidad, la función se ha desactivado de forma predeterminada. Para obtener más información, consulte la documentación.", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Maximos B-frames", "transcoding_max_b_frames_description": "Los valores más altos mejoran la eficiencia de la compresión, pero ralentizan la codificación. Puede que no sea compatible con la aceleración de hardware en dispositivos más antiguos. 0 desactiva los fotogramas B, mientras que -1 establece este valor automáticamente.", "transcoding_max_bitrate": "Máxima tasa de bits", - "transcoding_max_bitrate_description": "Establecer una tasa de bits máxima puede hacer que los tamaños de archivos sean más predecibles con un costo menor para la calidad. A 720p, los valores típicos son 2600k para VP9 o HEVC, o 4500k para H.264. Deshabilitado si se establece en 0.", + "transcoding_max_bitrate_description": "Establecer una tasa de bits máxima puede hacer que los tamaños de archivos sean más predecibles con un costo menor para la calidad. A 720p, los valores típicos son 2600 kbit/s para VP9 o HEVC, o 4500 kbit/s para H.264. Deshabilitado si se establece en 0.", "transcoding_max_keyframe_interval": "Intervalo máximo de fotogramas clave", "transcoding_max_keyframe_interval_description": "Establece la distancia máxima de fotograma entre fotogramas clave. Los valores más bajos empeoran la eficiencia de la compresión, pero mejoran los tiempos de búsqueda y pueden mejorar la calidad en escenas con movimientos rápidos. 0 establece este valor automáticamente.", "transcoding_optimal_description": "Vídeos con una resolución superior a la fijada o que no están en un formato aceptado", @@ -391,6 +394,7 @@ "allow_edits": "Permitir edición", "allow_public_user_to_download": "Permitir descargar al usuario público", "allow_public_user_to_upload": "Permitir cargar al usuario publico", + "alt_text_qr_code": "Código QR", "anti_clockwise": "En sentido antihorario", "api_key": "Clave API", "api_key_description": "Este valor sólo se mostrará una vez. Asegúrese de copiarlo antes de cerrar la ventana.", @@ -438,7 +442,7 @@ "blurred_background": "Fondo borroso", "bugs_and_feature_requests": "Errores y solicitudes de funciones", "build": "Compilación", - "build_image": "Construir imagen", + "build_image": "Crear imagen", "bulk_delete_duplicates_confirmation": "¿Estás seguro de que deseas eliminar de forma masiva {count, plural, one {# elemento duplicado} other {# elementos duplicados}}? Esto mantendrá el activo más grande de cada grupo y eliminará permanentemente todos los demás duplicados. ¡Esta acción no se puede deshacer!", "bulk_keep_duplicates_confirmation": "¿Estas seguro de que desea mantener {count, plural, one {# duplicate asset} other {# duplicate assets}} archivos duplicados? Esto resolverá todos los grupos duplicados sin borrar nada.", "bulk_trash_duplicates_confirmation": "¿Estas seguro de que desea eliminar masivamente {count, plural, one {# duplicate asset} other {# duplicate assets}} archivos duplicados? Esto mantendrá el archivo más grande de cada grupo y eliminará todos los demás duplicados.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Los comentarios están deshabilitados", "confirm": "Confirmar", "confirm_admin_password": "Confirmar Contraseña de Administrador", + "confirm_delete_face": "¿Estás seguro que deseas eliminar la cara de {name} del archivo?", "confirm_delete_shared_link": "¿Estás seguro de que deseas eliminar este enlace compartido?", "confirm_keep_this_delete_others": "Todos los demás activos de la pila se eliminarán excepto este activo. ¿Está seguro de que quiere continuar?", "confirm_password": "Confirmar contraseña", @@ -533,6 +538,7 @@ "delete_album": "Eliminar álbum", "delete_api_key_prompt": "¿Está seguro de que desea eliminar esta clave API?", "delete_duplicates_confirmation": "¿Está seguro de que desea eliminar permanentemente estos duplicados?", + "delete_face": "Eliminar cara", "delete_key": "Eliminar clave", "delete_library": "Eliminar biblioteca", "delete_link": "Eliminar enlace", @@ -600,6 +606,7 @@ "enabled": "Habilitado", "end_date": "Fecha final", "error": "Error", + "error_delete_face": "Error al eliminar la cara del archivo", "error_loading_image": "Error al cargar la imagen", "error_title": "Error: algo salió mal", "errors": { @@ -884,6 +891,7 @@ "month": "Mes", "more": "Mas", "moved_to_trash": "Movido a la papelera", + "mute_memories": "Silenciar Recuerdos", "my_albums": "Mis albums", "name": "Nombre", "name_or_nickname": "Nombre o apodo", @@ -979,6 +987,7 @@ "permanently_deleted_asset": "Archivo eliminado permanentemente", "permanently_deleted_assets_count": "Eliminado permanentemente {count, plural, one {# elemento} other {# elementos}}", "person": "Persona", + "person_birthdate": "Nacido el {date}", "person_hidden": "{name}{hidden, select, true { (oculto)} other {}}", "photo_shared_all_users": "Parece que compartiste tus fotos con todos los usuarios o no tienes ningún usuario con quien compartirlas.", "photos": "Fotos", @@ -1076,6 +1085,8 @@ "removed_from_archive": "Eliminado del archivo", "removed_from_favorites": "Eliminado de favoritos", "removed_from_favorites_count": "{count, plural, other {Eliminados #}} de favoritos", + "removed_memory": "Memoria eliminada", + "removed_photo_from_memory": "Se ha eliminado la foto de la memoria", "removed_tagged_assets": "Etiqueta eliminada de {count, plural, one {# activo} other {# activos}}", "rename": "Renombrar", "repair": "Reparar", @@ -1084,6 +1095,7 @@ "repository": "Repositorio", "require_password": "Contraseña requerida", "require_user_to_change_password_on_first_login": "Requerir que el usuario cambie la contraseña en el primer inicio de sesión", + "rescan": "Volver a escanear", "reset": "Reiniciar", "reset_password": "Restablecer la contraseña", "reset_people_visibility": "Restablecer la visibilidad de las personas", @@ -1127,6 +1139,7 @@ "search_options": "Opciones de búsqueda", "search_people": "Buscar personas", "search_places": "Buscar lugar", + "search_rating": "Buscar por calificación...", "search_settings": "Ajustes de la búsqueda", "search_state": "Buscar región/estado...", "search_tags": "Buscando etiquetas...", @@ -1250,6 +1263,7 @@ "tag_created": "Etiqueta creada: {tag}", "tag_feature_description": "Explore fotos y videos agrupados por temas de etiquetas lógicas", "tag_not_found_question": "¿No encuentra una etiqueta? Crea una nueva etiqueta.", + "tag_people": "Etiquetar personas", "tag_updated": "Etiqueta actualizada: {tag}", "tagged_assets": "Etiquetado(s) {count, plural, one {# activo} other {# activos}}", "tags": "Etiquetas", @@ -1290,6 +1304,7 @@ "unlink_motion_video": "Desvincular vídeo en movimiento", "unlink_oauth": "Desvincular OAuth", "unlinked_oauth_account": "Cuenta OAuth desconectada", + "unmute_memories": "Habilitar sonido recuerdos", "unnamed_album": "Album sin nombre", "unnamed_album_delete_confirmation": "¿Seguro que quieres borrar este álbum?", "unnamed_share": "Compartido sin nombre", @@ -1343,6 +1358,7 @@ "view_all": "Ver todas", "view_all_users": "Mostrar todos los usuarios", "view_in_timeline": "Mostrar en la línea de tiempo", + "view_link": "Ver enlace", "view_links": "Mostrar enlaces", "view_name": "Ver", "view_next_asset": "Mostrar siguiente elemento", @@ -1359,4 +1375,4 @@ "yes": "Sí", "you_dont_have_any_shared_links": "No tienes ningún enlace compartido", "zoom_image": "Acercar Imagen" -} +} \ No newline at end of file diff --git a/i18n/et.json b/i18n/et.json index dea16174f9..0fa6ba0247 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -41,6 +41,7 @@ "backup_settings": "Varundamise seaded", "backup_settings_description": "Halda andmebaasi varundamise seadeid", "check_all": "Märgi kõik", + "cleanup": "Koristus", "cleared_jobs": "Tööted eemaldatud: {job}", "config_set_by_file": "Konfiguratsioon on määratud konfifaili abil", "confirm_delete_library": "Kas oled kindel, et soovid kustutada {library} kogu?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Luba kogu perioodiline skaneerimine", "library_settings": "Väline kogu", "library_settings_description": "Halda välise kogu seadeid", - "library_tasks_description": "Soorita kogu toiminguid", + "library_tasks_description": "Otsi välistest kogudest uusi ja muutunud üksuseid", "library_watching_enable_description": "Jälgi välises kogus failide muudatusi", "library_watching_settings": "Kogu jälgimine (EKSPERIMENTAALNE)", "library_watching_settings_description": "Jälgi automaatselt muutunud faile", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Otsi pilte semantiliselt CLIP-manuste abil", "machine_learning_smart_search_enabled": "Luba nutiotsing", "machine_learning_smart_search_enabled_description": "Kui keelatud, siis ei kodeerita pilte nutiotsingu jaoks.", - "machine_learning_url_description": "Masinõppe serveri URL. Kui ette on antud rohkem kui üks URL, proovitakse neid järjest ükshaaval, kuni üks edukalt vastab.", + "machine_learning_url_description": "Masinõppe serveri URL. Kui ette on antud rohkem kui üks URL, proovitakse neid järjest ükshaaval, kuni üks edukalt vastab. Servereid, mis ei vasta, ignoreeritakse ajutiselt, kuni ühendus taastub.", "manage_concurrency": "Halda samaaegsust", "manage_log_settings": "Halda logi seadeid", "map_dark_style": "Tume stiil", @@ -147,6 +148,8 @@ "map_settings": "Kaart", "map_settings_description": "Halda kaardi seadeid", "map_style_description": "Kaarditeema style.json URL", + "memory_cleanup_job": "Mälestuste korrastamine", + "memory_generate_job": "Mälestuste genereerimine", "metadata_extraction_job": "Metaandmete eraldamine", "metadata_extraction_job_description": "Eralda igast üksusest metaandmed, nagu GPS-koordinaadid, näod ja resolutsioon", "metadata_faces_import_setting": "Luba nägude import", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Lülitab sisse räsi kontrolli; ära lülita seda välja, kui sa ei ole tagajärgedest teadlik", "storage_template_migration": "Talletusmalli migreerimine", "storage_template_migration_description": "Rakenda praegune {template} varem üleslaaditud üksustele", - "storage_template_migration_info": "Malli muudatused rakenduvad ainult uutele üksustele. Et rakendada malli tagasiulatuvalt varem üleslaaditud üksustele, käivita {job}.", + "storage_template_migration_info": "Talletusmall teeb kõik faililaiendid väiketähtedeks. Malli muudatused rakenduvad ainult uutele üksustele. Et rakendada malli tagasiulatuvalt varem üleslaaditud üksustele, käivita {job}.", "storage_template_migration_job": "Talletusmallide migreerimise tööde", "storage_template_more_details": "Et selle funktsiooni kohta rohkem teada saada, loe talletusmallide ja nende tagajärgede kohta", "storage_template_onboarding_description": "Kui sisse lülitatud, võimaldab see faile kasutaja määratud malli alusel automaatselt organiseerida. Stabiilsusprobleemide tõttu on see funktsioon vaikimisi välja lülitatud. Rohkem infot leiad dokumentatsioonist.", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Maksimaalne B-kaadrite arv", "transcoding_max_b_frames_description": "Kõrgemad väärtused parandavad pakkimise efektiivsust, aga aeglustavad kodeerimist. See valik ei pruugi olla ühilduv riistvaralise kiirendusega vanematel seadmetel. 0 lülitab B-kaadrid välja, -1 määrab väärtuse automaatselt.", "transcoding_max_bitrate": "Maksimaalne bitisagedus", - "transcoding_max_bitrate_description": "Maksimaalse bitisageduse määramine teeb failisuurused ennustatavamaks, väikese kvaliteedikao hinnaga. 720p resolutsiooni puhul on tüüpilised väärtused 2600k (VP9 ja HEVC) või 4500k (H.264). Väärtus 0 eemaldab piirangu.", + "transcoding_max_bitrate_description": "Maksimaalse bitisageduse määramine teeb failisuurused ennustatavamaks, väikese kvaliteedikao hinnaga. 720p resolutsiooni puhul on tüüpilised väärtused 2600 kbit/s (VP9 ja HEVC) või 4500 kbit/s (H.264). Väärtus 0 eemaldab piirangu.", "transcoding_max_keyframe_interval": "Maksimaalne võtmekaadri intervall", "transcoding_max_keyframe_interval_description": "Määrab maksimaalse kauguse võtmekaadrite vahel. Madalamad väärtused vähendavad pakkimise efektiivsust, aga parandavad otsimiskiirust ning võivad tõsta kiire liikumisega stseenide kvaliteeti. 0 määrab väärtuse automaatselt.", "transcoding_optimal_description": "Kõrgema kui lubatud resolutsiooniga või mittelubatud formaadis videod", @@ -391,6 +394,7 @@ "allow_edits": "Luba muutmine", "allow_public_user_to_download": "Luba avalikul kasutajal alla laadida", "allow_public_user_to_upload": "Luba avalikul kasutajal üles laadida", + "alt_text_qr_code": "QR kood", "anti_clockwise": "Vastupäeva", "api_key": "API võti", "api_key_description": "Seda väärtust kuvatakse ainult üks kord. Kopeeri see enne akna sulgemist.", @@ -431,7 +435,7 @@ "assets_were_part_of_album_count": "{count, plural, one {Üksus oli} other {Üksused olid}} juba osa albumist", "authorized_devices": "Autoriseeritud seadmed", "back": "Tagasi", - "back_close_deselect": "Tagasi, sulge, või tühista valik", + "back_close_deselect": "Tagasi, sulge või tühista valik", "backward": "Tagasi", "birthdate_saved": "Sünnikuupäev salvestatud", "birthdate_set_description": "Sünnikuupäeva kasutatakse isiku vanuse arvutamiseks foto tegemise hetkel.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Kommentaarid on keelatud", "confirm": "Kinnita", "confirm_admin_password": "Kinnita administraatori parool", + "confirm_delete_face": "Kas oled kindel, et soovid isiku {name} näo üksuselt kustutada?", "confirm_delete_shared_link": "Kas oled kindel, et soovid selle jagatud lingi kustutada?", "confirm_keep_this_delete_others": "Kõik muud üksused selles virnas kustutatakse. Kas oled kindel, et soovid jätkata?", "confirm_password": "Kinnita parool", @@ -533,6 +538,7 @@ "delete_album": "Kustuta album", "delete_api_key_prompt": "Kas oled kindel, et soovid selle API võtme kustutada?", "delete_duplicates_confirmation": "Kas oled kindel, et soovid need duplikaadid jäädavalt kustutada?", + "delete_face": "Kustuta nägu", "delete_key": "Kustuta võti", "delete_library": "Kustuta kogu", "delete_link": "Kustuta link", @@ -600,6 +606,7 @@ "enabled": "Lubatud", "end_date": "Lõppkuupäev", "error": "Viga", + "error_delete_face": "Viga näo kustutamisel", "error_loading_image": "Viga pildi laadimisel", "error_title": "Viga - midagi läks valesti", "errors": { @@ -718,6 +725,7 @@ "unable_to_submit_job": "Tööte edastamine ebaõnnestus", "unable_to_trash_asset": "Üksuse prügikasti liigutamine ebaõnnestus", "unable_to_unlink_account": "Konto lahtiühendamine ebaõnnestus", + "unable_to_unlink_motion_video": "Liikuva video linkimise tühistamine ebaõnnestus", "unable_to_update_album_cover": "Albumi kaanepildi muutmine ebaõnnestus", "unable_to_update_album_info": "Albumi info muutmine ebaõnnestus", "unable_to_update_library": "Kogu uuendamine ebaõnnestus", @@ -734,6 +742,7 @@ "expired": "Aegunud", "expires_date": "Aegub {date}", "explore": "Avasta", + "explorer": "Brauser", "export": "Ekspordi", "export_as_json": "Ekspordi JSON-formaati", "extension": "Laiend", @@ -742,6 +751,7 @@ "face_unassigned": "Seostamata", "failed_to_load_assets": "Üksuste laadimine ebaõnnestus", "favorite": "Lemmik", + "favorite_or_unfavorite_photo": "Lisa foto lemmikutesse või eemalda lemmikutest", "favorites": "Lemmikud", "feature_photo_updated": "Esiletõstetud foto muudetud", "features": "Funktsioonid", @@ -799,6 +809,7 @@ "include_shared_albums": "Kaasa jagatud albumid", "include_shared_partner_assets": "Kaasa partneri jagatud üksused", "individual_share": "Jagatud üksus", + "individual_shares": "Jagatud üksused", "info": "Info", "interval": { "day_at_onepm": "Iga päev kell 13", @@ -828,6 +839,7 @@ "library_options": "Kogu seaded", "light": "Hele", "like_deleted": "Meeldimine kustutatud", + "link_motion_video": "Lingi liikuv video", "link_options": "Lingi valikud", "link_to_oauth": "Ühenda OAuth", "linked_oauth_account": "OAuth konto ühendatud", @@ -879,6 +891,7 @@ "month": "Kuu", "more": "Rohkem", "moved_to_trash": "Liigutatud prügikasti", + "mute_memories": "Vaigista mälestused", "my_albums": "Minu albumid", "name": "Nimi", "name_or_nickname": "Nimi või hüüdnimi", @@ -974,6 +987,7 @@ "permanently_deleted_asset": "Üksus jäädavalt kustutatud", "permanently_deleted_assets_count": "{count, plural, one {# üksus} other {# üksust}} jäädavalt kustutatud", "person": "Isik", + "person_birthdate": "Sündinud {date}", "person_hidden": "{name}{hidden, select, true { (peidetud)} other {}}", "photo_shared_all_users": "Paistab, et oled oma fotosid kõigi kasutajatega jaganud, või pole ühtegi kasutajat, kellega jagada.", "photos": "Fotod", @@ -1071,6 +1085,8 @@ "removed_from_archive": "Arhiivist eemaldatud", "removed_from_favorites": "Lemmikutest eemaldatud", "removed_from_favorites_count": "{count, plural, other {# eemaldatud}} lemmikutest", + "removed_memory": "Mäletus eemaldatud", + "removed_photo_from_memory": "Foto mälestustest eemaldatud", "removed_tagged_assets": "Silt eemaldatud {count, plural, one {# üksuselt} other {# üksuselt}}", "rename": "Nimeta ümber", "repair": "Parandus", @@ -1079,6 +1095,7 @@ "repository": "Koodihoidla", "require_password": "Nõua parooli", "require_user_to_change_password_on_first_login": "Nõua kasutajalt esmakordsel sisenemisel parooli muutmist", + "rescan": "Skaneeri uuesti", "reset": "Lähtesta", "reset_password": "Lähtesta parool", "reset_people_visibility": "Lähtesta isikute nähtavus", @@ -1122,6 +1139,7 @@ "search_options": "Otsingu valikud", "search_people": "Otsi inimesi", "search_places": "Otsi kohti", + "search_rating": "Otsi hinnangu järgi...", "search_settings": "Otsi seadeid", "search_state": "Otsi osariiki...", "search_tags": "Otsi silte...", @@ -1225,7 +1243,7 @@ "start_date": "Alguskuupäev", "state": "Osariik", "status": "Staatus", - "stop_motion_photo": "Peata liikuv pilt", + "stop_motion_photo": "Peata liikuv foto", "stop_photo_sharing": "Lõpeta oma fotode jagamine?", "stop_photo_sharing_description": "{partner} ei pääse rohkem su fotodele ligi.", "stop_sharing_photos_with_user": "Lõpeta oma fotode selle kasutajaga jagamine", @@ -1245,6 +1263,7 @@ "tag_created": "Lisatud silt: {tag}", "tag_feature_description": "Fotode ja videote lehitsemine siltide kaupa grupeeritult", "tag_not_found_question": "Ei leia silti? Lisa uus silt.", + "tag_people": "Sildista inimesi", "tag_updated": "Muudetud silt: {tag}", "tagged_assets": "{count, plural, one {# üksus} other {# üksust}} sildistatud", "tags": "Sildid", @@ -1282,10 +1301,13 @@ "unknown_country": "Tundmatu riik", "unknown_year": "Teadmata aasta", "unlimited": "Piiramatu", + "unlink_motion_video": "Tühista liikuva video linkimine", "unlink_oauth": "Eemalda OAuth ühendus", "unlinked_oauth_account": "OAuth ühendus eemaldatud", + "unmute_memories": "Tühista mälestuste vaigistamine", "unnamed_album": "Nimetu album", "unnamed_album_delete_confirmation": "Kas oled kindel, et soovid selle albumi kustutada?", + "unnamed_share": "Nimetu jagamine", "unsaved_change": "Salvestamata muudatus", "unselect_all": "Ära vali ühtegi", "unselect_all_duplicates": "Ära vali duplikaate", @@ -1336,6 +1358,7 @@ "view_all": "Vaata kõiki", "view_all_users": "Vaata kõiki kasutajaid", "view_in_timeline": "Vaata ajajoonel", + "view_link": "Vaata linki", "view_links": "Vaata linke", "view_name": "Vaade", "view_next_asset": "Vaata järgmist üksust", @@ -1352,4 +1375,4 @@ "yes": "Jah", "you_dont_have_any_shared_links": "Sul pole ühtegi jagatud linki", "zoom_image": "Suumi pilti" -} +} \ No newline at end of file diff --git a/i18n/fa.json b/i18n/fa.json index ef058deb65..ca9b75c5e4 100644 --- a/i18n/fa.json +++ b/i18n/fa.json @@ -254,7 +254,7 @@ "transcoding_max_b_frames": "بیشترین B-frames", "transcoding_max_b_frames_description": "مقادیر بالاتر کارایی فشرده سازی را بهبود می‌بخشند، اما کدگذاری را کند می‌کنند. ممکن است با شتاب دهی سخت‌افزاری در دستگاه‌های قدیمی سازگار نباشد. مقدار( 0 ) B-frames را غیرفعال می‌کند، در حالی که مقدار ( 1 ) این مقدار را به صورت خودکار تنظیم می‌کند.", "transcoding_max_bitrate": "بیشترین بیت ریت", - "transcoding_max_bitrate_description": "تنظیم حداکثر بیت‌ریت می‌تواند اندازه فایل‌ها را در حدی قابل پیش‌بینی‌تر کند، هرچند که هزینه کمی برای کیفیت دارد. در وضوح 720p، مقادیر معمول 2600k برای VP9 یا HEVC و 4500k برای H.264 است. اگر به 0 تنظیم شود، غیرفعال می‌شود.", + "transcoding_max_bitrate_description": "تنظیم حداکثر بیت‌ریت می‌تواند اندازه فایل‌ها را در حدی قابل پیش‌بینی‌تر کند، هرچند که هزینه کمی برای کیفیت دارد. در وضوح 720p، مقادیر معمول 2600 kbit/s برای VP9 یا HEVC و 4500 kbit/s برای H.264 است. اگر به 0 تنظیم شود، غیرفعال می‌شود.", "transcoding_max_keyframe_interval": "حداکثر فاصله کلید فریم", "transcoding_max_keyframe_interval_description": "حداکثر فاصله فریم بین کلیدفریم‌ها را تنظیم می‌کند. مقادیر پایین‌تر کارایی فشرده‌سازی را کاهش می‌دهند، اما زمان جستجو را بهبود می‌بخشند و ممکن است کیفیت را در صحنه‌های با حرکت سریع بهبود دهند. مقدار 0 این مقدار را به‌طور خودکار تنظیم می‌کند.", "transcoding_optimal_description": "ویدیوهایی که از رزولوشن هدف بالاتر هستند یا در قالب پذیرفته شده نیستند", @@ -926,4 +926,4 @@ "yes": "بله", "you_dont_have_any_shared_links": "", "zoom_image": "بزرگنمایی تصویر" -} +} \ No newline at end of file diff --git a/i18n/fi.json b/i18n/fi.json index 843e42f887..ea36a994b4 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -299,7 +299,7 @@ "transcoding_max_b_frames": "B-kehysten enimmäismäärä", "transcoding_max_b_frames_description": "Korkeampi arvo parantaa pakkausta, mutta hidastaa enkoodausta. Ei välttämättä ole yhteensopiva vanhempien laitteiden kanssa. 0 poistaa B-kehykset käytöstä, -1 määrittää arvon automaattisesti.", "transcoding_max_bitrate": "Suurin bittinopeus", - "transcoding_max_bitrate_description": "Suurimman sallitun bittinopeuden asettaminen tekee tiedostojen koosta ennustettavampaa vaikka laatu voi hieman heiketä. 720p videossa tyypilliset arvot ovat 2600k VP9:lle ja HEVC:lle, tai 4500k H.254:lle. Jos 0, ei käytössä.", + "transcoding_max_bitrate_description": "Suurimman sallitun bittinopeuden asettaminen tekee tiedostojen koosta ennustettavampaa vaikka laatu voi hieman heiketä. 720p videossa tyypilliset arvot ovat 2600 kbit/s VP9:lle ja HEVC:lle, tai 4500 kbit/s H.254:lle. Jos 0, ei käytössä.", "transcoding_max_keyframe_interval": "Suurin avainkehysten väli", "transcoding_max_keyframe_interval_description": "Asettaa avainkehysten välin maksimiarvon. Alempi arvo huonontaa pakkauksen tehoa, mutta parantaa hakuaikoja ja voi parantaa laatua nopealiikkeisissä kohtauksissa. 0 asettaa arvon automaattisesti.", "transcoding_optimal_description": "Videot, joiden resoluutio on korkeampi kuin kohteen, tai ei hyväksytyssä formaatissa", @@ -766,8 +766,10 @@ "go_to_folder": "Mene kansioon", "go_to_search": "Siirry hakuun", "group_albums_by": "Ryhmitä albumi...", + "group_country": "Ryhmitä maan mukaan", "group_no": "Ei ryhmitystä", "group_owner": "Ryhmitä omistajan mukaan", + "group_places_by": "Ryhmitä paikat...", "group_year": "Ryhmitä vuoden mukaan", "has_quota": "On kiintiö", "hi_user": "Hei {name} ({email})", @@ -800,6 +802,7 @@ "include_shared_albums": "Sisällytä jaetut albumit", "include_shared_partner_assets": "Sisällytä jaetut kumppanikohteet", "individual_share": "Yksittäinen jako", + "individual_shares": "Yksittäiset jaot", "info": "Lisätietoja", "interval": { "day_at_onepm": "Joka päivä klo 13:00", @@ -1107,6 +1110,7 @@ "search": "Haku", "search_albums": "Etsi albumeita", "search_by_context": "Etsi kontekstin perusteella", + "search_by_description": "Etsi kuvauksen perusteella", "search_by_filename": "Hae tiedostonimen tai -päätteen mukaan", "search_by_filename_example": "esim. IMG_1234.JPG tai PNG", "search_camera_make": "Etsi kameramerkkiä...", @@ -1348,4 +1352,4 @@ "yes": "Kyllä", "you_dont_have_any_shared_links": "Sinulla ei ole jaettuja linkkejä", "zoom_image": "Zoomaa kuvaa" -} +} \ No newline at end of file diff --git a/i18n/fr.json b/i18n/fr.json index dfb095046f..3f479ab28f 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -41,6 +41,7 @@ "backup_settings": "Paramètres de la sauvegarde", "backup_settings_description": "Gérer les paramètres de la sauvegarde", "check_all": "Tout cocher", + "cleanup": "Nettoyage", "cleared_jobs": "Tâches supprimées pour : {job}", "config_set_by_file": "La configuration est actuellement définie par un fichier de configuration", "confirm_delete_library": "Êtes-vous sûr de vouloir supprimer la bibliothèque {library} ?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Activer l'analyse périodique de la bibliothèque", "library_settings": "Bibliothèque externe", "library_settings_description": "Gestion des paramètres des bibliothèques externes", - "library_tasks_description": "Exécution d'actions sur la bibliothèque", + "library_tasks_description": "Scanner les bibliothèques externes pour les nouveaux et/ou les éléments modifiés", "library_watching_enable_description": "Surveiller les modifications de fichiers dans les bibliothèques externes", "library_watching_settings": "Surveillance de bibliothèque (EXPÉRIMENTAL)", "library_watching_settings_description": "Surveiller automatiquement les fichiers modifiés", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Rechercher des images de manière sémantique en utilisant les intégrations CLIP", "machine_learning_smart_search_enabled": "Activer la recherche intelligente", "machine_learning_smart_search_enabled_description": "Si cette option est désactivée, les images ne seront pas encodées pour la recherche intelligente.", - "machine_learning_url_description": "L’URL du serveur d'apprentissage automatique. Si plusieurs URL sont fournies, chaque serveur sera essayé un par un jusqu’à ce que l’un d’eux réponde avec succès, dans l’ordre de la première à la dernière.", + "machine_learning_url_description": "L’URL du serveur d'apprentissage automatique. Si plusieurs URL sont fournies, chaque serveur sera essayé un par un jusqu’à ce que l’un d’eux réponde avec succès, dans l’ordre de la première à la dernière. Les serveurs ne répondant pas seront temporairement ignorés jusqu'à ce qu'ils soient de nouveau opérationnels.", "manage_concurrency": "Gérer du multitâche", "manage_log_settings": "Gérer les paramètres de journalisation", "map_dark_style": "Thème sombre", @@ -147,6 +148,8 @@ "map_settings": "Carte", "map_settings_description": "Gérer les paramètres de la carte", "map_style_description": "URL vers un thème de carte au format style.json", + "memory_cleanup_job": "Nettoyage des souvenirs", + "memory_generate_job": "Génération des souvenirs", "metadata_extraction_job": "Extraction des métadonnées", "metadata_extraction_job_description": "Extraction des informations des métadonnées de chaque média, telles que la position GPS, les visages et la résolution", "metadata_faces_import_setting": "Active l'importation des visages", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Active la vérification du hachage, ne désactivez pas cette option à moins d'être sûr de ce que vous faites", "storage_template_migration": "Migration du modèle de stockage", "storage_template_migration_description": "Appliquer le modèle courant {template} aux médias précédemment envoyés", - "storage_template_migration_info": "Les changements de modèle ne s'appliqueront qu'aux nouveaux médias. Pour appliquer rétroactivement le modèle aux médias précédemment envoyés, exécutez la tâche {job}.", + "storage_template_migration_info": "L'enregistrement des modèles convertit toutes les extensions en minuscule. Les changements de modèle ne s'appliqueront qu'aux nouveaux médias. Pour appliquer rétroactivement le modèle aux médias précédemment envoyés, exécutez la tâche {job}.", "storage_template_migration_job": "Tâche de migration du modèle de stockage", "storage_template_more_details": "Pour plus de détails sur cette fonctionnalité, reportez-vous au Modèle de stockage et à ses implications", "storage_template_onboarding_description": "Lorsqu'elle est activée, cette fonctionnalité réorganise les fichiers basés sur un modèle défini par l'utilisateur. En raison de problèmes de stabilité, la fonction a été désactivée par défaut. Pour plus d'informations, veuillez consulter la documentation.", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Nombre maximum de trames B", "transcoding_max_b_frames_description": "Des valeurs plus élevées améliorent l'efficacité de la compression, mais ralentissent l'encodage. Elles peuvent ne pas être compatibles avec l'accélération matérielle sur les anciens appareils. Une valeur de 0 désactive les trames B, tandis qu'une valeur de -1 définit automatiquement ce paramètre.", "transcoding_max_bitrate": "Débit binaire maximal", - "transcoding_max_bitrate_description": "Définir un débit binaire maximal peut résulter en des fichiers de taille plus prédictible, au prix d'une légère perte en qualité. En 720p, les valeurs sont 2600k pour du VP9 ou du HEVC ou 4500k pour du H.264. Désactivé si le débit binaire est à 0.", + "transcoding_max_bitrate_description": "Définir un débit binaire maximal peut résulter en des fichiers de taille plus prédictible, au prix d'une légère perte en qualité. En 720p, les valeurs sont 2600 kbit/s pour du VP9 ou du HEVC ou 4500 kbit/s pour du H.264. Désactivé si le débit binaire est à 0.", "transcoding_max_keyframe_interval": "Intervalle maximal entre les images clés", "transcoding_max_keyframe_interval_description": "Définit la distance maximale de trames entre les images clés. Les valeurs plus basses diminuent l'efficacité de la compression, mais améliorent les temps de recherche et peuvent améliorer la qualité dans les scènes avec des mouvements rapides. Une valeur de 0 définit automatiquement ce paramètre.", "transcoding_optimal_description": "Les vidéos dont la résolution est supérieure à celle attendue ou celles qui ne sont pas dans un format accepté", @@ -391,6 +394,7 @@ "allow_edits": "Autoriser les modifications", "allow_public_user_to_download": "Permettre aux utilisateurs non connectés de télécharger", "allow_public_user_to_upload": "Permettre l'envoi aux utilisateurs non connectés", + "alt_text_qr_code": "Image du code QR", "anti_clockwise": "Sens anti-horaire", "api_key": "Clé API", "api_key_description": "Cette valeur ne sera affichée qu'une seule fois. Assurez-vous de la copier avant de fermer la fenêtre.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Les commentaires sont désactivés", "confirm": "Confirmer", "confirm_admin_password": "Confirmer le mot de passe Admin", + "confirm_delete_face": "Êtes-vous sûr de vouloir supprimer le visage de {name} du média ?", "confirm_delete_shared_link": "Voulez-vous vraiment supprimer ce lien partagé ?", "confirm_keep_this_delete_others": "Tous les autres médias dans la pile seront supprimés sauf celui-ci. Êtes-vous sûr de vouloir continuer ?", "confirm_password": "Confirmer le mot de passe", @@ -533,6 +538,7 @@ "delete_album": "Supprimer l'album", "delete_api_key_prompt": "Voulez-vous vraiment supprimer cette clé API ?", "delete_duplicates_confirmation": "Êtes-vous certain de vouloir supprimer définitivement ces doublons ?", + "delete_face": "Supprimer le visage", "delete_key": "Supprimer la clé", "delete_library": "Supprimer la bibliothèque", "delete_link": "Supprimer le lien", @@ -600,6 +606,7 @@ "enabled": "Activé", "end_date": "Date de fin", "error": "Erreur", + "error_delete_face": "Erreur lors de la suppression du visage pour le média", "error_loading_image": "Erreur de chargement de l'image", "error_title": "Erreur - Quelque chose s'est mal passé", "errors": { @@ -884,6 +891,7 @@ "month": "Mois", "more": "Plus", "moved_to_trash": "Déplacé dans la corbeille", + "mute_memories": "Mettre en sourdine les souvenirs", "my_albums": "Mes albums", "name": "Nom", "name_or_nickname": "Nom ou surnom", @@ -1076,6 +1084,8 @@ "removed_from_archive": "Supprimé de l'archive", "removed_from_favorites": "Supprimé des favoris", "removed_from_favorites_count": "{count, plural, one {# supprimé} other {# supprimés}} des favoris", + "removed_memory": "Souvenir supprimé", + "removed_photo_from_memory": "Photo supprimée du souvenir", "removed_tagged_assets": "Tag supprimé de {count, plural, one {# média} other {# médias}}", "rename": "Renommer", "repair": "Réparer", @@ -1084,6 +1094,7 @@ "repository": "Dépôt", "require_password": "Demander le mot de passe", "require_user_to_change_password_on_first_login": "Demander à l'utilisateur de changer son mot de passe lors de sa première connexion", + "rescan": "Rescanner", "reset": "Réinitialiser", "reset_password": "Réinitialiser le mot de passe", "reset_people_visibility": "Réinitialiser la visibilité des personnes", @@ -1127,6 +1138,7 @@ "search_options": "Rechercher une option", "search_people": "Rechercher une personne", "search_places": "Rechercher un lieu", + "search_rating": "Chercher par évaluation...", "search_settings": "Paramètres de recherche", "search_state": "Rechercher par état/région...", "search_tags": "Recherche d'étiquettes...", @@ -1250,6 +1262,7 @@ "tag_created": "Étiquette créée : {tag}", "tag_feature_description": "Parcourir les photos et vidéos groupées par thèmes logiques", "tag_not_found_question": "Vous ne trouvez pas une étiquette ? Créer une nouvelle étiquette.", + "tag_people": "Étiqueter les personnes", "tag_updated": "Étiquette mise à jour : {tag}", "tagged_assets": "Étiquette ajoutée à {count, plural, one {# média} other {# médias}}", "tags": "Étiquettes", @@ -1290,6 +1303,7 @@ "unlink_motion_video": "Détacher la photo animée", "unlink_oauth": "Déconnecter OAuth", "unlinked_oauth_account": "Compte OAuth non connecté", + "unmute_memories": "Réactiver les souvenirs", "unnamed_album": "Album sans nom", "unnamed_album_delete_confirmation": "Êtes-vous sûr de vouloir supprimer cet album ?", "unnamed_share": "Partage sans nom", @@ -1343,6 +1357,7 @@ "view_all": "Voir tout", "view_all_users": "Voir tous les utilisateurs", "view_in_timeline": "Voir dans la vue chronologique", + "view_link": "Voir le lien", "view_links": "Voir les liens", "view_name": "Vue", "view_next_asset": "Voir le média suivant", @@ -1359,4 +1374,4 @@ "yes": "Oui", "you_dont_have_any_shared_links": "Vous n'avez aucun lien partagé", "zoom_image": "Zoomer" -} +} \ No newline at end of file diff --git a/i18n/he.json b/i18n/he.json index 72a214079b..67321af4cd 100644 --- a/i18n/he.json +++ b/i18n/he.json @@ -41,6 +41,7 @@ "backup_settings": "הגדרות גיבוי", "backup_settings_description": "ניהול הגדרות גיבוי מסד נתונים", "check_all": "סימון הכל", + "cleanup": "ניקוי", "cleared_jobs": "נוקו משימות עבור: {job}", "config_set_by_file": "התצורה מוגדרת כעת על ידי קובץ תצורה", "confirm_delete_library": "האם את/ה בטוח/ה שברצונך למחוק את הספרייה {library}?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "אפשר סריקת ספרייה תקופתית", "library_settings": "ספרייה חיצונית", "library_settings_description": "ניהול הגדרות ספרייה חיצונית", - "library_tasks_description": "ביצוע משימות ספרייה", + "library_tasks_description": "סרוק ספריות חיצוניות עבור נכסים חדשים ו/או שהשתנו", "library_watching_enable_description": "עקוב אחר שינויי קבצים בספריות חיצוניות", "library_watching_settings": "צפיית ספרייה (ניסיוני)", "library_watching_settings_description": "עקוב אוטומטית אחר שינויי קבצים", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "חיפוש תמונות באופן סמנטי באמצעות הטמעות של CLIP", "machine_learning_smart_search_enabled": "אפשר חיפוש חכם", "machine_learning_smart_search_enabled_description": "אם מושבת, תמונות לא יקודדו לחיפוש חכם.", - "machine_learning_url_description": "כתובת האתר של שרת למידת המכונה. אם ניתנת יותר מכתובת אחת, כל שרת ינסה בתורו עד אשר יענה בחיוב, בסדר התחלתי.", + "machine_learning_url_description": "כתובת ה-URL של שרת למידת המכונה. אם ניתנת יותר מכתובת URL אחת, כל שרת ינוסה ניסיון אחד בכל פעם עד שאחד מהם יגיב בהצלחה, לפי הסדר מהראשון עד האחרון. שרתים שלא מגיבים יוזנחו זמנית עד שיחזרו להיות מקוונים.", "manage_concurrency": "ניהול בו-זמניות", "manage_log_settings": "ניהול הגדרות רישום ביומן", "map_dark_style": "עיצוב כהה", @@ -147,6 +148,8 @@ "map_settings": "מפה", "map_settings_description": "ניהול הגדרות מפה", "map_style_description": "כתובת אתר לערכת נושא של מפה style.json", + "memory_cleanup_job": "ניקוי זיכרון", + "memory_generate_job": "יצירת זיכרון", "metadata_extraction_job": "חלץ מטא-נתונים", "metadata_extraction_job_description": "חלץ מידע מטא-נתונים מכל נכס, כגון GPS, פנים ורזולוציה", "metadata_faces_import_setting": "אפשר יבוא פנים", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "מאפשר אימות גיבוב, אל תשבית/י זאת אלא אם כן את/ה בטוח/ה בהשלכות", "storage_template_migration": "העברת תבנית אחסון", "storage_template_migration_description": "החל את ה{template} הנוכחית על נכסים שהועלו בעבר", - "storage_template_migration_info": "שינויים בתבנית יחולו רק על נכסים חדשים. כדי להחיל באופן רטרואקטיבי את התבנית על נכסים שהועלו בעבר, הפעל את {job}.", + "storage_template_migration_info": "תבנית האחסון תמיר את כל ההרחבות לאותיות קטנות. שינויים בתבנית יחולו רק על נכסים חדשים. כדי להחיל באופן רטרואקטיבי את התבנית על נכסים שהועלו בעבר, הפעל את {job}.", "storage_template_migration_job": "משימת העברת תבנית אחסון", "storage_template_more_details": "לפרטים נוספים אודות תכונה זו, עיין בתבנית האחסון ובהשלכותיה", "storage_template_onboarding_description": "כאשר מופעלת, תכונה זו תארגן אוטומטית קבצים בהתבסס על תבנית שהמשתמש הגדיר. עקב בעיות יציבות התכונה כבויה כברירת מחדל. למידע נוסף, נא לראות את התיעוד.", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "B-פריימים מרביים", "transcoding_max_b_frames_description": "ערכים גבוהים יותר משפרים את יעילות הדחיסה, אך מאטים את הקידוד. ייתכן שלא יהיה תואם עם האצת חומרה במכשירים ישנים יותר. 0 משבית את B-פריימים, בעוד ש1- מגדיר את הערך זה באופן אוטומטי.", "transcoding_max_bitrate": "קצב סיביות מרבי", - "transcoding_max_bitrate_description": "קביעת קצב סיביות מרבי יכולה להפוך את גדלי הקבצים לצפויים יותר בעלות קלה לאיכות. ב-720p, ערכים טיפוסיים הם 2600k עבור VP9 או HEVC, או 4500k עבור H.264. מושבת אם מוגדר ל-0.", + "transcoding_max_bitrate_description": "קביעת קצב סיביות מרבי יכולה להפוך את גדלי הקבצים לצפויים יותר בעלות קלה לאיכות. ב-720p, ערכים טיפוסיים הם 2600 kbit/s עבור VP9 או HEVC, או 4500 kbit/s עבור H.264. מושבת אם מוגדר ל-0.", "transcoding_max_keyframe_interval": "מרווח תמונת מפתח מרבי", "transcoding_max_keyframe_interval_description": "מגדיר את מרחק הפריימים המרבי בין תמונות מפתח. ערכים נמוכים גורעים את יעילות הדחיסה, אך משפרים את זמני החיפוש ועשויים לשפר את האיכות בסצנות עם תנועה מהירה. 0 מגדיר ערך זה באופן אוטומטי.", "transcoding_optimal_description": "סרטונים גבוהים מרזולוציית היעד או לא בפורמט מקובל", @@ -391,6 +394,7 @@ "allow_edits": "אפשר עריכות", "allow_public_user_to_download": "אפשר למשתמש ציבורי להוריד", "allow_public_user_to_upload": "אפשר למשתמש ציבורי להעלות", + "alt_text_qr_code": "תמונת קוד QR", "anti_clockwise": "נגד כיוון השעון", "api_key": "מפתח API", "api_key_description": "הערך הזה יוצג רק פעם אחת. נא לוודא שהעתקת אותו לפני סגירת החלון.", @@ -437,7 +441,7 @@ "birthdate_set_description": "תאריך לידה משמש לחישוב הגיל של האדם הזה בזמן תצלום.", "blurred_background": "רקע מטושטש", "bugs_and_feature_requests": "באגים & בקשות לתכונות", - "build": "גרסאת בנייה", + "build": "גרסת בנייה", "build_image": "גרסת תוכנה", "bulk_delete_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך למחוק בכמות גדולה {count, plural, one {נכס # כפול} other {# נכסים כפולים}}? זה ישמור על הנכס הכי גדול של כל קבוצה וימחק לצמיתות את כל שאר הכפילויות. את/ה לא יכול/ה לבטל את הפעולה הזו!", "bulk_keep_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך להשאיר {count, plural, one {נכס # כפול} other {# נכסים כפולים}}? זה יפתור את כל הקבוצות הכפולות מבלי למחוק דבר.", @@ -481,6 +485,7 @@ "comments_are_disabled": "תגובות מושבתות", "confirm": "אישור", "confirm_admin_password": "אישור סיסמת מנהל", + "confirm_delete_face": "האם את/ה בטוח/ה שברצונך למחוק את הפנים של {name} מהנכס?", "confirm_delete_shared_link": "האם את/ה בטוח/ה שברצונך למחוק את הקישור המשותף הזה?", "confirm_keep_this_delete_others": "כל שאר הנכסים בערימה יימחקו למעט נכס זה. האם את/ה בטוח/ה שברצונך להמשיך?", "confirm_password": "אשר סיסמה", @@ -533,6 +538,7 @@ "delete_album": "מחק אלבום", "delete_api_key_prompt": "האם את/ה בטוח/ה שברצונך למחוק מפתח ה-API הזה?", "delete_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך למחוק לצמיתות את הכפילויות האלה?", + "delete_face": "מחק פנים", "delete_key": "מחק מפתח", "delete_library": "מחק ספרייה", "delete_link": "מחק קישור", @@ -600,6 +606,7 @@ "enabled": "מופעל", "end_date": "תאריך סיום", "error": "שגיאה", + "error_delete_face": "שגיאה במחיקת פנים מנכס", "error_loading_image": "שגיאה בטעינת התמונה", "error_title": "שגיאה - משהו השתבש", "errors": { @@ -884,6 +891,7 @@ "month": "חודש", "more": "עוד", "moved_to_trash": "הועבר לאשפה", + "mute_memories": "השתקת זיכרונות", "my_albums": "האלבומים שלי", "name": "שם", "name_or_nickname": "שם או כינוי", @@ -1076,6 +1084,8 @@ "removed_from_archive": "הוסר מארכיון", "removed_from_favorites": "הוסר ממועדפים", "removed_from_favorites_count": "{count, plural, other {הוסרו #}} מהמועדפים", + "removed_memory": "זיכרון הוסר", + "removed_photo_from_memory": "התמונה הוסרה מהזיכרון", "removed_tagged_assets": "תג הוסר מ{count, plural, one {נכס #} other {# נכסים}}", "rename": "שנה שם", "repair": "תיקון", @@ -1084,6 +1094,7 @@ "repository": "מאגר", "require_password": "דרוש סיסמה", "require_user_to_change_password_on_first_login": "דרוש מהמשתמש לשנות סיסמה בכניסה הראשונה", + "rescan": "סרוק מחדש", "reset": "איפוס", "reset_password": "איפוס סיסמה", "reset_people_visibility": "אפס את נראות האנשים", @@ -1127,6 +1138,7 @@ "search_options": "אפשרויות חיפוש", "search_people": "חיפוש אנשים", "search_places": "חיפוש מקומות", + "search_rating": "חיפוש לפי דירוג...", "search_settings": "הגדרות חיפוש", "search_state": "חיפוש מדינה...", "search_tags": "חיפוש תגים...", @@ -1250,6 +1262,7 @@ "tag_created": "נוצר תג: {tag}", "tag_feature_description": "עיון בתמונות וסרטונים שקובצו על ידי נושאי תג לוגיים", "tag_not_found_question": "לא מצליח למצוא תג? צור תג חדש", + "tag_people": "תייג אנשים", "tag_updated": "תג מעודכן: {tag}", "tagged_assets": "תויגו {count, plural, one {נכס #} other {# נכסים}}", "tags": "תגים", @@ -1290,6 +1303,7 @@ "unlink_motion_video": "בטל קישור סרטון תנועה", "unlink_oauth": "בטל קישור OAuth", "unlinked_oauth_account": "בוטל קישור חשבון OAuth", + "unmute_memories": "בטל השתקת זיכרונות", "unnamed_album": "אלבום ללא שם", "unnamed_album_delete_confirmation": "את/ה בטוח/ה שברצונך למחוק את האלבום הזה?", "unnamed_share": "שיתוף ללא שם", @@ -1343,6 +1357,7 @@ "view_all": "הצג הכל", "view_all_users": "הצג את כל המשתמשים", "view_in_timeline": "ראה בציר הזמן", + "view_link": "הצג קישור", "view_links": "הצג קישורים", "view_name": "הצג", "view_next_asset": "הצג את הנכס הבא", @@ -1359,4 +1374,4 @@ "yes": "כן", "you_dont_have_any_shared_links": "אין לך קישורים משותפים", "zoom_image": "זום לתמונה" -} +} \ No newline at end of file diff --git a/i18n/hi.json b/i18n/hi.json index fdc79c15eb..c2534fed72 100644 --- a/i18n/hi.json +++ b/i18n/hi.json @@ -13,14 +13,14 @@ "add_a_location": "एक स्थान जोड़ें", "add_a_name": "नाम जोड़ें", "add_a_title": "एक शीर्षक जोड़ें", - "add_exclusion_pattern": "निषेध उदाहरण जोड़ें", + "add_exclusion_pattern": "अपवाद उदाहरण जोड़ें", "add_import_path": "आयात पथ जोड़ें", "add_location": "स्थान जोड़ें", "add_more_users": "अधिक उपयोगकर्ता जोड़ें", "add_partner": "जोड़ीदार जोड़ें", "add_path": "पथ जोड़ें", "add_photos": "फ़ोटो जोड़ें", - "add_to": "इसमें जोड़ें..।", + "add_to": "इसमें जोड़ें…", "add_to_album": "एल्बम में जोड़ें", "add_to_shared_album": "साझा एल्बम में जोड़ें", "add_url": "URL जोड़ें", @@ -47,7 +47,7 @@ "exclusion_pattern_description": "Exclusion पैटर्न आपको अपनी लाइब्रेरी को स्कैन करते समय फ़ाइलों और फ़ोल्डरों को अनदेखा करने देता है। यह उपयोगी है यदि आपके पास ऐसे फ़ोल्डर हैं जिनमें ऐसी फ़ाइलें हैं जिन्हें आप आयात नहीं करना चाहते हैं, जैसे RAW फ़ाइलें।", "external_library_created_at": "बाहरी लाइब्रेरी ({date} को बनाई गई)", "external_library_management": "बाहरी लाइब्रेरी प्रबंधन", - "face_detection": "चेहरे का पहचान", + "face_detection": "मुख संशोधन", "face_detection_description": "मशीन लर्निंग का उपयोग करके संपत्तियों में चेहरों का पता लगाएं। वीडियो के लिए, केवल थंबनेल पर विचार किया जाता है। \"सभी\" परिसंपत्तियों को (पुनः) संसाधित करता है। \"लापता\" उन परिसंपत्तियों को कतारबद्ध करता है जिन्हें अभी तक संसाधित नहीं किया गया है। फेस डिटेक्शन पूरा होने के बाद पहचाने गए चेहरों को चेहरे की पहचान के लिए कतारबद्ध किया जाएगा, उन्हें मौजूदा या नए लोगों में समूहित किया जाएगा।", "facial_recognition_job_description": "समूह ने लोगों में चेहरों का पता लगाया। यह चरण फेस डिटेक्शन पूरा होने के बाद चलता है। \"सभी\" चेहरों को (पुनः) समूहित करता है। \"लापता\" कतार में वे चेहरे हैं जिनके लिए कोई व्यक्ति नियुक्त नहीं है।", "failed_job_command": "कार्य {job} के लिए आदेश {command} विफल", diff --git a/i18n/hr.json b/i18n/hr.json index b6d9c97748..8b8e909391 100644 --- a/i18n/hr.json +++ b/i18n/hr.json @@ -288,7 +288,7 @@ "transcoding_max_b_frames": "Maksimalni B-frameovi", "transcoding_max_b_frames_description": "Više vrijednosti poboljšavaju učinkovitost kompresije, ali usporavaju kodiranje. Možda nije kompatibilan s hardverskim ubrzanjem na starijim uređajima. 0 onemogućuje B-frameove, dok -1 automatski postavlja ovu vrijednost.", "transcoding_max_bitrate": "Maksimalne brzina prijenosa (bitrate)", - "transcoding_max_bitrate_description": "Postavljanje maksimalne brzine prijenosa može učiniti veličine datoteka predvidljivijima uz manji trošak za kvalitetu. Pri 720p, tipične vrijednosti su 2600k za VP9 ili HEVC ili 4500k za H.264. Onemogućeno ako je postavljeno na 0.", + "transcoding_max_bitrate_description": "Postavljanje maksimalne brzine prijenosa može učiniti veličine datoteka predvidljivijima uz manji trošak za kvalitetu. Pri 720p, tipične vrijednosti su 2600 kbit/s za VP9 ili HEVC ili 4500 kbit/s za H.264. Onemogućeno ako je postavljeno na 0.", "transcoding_max_keyframe_interval": "Maksimalni interval ključnih sličica", "transcoding_max_keyframe_interval_description": "Postavlja maksimalnu udaljenost slika između ključnih kadrova. Niže vrijednosti pogoršavaju učinkovitost kompresije, ali poboljšavaju vrijeme traženja i mogu poboljšati kvalitetu u scenama s brzim kretanjem. 0 automatski postavlja ovu vrijednost.", "transcoding_optimal_description": "Videozapisi koji su veći od ciljne rezolucije ili nisu u prihvatljivom formatu", @@ -468,6 +468,7 @@ "comments_are_disabled": "Komentari onemogućeni", "confirm": "Potvrdi", "confirm_admin_password": "Potvrdite lozinku administratora", + "confirm_delete_face": "Jeste li sigurni da želite izbrisati lice {name} iz knjižnice materijala.", "confirm_delete_shared_link": "Jeste li sigurni da želite izbrisati ovu zajedničku vezu?", "confirm_keep_this_delete_others": "Sva druga sredstva u nizu bit će izbrisana osim ovog sredstva. Jeste li sigurni da želite nastaviti?", "confirm_password": "Potvrdite lozinku", @@ -1252,4 +1253,4 @@ "yes": "", "you_dont_have_any_shared_links": "", "zoom_image": "" -} +} \ No newline at end of file diff --git a/i18n/hu.json b/i18n/hu.json index 5da84cdf3a..513775e45e 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -41,6 +41,7 @@ "backup_settings": "Biztonsági mentés beállításai", "backup_settings_description": "Adatbázis mentési beállításainak kezelése", "check_all": "Összes Kipiálása", + "cleanup": "Takarítás", "cleared_jobs": "{job}: feladatai törölve", "config_set_by_file": "A konfigurációt jelenleg egy konfigurációs fájl állítja be", "confirm_delete_library": "Biztosan ki szeretnéd törölni a {library} képtárat?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Képtár időszakos átfésülésének engedélyezése", "library_settings": "Külső Képtár", "library_settings_description": "Külső képtár beállításainak kezelése", - "library_tasks_description": "Képtár feladatok elvégzése", + "library_tasks_description": "Külső könyvtárak szkennelése új és/vagy módosított elemek után", "library_watching_enable_description": "Külső képtár változásainak figyelése", "library_watching_settings": "Képtár figyelése (KÍSÉRLETI)", "library_watching_settings_description": "Megváltozott fájlok automatikus észlelése", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Képek szemantikai keresése CLIP beágyazások segítségével", "machine_learning_smart_search_enabled": "Okos keresés engedélyezése", "machine_learning_smart_search_enabled_description": "Ha ki van kapcsolva, a képek nem lesznek átalakítva okos kereséshez.", - "machine_learning_url_description": "Gépi tanulás szerver URL címe. Ha többi, mint egy URL van megadva, mindegyik szervert egyenként próbálja meg, amíg az egyik sikeresen nem válaszol, sorrendben az elsőtől az utólsóig.", + "machine_learning_url_description": "Gépi tanulás szerver URL címe. Ha többi, mint egy URL van megadva, mindegyik szervert egyenként próbálja meg, amíg az egyik sikeresen nem válaszol, sorrendben az elsőtől az utólsóig. A nem válaszoló szervereket átmenetileg figyelmen kívül hagyja, amíg újra online nem lesznek.", "manage_concurrency": "Párhuzamos Feladatok Kezelése", "manage_log_settings": "Naplózási beállítások kezelése", "map_dark_style": "Sötét stílus", @@ -147,6 +148,8 @@ "map_settings": "Térkép", "map_settings_description": "Térkép beállítások kezelése", "map_style_description": "Egy style.json térképtémára mutató URL cím", + "memory_cleanup_job": "Memória takarítás", + "memory_generate_job": "Emlék létrehozása", "metadata_extraction_job": "Metaadatok kinyerése", "metadata_extraction_job_description": "Metaadat információk (pl. GPS, arcok és felbontás) kinyerése minden elemből", "metadata_faces_import_setting": "Arc importálás engedélyezése", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "B-képkockák maximum száma", "transcoding_max_b_frames_description": "Nagyobb értékek megnövelik a tömörítés hatékonyságát, de lelassítják a kódolást. Nem minden hardvereszköz támogatja. A 0 érték kikapcsolja a B-képkockákat, míg -1 esetén a szoftver magának választ értéket.", "transcoding_max_bitrate": "Maximum bitráta", - "transcoding_max_bitrate_description": "Maximum bitráta beállítása konzisztensebb fájlméretet eredményez egy kevés minőségi romlás árán. 720p esetén jellemző érték lehet 2600k a VP9 vagy HEVC kódoláshoz, 4500k a H.264 kódoláshoz. A 0 érték esetén nincs maximum bitráta.", + "transcoding_max_bitrate_description": "Maximum bitráta beállítása konzisztensebb fájlméretet eredményez egy kevés minőségi romlás árán. 720p esetén jellemző érték lehet 2600 kbit/s a VP9 vagy HEVC kódoláshoz, 4500 kbit/s a H.264 kódoláshoz. A 0 érték esetén nincs maximum bitráta.", "transcoding_max_keyframe_interval": "Maximum kulcskocka intervallum", "transcoding_max_keyframe_interval_description": "Beállítja a kulcskockák közötti legnagyobb lehetséges távolságot. Alacsony érték csökkenti a tömörítési hatékonyságot, de lejátszás közben az előre- és hátratekerés gyorsabb, valamint javíthatja a gyorsan mozgó jelenetek képminőségét. 0 esetén a szoftver magának állítja be az értéket.", "transcoding_optimal_description": "A célfelbontásnál nagyobb vagy a nem elfogadott formátumú videókat", @@ -391,6 +394,7 @@ "allow_edits": "Módosítások engedélyezése", "allow_public_user_to_download": "Engedélyezi a letöltést publikus felhasználó számára", "allow_public_user_to_upload": "Engedélyezi a feltöltést publikus felhasználó számára", + "alt_text_qr_code": "QR kód kép", "anti_clockwise": "Óramutató járásával ellentétes irány", "api_key": "API Kulcs", "api_key_description": "Ez csak most az egyszer jelenik meg. Az ablak bezárása előtt feltétlenül másold.", @@ -481,6 +485,7 @@ "comments_are_disabled": "A megjegyzések le vannak tiltva", "confirm": "Jóváhagy", "confirm_admin_password": "Admin Jelszó Újból", + "confirm_delete_face": "Biztos, hogy törölni szeretnéd a(z) {name} arcát az elemről?", "confirm_delete_shared_link": "Biztosan törölni szeretnéd ezt a megosztott linket?", "confirm_keep_this_delete_others": "Minden más elem a készletben törlésre kerül, kivéve ezt az elemet. Biztosan folytatni szeretnéd?", "confirm_password": "Jelszó megerősítése", @@ -533,6 +538,7 @@ "delete_album": "Album törlése", "delete_api_key_prompt": "Biztosan törölni szeretnéd ezt az API kulcsot?", "delete_duplicates_confirmation": "Biztosan véglegesen törölni szeretnéd ezeket a duplikátumokat?", + "delete_face": "Arc törlése", "delete_key": "Kulcs törlése", "delete_library": "Képtár Törlése", "delete_link": "Link törlése", @@ -600,6 +606,7 @@ "enabled": "Engedélyezve", "end_date": "Vég dátum", "error": "Hiba", + "error_delete_face": "Hiba az arc törlése során", "error_loading_image": "Hiba a kép betöltése közben", "error_title": "Hiba - valami félresikerült", "errors": { @@ -802,6 +809,7 @@ "include_shared_albums": "Megosztott albumokkal együtt", "include_shared_partner_assets": "Partner által megosztott elemekkel együtt", "individual_share": "Egymagában megosztott elem", + "individual_shares": "Egyéni megosztások", "info": "Infó", "interval": { "day_at_onepm": "Minden nap 13 órakor", @@ -883,6 +891,7 @@ "month": "Hónap", "more": "Továbbiak", "moved_to_trash": "Áthelyezve a lomtárba", + "mute_memories": "Emlékek elnémítása", "my_albums": "Saját albumaim", "name": "Név", "name_or_nickname": "Név vagy becenév", @@ -987,6 +996,7 @@ "pick_a_location": "Hely választása", "place": "Hely", "places": "Helyek", + "places_count": "{count, plural, one {{count, number} Helyszín} other {{count, number} Helyszín}}", "play": "Lejátszás", "play_memories": "Emlékek lejátszása", "play_motion_photo": "Mozgókép lejátszása", @@ -1074,6 +1084,8 @@ "removed_from_archive": "Archívumból eltávolítva", "removed_from_favorites": "Kedvencekből eltávolítva", "removed_from_favorites_count": "A kedvencekből {count, plural, other {# elem}} eltávolítva", + "removed_memory": "Eltávolított emlék", + "removed_photo_from_memory": "Fotó törölve az emlékből", "removed_tagged_assets": "Címke eltávolítva {count, plural, one {# elemről} other {# elemről}}", "rename": "Átnevezés", "repair": "Javítás", @@ -1082,6 +1094,7 @@ "repository": "Tároló", "require_password": "Jelszó megadása szükséges", "require_user_to_change_password_on_first_login": "A felhasználónak kötelező megváltoztatnia a jelszavát az első bejelentkezéskor", + "rescan": "Újraszkennelés", "reset": "Visszaállítás", "reset_password": "Jelszó visszaállítása", "reset_people_visibility": "Személyek láthatóságának visszaállítása", @@ -1125,6 +1138,7 @@ "search_options": "Keresési lehetőségek", "search_people": "Személyek keresése", "search_places": "Helyek keresése", + "search_rating": "Keresés értékelés szerint...", "search_settings": "Keresési beállítások", "search_state": "Megye/Állam keresése...", "search_tags": "Címkék keresése...", @@ -1171,6 +1185,7 @@ "shared_from_partner": "{partner} fényképei", "shared_link_options": "Megosztott link beállításai", "shared_links": "Megosztott linkek", + "shared_links_description": "Fényképek és videók megosztása linkkel", "shared_photos_and_videos_count": "{assetCount, plural, other {# megosztott kép és videó.}}", "shared_with_partner": "Megosztva {partner} partnereddel", "sharing": "Megosztás", @@ -1247,6 +1262,7 @@ "tag_created": "Létrehozott címke: {tag}", "tag_feature_description": "Fényképek és videók böngészése a címkék témája szerint csoportosítva", "tag_not_found_question": "Nem találod a címkét? Hozz létre egy új címkét", + "tag_people": "Emberek címkézése", "tag_updated": "Frissített címke: {tag}", "tagged_assets": "{count, plural, one {# elem} other {# elem}} felcímkézve", "tags": "Címkék", @@ -1287,6 +1303,7 @@ "unlink_motion_video": "Mozgókép leválasztása", "unlink_oauth": "OAuth leválasztása", "unlinked_oauth_account": "Leválasztott OAuth fiók", + "unmute_memories": "Emlékek mutatása", "unnamed_album": "Névtelen Album", "unnamed_album_delete_confirmation": "Biztosan törölni szeretnéd ezt az albumot?", "unnamed_share": "Névtelen Megosztás", @@ -1340,6 +1357,7 @@ "view_all": "Összes Megtekintése", "view_all_users": "Minden Felhasználó Megtekintése", "view_in_timeline": "Megtekintés az idővonalon", + "view_link": "Link megtekintése", "view_links": "Linkek megtekintése", "view_name": "Megtekintés", "view_next_asset": "Következő elem megtekintése", @@ -1356,4 +1374,4 @@ "yes": "Igen", "you_dont_have_any_shared_links": "Nincsenek megosztott linkjeid", "zoom_image": "Kép Nagyítása" -} +} \ No newline at end of file diff --git a/i18n/id.json b/i18n/id.json index 79c9deb178..92f59363aa 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -41,6 +41,7 @@ "backup_settings": "Pengaturan Pencadangan", "backup_settings_description": "Kelola pengaturan pencadangan basis data", "check_all": "Periksa Semua", + "cleanup": "Pembersihan", "cleared_jobs": "Tugas terselesaikan untuk: {job}", "config_set_by_file": "Konfigurasi saat ini ditetapkan oleh berkas konfigurasi", "confirm_delete_library": "Apakah Anda yakin ingin menghapus pustaka {library}?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Aktifkan pemindaian pustaka berkala", "library_settings": "Pustaka Eksternal", "library_settings_description": "Kelola pengaturan pustaka eksternal", - "library_tasks_description": "Lakukan tugas pustaka", + "library_tasks_description": "Pindai pustaka eksternal untuk aset baru dan/atau berubah", "library_watching_enable_description": "Pantau perubahan berkas dalam pustaka eksternal", "library_watching_settings": "Pemantauan pustaka (UJI COBA)", "library_watching_settings_description": "Pantau berkas yang telah diubah secara otomatis", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Cari gambar secara semantik menggunakan penyematan CLIP", "machine_learning_smart_search_enabled": "Aktifkan pencarian pintar", "machine_learning_smart_search_enabled_description": "Jika dinonaktifkan, gambar tidak akan dienkode untuk pencarian pintar.", - "machine_learning_url_description": "URL server pembelajaran mesin. Jika lebih dari satu URL disediakan, setiap server akan dicoba satu per satu sampai salah satu berhasil merespons, dari urutan pertama sampai terakhir.", + "machine_learning_url_description": "URL server pembelajaran mesin. Jika lebih dari satu URL disediakan, setiap server akan dicoba satu per satu sampai salah satu berhasil merespons, dari urutan pertama sampai terakhir. Server yang tidak merespons akan diabaikan sementara sampai kembali daring.", "manage_concurrency": "Kelola Konkurensi", "manage_log_settings": "Kelola pengaturan log", "map_dark_style": "Gaya gelap", @@ -147,6 +148,8 @@ "map_settings": "Peta", "map_settings_description": "Kelola pengaturan peta", "map_style_description": "URL ke tema peta style.json", + "memory_cleanup_job": "Pembersihan memori", + "memory_generate_job": "Pembuatan memori", "metadata_extraction_job": "Ekstrak metadata", "metadata_extraction_job_description": "Ekstrak informasi metadata dari setiap aset, seperti GPS, wajah dan resolusi", "metadata_faces_import_setting": "Aktifkan impor wajah", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Mengaktifkan verifikasi hash, jangan mengaktifkan ini kecuali Anda sudah tahu kekurangannya", "storage_template_migration": "Migrasi templat penyimpanan", "storage_template_migration_description": "Tetapkan {template} saat ini pada aset yang sebelumnya diunggah", - "storage_template_migration_info": "Perubahan templat hanya akan diterapkan pada aset baru. Untuk menerapkan templat pada setiap aset yang sebelumnya telah diunggah, jalankan {job}.", + "storage_template_migration_info": "Templat penyimpanan akan mengubah semua ekstensi ke huruf kecil. Perubahan templat hanya akan diterapkan pada aset baru. Untuk menerapkan templat pada setiap aset yang sebelumnya telah diunggah, jalankan {job}.", "storage_template_migration_job": "Tugas Migrasi Templat Ruang Penyimpanan", "storage_template_more_details": "Untuk detail lebih lanjut tentang fitur ini, pergi ke Templat Penyimpanan dan kekurangannya", "storage_template_onboarding_description": "Ketika diaktifkan, fitur ini akan mengelola berkas secara otomatis berdasarkan templat pengguna. Karena masalah stabilitas, fitur ini telah dimatikan secara bawaan. Untuk informasi lebih lanjut, silakan lihat dokumentasi.", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Bingkai B maksimum", "transcoding_max_b_frames_description": "Nilai yang lebih tinggi meningkatkan efisiensi kompresi, tetapi membuat pengodean lebih lambat. Mungkin tidak kompatibel dengan akselerasi perangkat keras pada perangkat lawas. 0 menonaktifkan bingkai B, sedangkan -1 mengatur nilai ini secara otomatis.", "transcoding_max_bitrate": "Kecepatan bit maksimum", - "transcoding_max_bitrate_description": "Menetapkan kecepatan bit maksimum dapat membuat ukuran berkas lebih dapat diprediksi dengan kekurangan minor pada kualitas. Pada 720p, nilai umum adalah 2600k untuk VP9 atau HEVC, atau 4500k untuk H.264. Dinonaktifkan jika ditetapkan ke 0.", + "transcoding_max_bitrate_description": "Menetapkan kecepatan bit maksimum dapat membuat ukuran berkas lebih dapat diprediksi dengan kekurangan minor pada kualitas. Pada 720p, nilai umum adalah 2600 kbit/s untuk VP9 atau HEVC, atau 4500 kbit/s untuk H.264. Dinonaktifkan jika ditetapkan ke 0.", "transcoding_max_keyframe_interval": "Interval bingkai kunci maksimum", "transcoding_max_keyframe_interval_description": "Menetapkan jarak bingkai maksimum antara bingkai kunci. Nilai yang lebih rendah membuat efisiensi kompresi lebih buruk, tetapi meningkatkan waktu pencarian dan dapat meningkatkan kualitas dalam adegan dengan gerakan cepat. 0 menetapkan nilai ini secara otomatis.", "transcoding_optimal_description": "Video lebih tinggi dari resolusi sasaran atau tidak dalam format yang diterima", @@ -391,6 +394,7 @@ "allow_edits": "Perbolehkan penyuntingan", "allow_public_user_to_download": "Perbolehkan pengguna publik untuk mengunduh", "allow_public_user_to_upload": "Perbolehkan pengguna publik untuk mengunggah", + "alt_text_qr_code": "Gambar kode QR", "anti_clockwise": "Berlawanan arah jarum jam", "api_key": "Kunci API", "api_key_description": "Nilai ini hanya akan ditampilkan sekali. Pastikan untuk menyalin sebelum menutup jendela ini.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Komentar dinonaktifkan", "confirm": "Konfirmasi", "confirm_admin_password": "Konfirmasi Kata Sandi Admin", + "confirm_delete_face": "Apakah Anda yakin ingin menghapus wajah {name} dari aset?", "confirm_delete_shared_link": "Apakah Anda yakin ingin menghapus tautan terbagi ini?", "confirm_keep_this_delete_others": "Semua aset lain di dalam stack akan dihapus kecuali aset ini. Anda yakin untuk melanjutkan?", "confirm_password": "Konfirmasi kata sandi", @@ -533,6 +538,7 @@ "delete_album": "Hapus album", "delete_api_key_prompt": "Apakah Anda yakin ingin menghapus kunci API ini?", "delete_duplicates_confirmation": "Apakah Anda yakin ingin menghapus duplikat ini secara permanen?", + "delete_face": "Hapus wajah", "delete_key": "Hapus kunci", "delete_library": "Hapus Pustaka", "delete_link": "Hapus tautan", @@ -600,6 +606,7 @@ "enabled": "Diaktifkan", "end_date": "Tanggal akhir", "error": "Eror", + "error_delete_face": "Terjadi kesalahan menghapus wajah dari aset", "error_loading_image": "Terjadi eror memuat gambar", "error_title": "Eror - Ada yang salah", "errors": { @@ -884,6 +891,7 @@ "month": "Bulan", "more": "Lainnya", "moved_to_trash": "Dipindahkan ke sampah", + "mute_memories": "Nonaktifkan Kenangan", "my_albums": "Album saya", "name": "Nama", "name_or_nickname": "Nama atau nama panggilan", @@ -1076,6 +1084,8 @@ "removed_from_archive": "Dihapus dari arsip", "removed_from_favorites": "Dihapus dari favorit", "removed_from_favorites_count": "{count, plural, other {Menghapus #}} dari favorit", + "removed_memory": "Memori dihapus", + "removed_photo_from_memory": "Foto dihapus dari memori", "removed_tagged_assets": "Hapus tag dari {count, plural, one {# aset} other {# aset}}", "rename": "Ubah nama", "repair": "Perbaiki", @@ -1084,6 +1094,7 @@ "repository": "Repositori", "require_password": "Memerlukan kata sandi", "require_user_to_change_password_on_first_login": "Memerlukan pengguna untuk mengubah kata sandi pada log masuk pertama", + "rescan": "Pindai ulang", "reset": "Atur ulang", "reset_password": "Atur ulang kata sandi", "reset_people_visibility": "Atur ulang keterlihatan orang", @@ -1127,6 +1138,7 @@ "search_options": "Pilihan pencarian", "search_people": "Cari orang", "search_places": "Cari tempat", + "search_rating": "Cari berdasarkan penilaian...", "search_settings": "Pengaturan pencarian", "search_state": "Cari negara bagian...", "search_tags": "Cari tag...", @@ -1250,6 +1262,7 @@ "tag_created": "Tag yang di buat: {tag}", "tag_feature_description": "Menjelajahi foto dan video yang dikelompokkan berdasarkan topik tag logis", "tag_not_found_question": "Tidak dapat menemukan tag? Buat tag baru.", + "tag_people": "Tandai Orang", "tag_updated": "Tag yang diperbarui: {tag}", "tagged_assets": "Ditandai {count, plural, one {# aset} other {# aset}}", "tags": "Tag", @@ -1290,6 +1303,7 @@ "unlink_motion_video": "Membatalkan tautan video gerak", "unlink_oauth": "Putuskan OAuth", "unlinked_oauth_account": "Akun OAuth terputus", + "unmute_memories": "Aktifkan Kenangan", "unnamed_album": "Album Tanpa Nama", "unnamed_album_delete_confirmation": "Apakah kamu yakin akan menghapus album ini?", "unnamed_share": "Pembagian Tanpa Nama", @@ -1343,6 +1357,7 @@ "view_all": "Tampilkan Semua", "view_all_users": "Tampilkan semua pengguna", "view_in_timeline": "Lihat di timeline", + "view_link": "Tampilkan tautan", "view_links": "Tampilkan tautan", "view_name": "Tampilkan", "view_next_asset": "Tampilkan aset berikutnya", @@ -1359,4 +1374,4 @@ "yes": "Ya", "you_dont_have_any_shared_links": "Anda tidak memiliki tautan terbagi", "zoom_image": "Perbesar Gambar" -} +} \ No newline at end of file diff --git a/i18n/it.json b/i18n/it.json index a00980bf4c..358a96d28a 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -41,6 +41,7 @@ "backup_settings": "Impostazioni di backup", "backup_settings_description": "Gestisci le impostazioni dei backup", "check_all": "Controlla Tutto", + "cleanup": "Pulisci", "cleared_jobs": "Cancellati i processi per: {job}", "config_set_by_file": "La configurazione è attualmente impostata da un file di configurazione", "confirm_delete_library": "Sei sicuro di voler cancellare la libreria {library}?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Attiva la scansione periodica della libreria", "library_settings": "Libreria Esterna", "library_settings_description": "Gestisci le impostazioni della libreria esterna", - "library_tasks_description": "Esegui processi della libreria", + "library_tasks_description": "Scansiona le librerie esterne per i nuovi aggiornamenti", "library_watching_enable_description": "Osserva le librerie esterne per cambiamenti", "library_watching_settings": "Osserva librerie (SPERIMENTALE)", "library_watching_settings_description": "Osserva automaticamente i cambiamenti dei file", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Cerca immagini semanticamente utilizzato gli embedding CLIP", "machine_learning_smart_search_enabled": "Attiva ricerca intelligente", "machine_learning_smart_search_enabled_description": "Se disabilitato le immagini non saranno codificate per la ricerca intelligente.", - "machine_learning_url_description": "URL del server machine learning. Se sono stati forniti più di un URL, verrà testato un server alla volta finché uno non risponderà, in ordine dal primo all'ultimo.", + "machine_learning_url_description": "URL del server machine learning. Se sono stati forniti più di un URL, verrà testato un server alla volta finché uno non risponderà, in ordine dal primo all'ultimo. I server che non rispondono saranno temporaneamente ignorati finché non torneranno online.", "manage_concurrency": "Gestisci Concorrenza", "manage_log_settings": "Gestisci le impostazioni dei log", "map_dark_style": "Tema scuro", @@ -147,6 +148,8 @@ "map_settings": "Impostazioni Mappa e Posizione", "map_settings_description": "Gestisci impostazioni mappa", "map_style_description": "URL per un tema della mappa style.json", + "memory_cleanup_job": "pulizia memoria", + "memory_generate_job": "Generazione della memoria", "metadata_extraction_job": "Estrazione Metadata", "metadata_extraction_job_description": "Estrai informazioni dai metadati di ciascun asset, ad esempio coordinate GPS, volti e risoluzione", "metadata_faces_import_setting": "Abilita l'importazione dei volti", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "B-frames Massimi", "transcoding_max_b_frames_description": "Valori più alti migliorano l'efficienza di compressione, ma rallentano l'encoding. Potrebbero non essere compatibili con l'accelerazione hardware su dispositivi più vecchi. 0 disabilita i B-frames, mentre -1 imposta questo valore automaticamente.", "transcoding_max_bitrate": "Bitrate massimo", - "transcoding_max_bitrate_description": "Impostare un bitrate massimo può rendere le dimensioni dei file più prevedibili a un costo minore per la qualità. A 720p, i valori tipici sono 2600k per VP9 o HEVC, o 4500k per H.264. Disabilitato se impostato su 0.", + "transcoding_max_bitrate_description": "Impostare un bitrate massimo può rendere le dimensioni dei file più prevedibili a un costo minore per la qualità. A 720p, i valori tipici sono 2600 kbit/s per VP9 o HEVC, o 4500 kbit/s per H.264. Disabilitato se impostato su 0.", "transcoding_max_keyframe_interval": "Intervallo massimo dei keyframe", "transcoding_max_keyframe_interval_description": "Imposta la distanza massima tra i keyframe. Valori più bassi peggiorano l'efficienza di compressione, però migliorano i tempi di ricerca e possono migliorare la qualità nelle scene con movimenti rapidi. 0 imposta questo valore automaticamente.", "transcoding_optimal_description": "Video con risoluzione più alta rispetto alla risoluzione desiderata o in formato non accettato", @@ -391,6 +394,7 @@ "allow_edits": "Permetti Modifiche", "allow_public_user_to_download": "Permetti agli utenti pubblici di scaricare", "allow_public_user_to_upload": "Permetti agli utenti pubblici di caricare", + "alt_text_qr_code": "Immagine QR", "anti_clockwise": "Senso anti-orario", "api_key": "Chiave API", "api_key_description": "Il valore verrà mostrato solo una volta. Assicurati di copiarlo prima di chiudere la finestra.", @@ -406,7 +410,7 @@ "are_these_the_same_person": "Sono la stessa persona?", "are_you_sure_to_do_this": "Sei sicuro di voler procedere?", "asset_added_to_album": "Aggiunto all'album", - "asset_adding_to_album": "In aggiunta all'album...", + "asset_adding_to_album": "Aggiungendo all'album…", "asset_description_updated": "La descrizione del media è stata aggiornata", "asset_filename_is_offline": "Il media {filename} è offline", "asset_has_unassigned_faces": "Il media ha dei volti non categorizzati", @@ -480,11 +484,12 @@ "comments_and_likes": "Commenti & mi piace", "comments_are_disabled": "I commenti sono disabilitati", "confirm": "Conferma", - "confirm_admin_password": "Conferma password amministratore", + "confirm_admin_password": "Conferma password dell'amministratore", + "confirm_delete_face": "Sei sicuro di voler cancellare il volto di {name} dall'asset?", "confirm_delete_shared_link": "Sei sicuro di voler eliminare questo link condiviso?", - "confirm_keep_this_delete_others": "Tutti gli altri asset nello stack saranno eliminati, eccetto questo asset. Vuoi continuare?", + "confirm_keep_this_delete_others": "Tutti gli altri asset nello stack saranno eliminati, eccetto questo asset. Sei sicuro di voler continuare?", "confirm_password": "Conferma password", - "contain": "Adatta", + "contain": "Adatta alla finestra", "context": "Contesto", "continue": "Continua", "copied_image_to_clipboard": "Immagine copiata negli appunti.", @@ -497,8 +502,8 @@ "copy_password": "Copia password", "copy_to_clipboard": "Copia negli appunti", "country": "Nazione", - "cover": "Riempi", - "covers": "Miniature", + "cover": "Riempi la finestra", + "covers": "Copre", "create": "Crea", "create_album": "Crea album", "create_library": "Crea libreria", @@ -509,21 +514,21 @@ "create_new_person_hint": "Assegna gli asset selezionati a una nuova persona", "create_new_user": "Crea nuovo utente", "create_tag": "Crea tag", - "create_tag_description": "Crea un nuova tag. Per i tag annidati, si prega di inserire il percorso completo del tag tra cui slash in avanti.", + "create_tag_description": "Crea un nuovo tag. Per i tag annidati, si prega di inserire il percorso completo del tag tra cui barre oblique.", "create_user": "Crea utente", "created": "Creato", - "current_device": "Dispositivo corrente", + "current_device": "Dispositivo attuale", "custom_locale": "Localizzazione personalizzata", "custom_locale_description": "Formatta data e numeri in base alla lingua e al paese", "dark": "Scuro", "date_after": "Data dopo", - "date_and_time": "Data e tempo", + "date_and_time": "Data e ora", "date_before": "Data prima", "date_of_birth_saved": "Data di nascita salvata con successo", "date_range": "Intervallo di date", "day": "Giorno", - "deduplicate_all": "De-duplica Tutti", - "deduplication_criteria_1": "Dimensione immagine in byte", + "deduplicate_all": "Duplica Tutti", + "deduplication_criteria_1": "Dimensione immagine in bytes", "deduplication_criteria_2": "Numero di dati EXIF", "deduplication_info": "Informazioni di deduplicazione", "deduplication_info_description": "Per preselezionare automaticamente gli asset e rimuovere i duplicati in massa, verifichiamo:", @@ -533,6 +538,7 @@ "delete_album": "Elimina album", "delete_api_key_prompt": "Sei sicuro di voler eliminare questa chiave API?", "delete_duplicates_confirmation": "Sei sicuro di voler eliminare questi duplicati per sempre?", + "delete_face": "cancella faccia", "delete_key": "Elimina chiave", "delete_library": "Elimina libreria", "delete_link": "Elimina link", @@ -600,6 +606,7 @@ "enabled": "Abilitato", "end_date": "Data Fine", "error": "Errore", + "error_delete_face": "Errore nel cancellare la faccia dalla foto", "error_loading_image": "Errore nel caricamento dell'immagine", "error_title": "Errore - Qualcosa è andato storto", "errors": { @@ -769,7 +776,7 @@ "group_country": "Raggruppa per paese", "group_no": "Nessun raggruppamento", "group_owner": "Raggruppa in base al proprietario", - "group_places_by": "Raggruppa posti per", + "group_places_by": "Raggruppa posti per...", "group_year": "Raggruppa per anno", "has_quota": "Ha limite", "hi_user": "Ciao {name} ({email})", @@ -884,6 +891,7 @@ "month": "Mese", "more": "Di più", "moved_to_trash": "Spostato nel cestino", + "mute_memories": "Silenzia ricordi", "my_albums": "I miei album", "name": "Nome", "name_or_nickname": "Nome o soprannome", @@ -988,7 +996,7 @@ "pick_a_location": "Scegli una posizione", "place": "Posizione", "places": "Luoghi", - "places_count": "{count, plural, one {{count, number} Luogo} altro {{count, number} Luoghi}}", + "places_count": "{count, plural, one {{count, number} Luogo} other {{count, number} Places}}", "play": "Avvia", "play_memories": "Avvia ricordi", "play_motion_photo": "Avvia Foto in movimento", @@ -1076,6 +1084,8 @@ "removed_from_archive": "Rimosso dall'archivio", "removed_from_favorites": "Rimosso dai preferiti", "removed_from_favorites_count": "{count, plural, one {Rimosso } other {Rimossi #}} dai preferiti", + "removed_memory": "Memoria rimossa", + "removed_photo_from_memory": "Foto rimossa dalla memoria", "removed_tagged_assets": "Rimossa etichetta {count, plural, one {# dall'asset} other {# dagli asset}}", "rename": "Rinomina", "repair": "Ripara", @@ -1084,6 +1094,7 @@ "repository": "Repository", "require_password": "Richiedi password", "require_user_to_change_password_on_first_login": "Richiedi all'utente di cambiare password al primo accesso", + "rescan": "Scansiona nuovamente", "reset": "Ripristina", "reset_password": "Ripristina password", "reset_people_visibility": "Ripristina visibilità persone", @@ -1127,6 +1138,7 @@ "search_options": "Opzioni Ricerca", "search_people": "Cerca persone", "search_places": "Cerca luoghi", + "search_rating": "Cerca per valutazione...", "search_settings": "Cerca Impostazioni", "search_state": "Cerca stato...", "search_tags": "Cerca tag...", @@ -1250,6 +1262,7 @@ "tag_created": "Tag creata: {tag}", "tag_feature_description": "Navigazione foto e video raggruppati per argomenti tag logici", "tag_not_found_question": "Non riesci a trovare un tag? Creane uno nuovo.", + "tag_people": "Tagga persone", "tag_updated": "Tag {tag} aggiornata", "tagged_assets": "{count, plural, one {# asset etichettato} other {# asset etichettati}}", "tags": "Tag", @@ -1290,6 +1303,7 @@ "unlink_motion_video": "Scollega video in movimento", "unlink_oauth": "Scollega OAuth", "unlinked_oauth_account": "Scollega account OAuth", + "unmute_memories": "Disattiva l'audio dei ricordi", "unnamed_album": "Album senza nome", "unnamed_album_delete_confirmation": "Sei sicuro di voler eliminare questo album?", "unnamed_share": "Condivisione senza nome", @@ -1330,7 +1344,7 @@ "variables": "Variabili", "version": "Versione", "version_announcement_closing": "Il tuo amico, Alex", - "version_announcement_message": "Ehilà! È stata rilasciata una nuova versione di Immich. Leggi le note di rilascio e assicurati che i tuoi file docker-compose.yml/.env siano aggiornati per evitare problemi e incongruenze, soprattutto se utilizzi WatchTower o altri strumenti per aggiornare Immich in automatico.", + "version_announcement_message": "Ehilà! È stata rilasciata una nuova versione di Immich. Leggi le release notes e assicurati che i tuoi file di configurazione siano aggiornati per evitare problemi e incongruenze, soprattutto se utilizzi WatchTower o altri strumenti per aggiornare Immich in automatico.", "version_history": "Storico delle Versioni", "version_history_item": "Versione installata {version} il {date}", "video": "Video", @@ -1343,6 +1357,7 @@ "view_all": "Vedi tutto", "view_all_users": "Visualizza tutti gli utenti", "view_in_timeline": "Visualizza in timeline", + "view_link": "Visualizza link", "view_links": "Visualizza i link", "view_name": "Visualizza", "view_next_asset": "Visualizza risorsa successiva", @@ -1359,4 +1374,4 @@ "yes": "Si", "you_dont_have_any_shared_links": "Non è presente alcun link condiviso", "zoom_image": "Ingrandisci immagine" -} +} \ No newline at end of file diff --git a/i18n/ja.json b/i18n/ja.json index 98054f14ca..154e6be42e 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -256,7 +256,7 @@ "transcoding_max_b_frames": "最大Bフレーム", "transcoding_max_b_frames_description": "値を高くすると圧縮効率が向上しますが、エンコード速度が遅くなります。古いデバイスのハードウェアアクセラレーションでは対応していない場合があります。\"0\" はBフレームを無効にし、\"-1\" はこの値を自動的に設定します。", "transcoding_max_bitrate": "最大ビットレート", - "transcoding_max_bitrate_description": "最大ビットレートを設定すると、品質にわずかな影響を与えながらも、ファイルサイズを予測しやすくなります。720pの場合、一般的な値は VP9 や HEVC で \"2600k\"、H.264 で \"4500k\" です。\"0\" に設定すると無効になります。", + "transcoding_max_bitrate_description": "最大ビットレートを設定すると、品質にわずかな影響を与えながらも、ファイルサイズを予測しやすくなります。720pの場合、一般的な値は VP9 や HEVC で \"2600 kbit/s\"、H.264 で \"4500 kbit/s\" です。\"0\" に設定すると無効になります。", "transcoding_max_keyframe_interval": "最大キーフレーム間隔", "transcoding_max_keyframe_interval_description": "キーフレーム間の最大フレーム間隔を設定します。値を低くすると圧縮効率が悪化しますが、シーク時間が改善され、動きの速いシーンの品質が向上する場合があります。\"0\" に設定すると、この値が自動的に設定されます。", "transcoding_optimal_description": "設定解像度を超える動画、または容認されていない形式の動画", @@ -1257,4 +1257,4 @@ "yes": "はい", "you_dont_have_any_shared_links": "共有リンクはありません", "zoom_image": "画像を拡大" -} +} \ No newline at end of file diff --git a/i18n/ko.json b/i18n/ko.json index ef8227c3f2..29971e5e30 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -68,7 +68,7 @@ "image_prefer_embedded_preview": "포함된 미리 보기 선호", "image_prefer_embedded_preview_setting_description": "가능한 경우 이미지 처리 시 RAW 사진에 포함된 미리 보기를 사용합니다. 포함된 미리 보기는 카메라에서 생성된 것으로 카메라마다 품질이 다릅니다. 일부 이미지의 경우 더 정확한 색상이 표현될 수 있지만 반대로 더 많은 아티팩트가 있을 수도 있습니다.", "image_prefer_wide_gamut": "넓은 색 영역 선호", - "image_prefer_wide_gamut_setting_description": "섬네일 이미지에 Display P3을 사용합니다. 많은 색상을 표현할 수 있어 더 정확한 표현이 가능하지만, 오래된 브라우저를 사용하는 경우 이미지가 다르게 보일 수 있습니다. 색상 왜곡을 방지하기 위해 sRGB 이미지는 이 설정이 적용되지 않습니다.", + "image_prefer_wide_gamut_setting_description": "섬네일 이미지에 Display P3를 사용합니다. 많은 색상을 표현할 수 있어 더 정확한 표현이 가능하지만, 오래된 브라우저를 사용하는 경우 이미지가 다르게 보일 수 있습니다. 색상 왜곡을 방지하기 위해 sRGB 이미지는 이 설정이 적용되지 않습니다.", "image_preview_description": "메타데이터를 제거한 중간 크기의 이미지, 단일 항목을 보는 경우 및 기계 학습에 사용됨", "image_preview_quality_description": "1부터 100 사이의 미리보기 품질. 값이 높을수록 좋지만 파일 크기가 커져 앱의 반응성이 떨어질 수 있으며, 값이 낮으면 기계 학습의 품질이 떨어질 수 있습니다.", "image_preview_title": "미리보기 설정", @@ -131,7 +131,7 @@ "machine_learning_smart_search_description": "CLIP 임베딩으로 자연어를 사용하여 이미지 검색", "machine_learning_smart_search_enabled": "스마트 검색 활성화", "machine_learning_smart_search_enabled_description": "비활성화된 경우 스마트 검색을 위한 이미지 처리를 진행하지 않습니다.", - "machine_learning_url_description": "기계 학습 서버의 URL을 입럭합니다. 여러 개의 URL이 입력된 경우 모든 서버에 응답을 보낸 뒤 응답에 성공한 서버가 사용됩니다.", + "machine_learning_url_description": "기계 학습 서버의 URL을 입력합니다. URL이 여러 개인 경우 첫 번째 서버부터 마지막까지 성공적으로 응답할 때까지 한 번에 하나씩 순서대로 요청을 시도합니다. 응답하지 않는 서버는 다시 사용 가능할 때까지 일시적으로 제외됩니다.", "manage_concurrency": "동시성 관리", "manage_log_settings": "로그 설정 관리", "map_dark_style": "다크 스타일", @@ -291,7 +291,7 @@ "transcoding_max_b_frames": "최대 B 프레임", "transcoding_max_b_frames_description": "값이 높으면 압축 효율이 향상되지만 인코딩 속도가 저하됩니다. 오래된 기기의 하드웨어 가속과 호환되지 않을 수 있습니다. 0을 입력한 경우 B 프레임을 비활성화하며, -1을 입력한 경우 자동으로 설정합니다.", "transcoding_max_bitrate": "최대 비트레이트", - "transcoding_max_bitrate_description": "최대 비트레이트를 지정하면 품질이 일부 저하되지만 파일 크기가 예측 가능한 수준으로 일정하게 유지됩니다. 일반적으로 720p 기준 VP9 및 HEVC는 2600k, H.264는 4500k를 사용합니다. 0을 입력한 경우 비활성화됩니다.", + "transcoding_max_bitrate_description": "최대 비트레이트를 지정하면 품질이 일부 저하되지만 파일 크기가 예측 가능한 수준으로 일정하게 유지됩니다. 일반적으로 720p 기준 VP9 및 HEVC는 2600 kbit/s, H.264는 4500 kbit/s를 사용합니다. 0을 입력한 경우 비활성화됩니다.", "transcoding_max_keyframe_interval": "최대 키프레임 간격", "transcoding_max_keyframe_interval_description": "키프레임 사이 최대 프레임 거리를 설정합니다. 값이 낮으면 압축 효율이 저하되지만 검색 시간이 개선되고 빠른 움직임이 있는 장면에서 품질이 향상됩니다. 0을 입력한 경우 자동으로 설정합니다.", "transcoding_optimal_description": "목표 해상도보다 높은 동영상 또는 허용되지 않는 형식의 동영상", @@ -1323,4 +1323,4 @@ "yes": "네", "you_dont_have_any_shared_links": "생성한 공유 링크가 없습니다.", "zoom_image": "이미지 확대" -} +} \ No newline at end of file diff --git a/i18n/lt.json b/i18n/lt.json index 5e478a37a9..738dc7bc7f 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -20,7 +20,7 @@ "add_partner": "Pridėti partnerį", "add_path": "Pridėti kelią", "add_photos": "Pridėti nuotraukų", - "add_to": "Pridėti į ...", + "add_to": "Pridėti į…", "add_to_album": "Pridėti į albumą", "add_to_shared_album": "Pridėti į bendrinamą albumą", "add_url": "Pridėti URL", @@ -50,6 +50,7 @@ "create_job": "Sukurti darbą", "cron_expression": "Cron išraiška", "cron_expression_description": "Nustatyti skanavimo intervalą naudojant cron formatą. Norėdami gauti daugiau informacijos žiūrėkite Crontab Guru", + "cron_expression_presets": "Išankstiniai Cron nustatymai", "disable_login": "Išjungti prisijungimą", "duplicate_detection_job_description": "Vykdykite mašininį mokymąsi panašių vaizdų aptikimui. Priklauso nuo išmaniosios paieškos", "exclusion_pattern_description": "Išimčių šablonai leidžia nepaisyti failų ir aplankų skenuojant jūsų biblioteką. Tai yra naudinga, jei turite aplankų su failais, kurių nenorite importuoti, pavyzdžiui, RAW failai.", @@ -142,6 +143,8 @@ "map_settings": "Žemėlapis", "map_settings_description": "Tvarkyti žemėlapio parametrus", "map_style_description": "URL į style.json žemėlapio temą", + "memory_cleanup_job": "Atsiminimų valymas", + "memory_generate_job": "Atsiminimų generavimas", "metadata_extraction_job": "Metaduomenų nuskaitymas", "metadata_extraction_job_description": "Kiekvieno bibliotekos elemento metaduomenų nuskaitymas, tokių kaip GPS koordinatės, veidai ar rezoliucija", "metadata_faces_import_setting": "Įjungti veidų importą", @@ -904,6 +907,8 @@ "removed_from_archive": "Pašalinta iš archyvo", "removed_from_favorites": "Pašalinta iš mėgstamiausių", "removed_from_favorites_count": "{count, plural, one {# pašalintas} few {# pašalinti} other {# pašalinta}} iš mėgstamiausių", + "removed_memory": "Atsiminimas pašalintas", + "removed_photo_from_memory": "Nuotrauka ištrinta iš atsiminimų", "removed_tagged_assets": "Žyma pašalinta iš {count, plural, one {# elemento} other {# elementų}}", "rename": "Pervadinti", "repair": "Pataisyti", diff --git a/i18n/lv.json b/i18n/lv.json index 38b0bf6426..d962ae1bd1 100644 --- a/i18n/lv.json +++ b/i18n/lv.json @@ -20,7 +20,7 @@ "add_partner": "Pievienot partneri", "add_path": "Pievienot ceļu", "add_photos": "Pievienot fotoattēlus", - "add_to": "Pievienot ..", + "add_to": "Pievienot…", "add_to_album": "Pievienot albumam", "add_to_shared_album": "Pievienot koplietotam albumam", "add_url": "Pievienot URL", @@ -49,7 +49,7 @@ "external_library_management": "Ārējo bibliotēku pārvaldība", "face_detection": "Seju noteikšana", "image_format": "Formāts", - "image_format_description": "", + "image_format_description": "WebP veido mazākus failus nekā JPEG, taču to kodēšana ir lēnāka.", "image_prefer_embedded_preview": "", "image_prefer_embedded_preview_setting_description": "", "image_prefer_wide_gamut": "", @@ -299,12 +299,13 @@ "asset_offline": "", "asset_uploading": "Augšupielādē...", "assets": "aktīvi", - "authorized_devices": "", + "authorized_devices": "Autorizētās ierīces", "back": "Atpakaļ", "backward": "", "birthdate_saved": "Dzimšanas datums veiksmīgi saglabāts", "birthdate_set_description": "Dzimšanas datums tiek izmantots, lai aprēķinātu šīs personas vecumu fotogrāfijas uzņemšanas brīdī.", "blurred_background": "", + "bugs_and_feature_requests": "Kļūdas un funkciju pieprasījumi", "camera": "", "camera_brand": "", "camera_model": "", @@ -326,12 +327,13 @@ "clear": "Notīrīt", "clear_all": "Notīrīt visu", "clear_message": "", - "clear_value": "", + "clear_value": "Notīrīt vērtību", + "clockwise": "Pulksteņrādītāja virzienā", "close": "Aizvērt", "collapse": "Sakļaut", "collapse_all": "Sakļaut visu", "color": "Krāsa", - "color_theme": "", + "color_theme": "Krāsu tēma", "comment_deleted": "Komentārs dzēsts", "comment_options": "", "comments_are_disabled": "", diff --git a/i18n/mr.json b/i18n/mr.json index 0967ef424b..ec05d6c702 100644 --- a/i18n/mr.json +++ b/i18n/mr.json @@ -1 +1,62 @@ -{} +{ + "about": "विषयी", + "account": "खाते", + "account_settings": "खाते व्यवस्था", + "acknowledge": "मान्यता", + "action": "कृती", + "actions": "कृत्ये", + "active": "सक्रिय", + "activity": "गतिविधि", + "activity_changed": "गतिविधि {enabled, select, true {enabled} other {disabled}}", + "add": "जोडा", + "add_a_description": "वर्णन करा", + "add_a_location": "एक स्थळ टाका", + "add_a_name": "नाव टाका", + "add_a_title": "शीर्षक टाका", + "add_exclusion_pattern": "अपवाद नमुना जोडा", + "add_import_path": "आयात मार्ग टाका", + "add_location": "स्थळ टाका", + "add_more_users": "अधिक वापरकर्ते जोडा", + "add_partner": "भागीदार जोडा", + "add_path": "मार्ग टाका", + "add_photos": "छायाचित्रे जोडा", + "add_to": "त्या मध्ये जोडा…", + "add_to_album": "संग्रहात टाका", + "add_to_shared_album": "सामायिक संग्रहात टाका", + "add_url": "URL जोडा", + "added_to_archive": "संग्रहालयात जोडले", + "added_to_favorites": "आवडत्यात टाकले", + "added_to_favorites_count": "आवडत्यात {count, number} टाकले", + "admin": { + "add_exclusion_pattern_description": "अपवाद अनुकूलन जोडा. ** आणि ? या उपयोगात ग्लोबिंग समर्थित आहे. कोणत्याही \"Raw\" नावाच्या निर्देशिकेमधील सर्व खतावण्या दुर्लक्षीत करण्यासाठी \"/Raw/\" वापरा. \".tif\" या सामान्य पथावर समाप्त असलेल्या सर्व खतावण्या दुर्लक्षीत करण्यासाठी \"**/.tif\" वापरा. विशिष्ट पथ दुर्लक्ष करण्यासाठी \"/path/to/ignore/**\" वापरा.", + "asset_offline_description": "ही बाह्य संग्रहालय संसाधने डिस्कवर नाहीत आणि ट्रॅशमध्ये विस्थापित केली गेली आहेत. जर फाइल संग्रहालयामध्ये विस्थापित केली गेली आहे, तर नवीन संगत संसाधन किंव्हा रोजीनिशी मध्ये तपासा. हा संसाधन वापर करण्यासाठी कृपया निम्नलिखित खतावणी पथाला इम्मीच द्वारा वापरू शकतो याची तपासणी करा आणि तो संग्रहालय चाळा.", + "authentication_settings": "प्रमाणीकरण साधक", + "authentication_settings_description": "परवलीचा शब्द, OAuth आणि अन्य प्रमाणीकरण प्रबंधन करा", + "authentication_settings_disable_all": "तुम्हाला खात्री आहे की तुम्ही सर्व प्रवेश पद्धती बंद करू इच्छिता? प्रवेश पूर्णपणे बंद होइल!.", + "authentication_settings_reenable": "परत चालू करण्यासाठी Server Command वापरा.", + "background_task_job": "पृष्ठभूमि कार्य", + "backup_database": "माहिती संचयाची सुरक्षित प्रत करा", + "backup_database_enable_description": "माहिती संचयाच्या प्रतिलिपी चालू करा", + "backup_keep_last_amount": "पूर्वीच्या किती प्रतिलिपी ठेवायच्या", + "backup_settings": "प्रतिलिपी व्यवस्था", + "backup_settings_description": "माहिती संचय प्रतिलिपी व्यवस्थापन", + "check_all": "सर्व तपासा", + "cleared_jobs": "{job}: च्या कार्यवाह्या काढल्या", + "config_set_by_file": "संरचना सध्या संरचना खतावणीद्वारे निश्चित केली आहे", + "confirm_delete_library": "तुम्हाला नक्की हे {library} संग्रहालय हटवायचे आहे का?", + "confirm_delete_library_assets": "तुम्हाला नक्की हे संग्रहालय हटवायचे आहे का? इम्मीच मधून {count, plural, one {# contained asset} other {all # contained assets}} काढले जातील, आणि पूर्ववत करता येणार नाहीत. छायाचित्रे डिस्क वर राहतील.", + "confirm_email_below": "पुष्टी करण्या साठी, खाली \"{email}\" टंकलिखित करा", + "confirm_reprocess_all_faces": "तुम्हाला खात्री आहे का की तुम्हाला सर्व चेहऱ्यांवर पुन्हा प्रक्रिया करायची आहे? यामुळे नाव दिलेले लोकही साफ होतील.", + "confirm_user_password_reset": "तुम्हाला नक्की {user} चा परवलीचा शब्द बदलायचा आहे का?", + "create_job": "कार्य बनवा", + "cron_expression": "वेळापत्रक सूत्र", + "cron_expression_description": "चाळन्याचे वेळापत्रक क्रॉन पद्धती ने करा. अधिक माहिती साठी पहा: क्रॉन गुरु", + "cron_expression_presets": "पूर्वनिर्धारित वेळापत्रक सूत्रे", + "disable_login": "प्रवेशाधिकर वर्ज्य करा", + "duplicate_detection_job_description": "सारख्या छायाचित्रांचा शोध घेण्यासाठी यांत्रिकी प्रशिक्षण द्या. ही कार्यक्षमता चतुर शोधप्रणालीवर अवलंबून आहे", + "exclusion_pattern_description": "आपले संग्रहालय चाळताना अपवाद नमुने आपल्याला खतावण्या आणि र्निर्देशिकेला दुर्लक्षीत करू देतात. आपल्याकडे कच्च्या खतावण्या सारख्या आयात करू इच्छित नसलेल्या असंपादित (RAW) खतावण्या असलेल्या निर्देशिका असल्यास हे उपयुक्त आहे.", + "external_library_created_at": "बाह्य संग्रहालय ({date} रोजी बनवले गेले)", + "external_library_management": "बाह्य संग्रहालय व्यवस्थापन", + "face_detection": "मुख संशोधन" + } +} diff --git a/i18n/ms.json b/i18n/ms.json index 9916ca62a0..7da863750d 100644 --- a/i18n/ms.json +++ b/i18n/ms.json @@ -299,7 +299,7 @@ "transcoding_max_b_frames": "Bingkai-B maksimum", "transcoding_max_b_frames_description": "Nilai yang lebih tinggi meningkatkan kecekapan mampatan, tetapi memperlahankan pengekodan. Mungkin tidak serasi dengan pecutan perkakasan pada peranti lama. 0 melumpuhkan bingkai B, manakala -1 menetapkan nilai ini secara automatik.", "transcoding_max_bitrate": "Kadar bit maksimum", - "transcoding_max_bitrate_description": "Menetapkan kadar bit maksima boleh menjadikan saiz fail lebih boleh diramal dengan kekurangan yang kecil kepada kualiti. Pada 720p, nilai biasa ialah 2600k untuk VP9 atau HEVC, atau 4500k untuk H.264. Dilumpuhkan jika ditetapkan kepada 0.", + "transcoding_max_bitrate_description": "Menetapkan kadar bit maksima boleh menjadikan saiz fail lebih boleh diramal dengan kekurangan yang kecil kepada kualiti. Pada 720p, nilai biasa ialah 2600 kbit/s untuk VP9 atau HEVC, atau 4500 kbit/s untuk H.264. Dilumpuhkan jika ditetapkan kepada 0.", "transcoding_max_keyframe_interval": "Selangan keyframe maksimum", "transcoding_max_keyframe_interval_description": "Menetapkan jarak bingkai maksimum antara keyframes. Nilai yang lebih rendah memburukkan kecekapan mampatan, tetapi menambah baik masa carian dan mungkin meningkatkan kualiti dalam adegan dengan pergerakan pantas. 0 menetapkan nilai ini secara automatik.", "transcoding_optimal_description": "Video yang lebih tinggi daripada resolusi sasaran atau tidak dalam format yang diterima", @@ -372,4 +372,4 @@ "yes": "Ya", "you_dont_have_any_shared_links": "Anda tidak mempunyai apa-apa pautan yang dikongsi", "zoom_image": "Zum Gambar" -} +} \ No newline at end of file diff --git a/i18n/nb_NO.json b/i18n/nb_NO.json index b29c8c1db6..78f7941760 100644 --- a/i18n/nb_NO.json +++ b/i18n/nb_NO.json @@ -41,6 +41,7 @@ "backup_settings": "Backupinnstillinger", "backup_settings_description": "Håndter innstillinger for databasebackup", "check_all": "Merk Alle", + "cleanup": "Opprydding", "cleared_jobs": "Ryddet opp jobber for: {job}", "config_set_by_file": "Konfigurasjonen er for øyeblikket satt av en konfigurasjonsfil", "confirm_delete_library": "Er du sikker på at du vil slette biblioteket {library}?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Aktiver periodisk skanning av bibliotek", "library_settings": "Eksternt bibliotek", "library_settings_description": "Administrer innstillinger for eksterne bibliotek", - "library_tasks_description": "Utfør bibliotekoppgaver", + "library_tasks_description": "Skann eksterne biblioteker for nye og/eller endrede ressurser", "library_watching_enable_description": "Overvåk eksterne bibliotek for filendringer", "library_watching_settings": "Overvåkning av bibliotek (EKSPERIMENTELL)", "library_watching_settings_description": "Se automatisk etter endrede filer", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Søk etter bilder semantisk ved å bruke CLIP-embeddings", "machine_learning_smart_search_enabled": "Aktiver smart søk", "machine_learning_smart_search_enabled_description": "Hvis deaktivert, vil bilder ikke bli enkodet for smart søk.", - "machine_learning_url_description": "URL til maskinlærings-serveren. Hvis mer enn en URL er lagt inn, hver server vill bli forsøkt en om gangen frem til en svarer suksessfullt, i rekkefølge fra først til sist.", + "machine_learning_url_description": "URL til maskinlærings-serveren. Hvis mer enn en URL er lagt inn, hver server vill bli forsøkt en om gangen frem til en svarer suksessfullt, i rekkefølge fra først til sist. Servere som ikke svarer vil midlertidig bli oversett frem til dem svarer igjen.", "manage_concurrency": "Administrer samtidighet", "manage_log_settings": "Administrer logginnstillinger", "map_dark_style": "Mørk stil", @@ -147,6 +148,8 @@ "map_settings": "Innstillinger for kart og GPS", "map_settings_description": "Administrer kartinnstillinger", "map_style_description": "URL til et style.json-karttema", + "memory_cleanup_job": "Minneopprydding", + "memory_generate_job": "Minnegenerering", "metadata_extraction_job": "Hent metadata", "metadata_extraction_job_description": "Hent metadatainformasjon fra hver fil, for eksempel GPS-posisjon og oppløsning", "metadata_faces_import_setting": "Aktiver ansikts importering", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Aktiver hasjverifisering. Ikke deaktiver dette med mindre du er sikker på konsekvensene", "storage_template_migration": "Lagringsmal migrering", "storage_template_migration_description": "Bruk gjeldende {template} på tidligere opplastede bilder", - "storage_template_migration_info": "Malendringer vil kun gjelde nye ressurser. For å anvende malen på tidligere opplastede ressurser, kjør {job}.", + "storage_template_migration_info": "Lagringsmalen vil endre filtypen til små bokstaver. Malendringer vil kun gjelde nye ressurser. For å anvende malen på tidligere opplastede ressurser, kjør {job}.", "storage_template_migration_job": "Migreringsjobb for lagringsmal", "storage_template_more_details": "For mer informasjon om denne funksjonen, se lagringsmalen og dens konsekvenser", "storage_template_onboarding_description": "Når aktivert, vil denne funksjonen automatisk organisere filer basert på en brukerdefinert mal. På grunn av stabilitetsproblemer er funksjonen deaktivert som standard. For mer informasjon, se documentation.", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Maksimalt antall B-frames", "transcoding_max_b_frames_description": "Høyere verdier forbedrer komprimeringseffektiviteten, men senker ned kodingen. Kan være inkompatibelt med maskinvareakselerasjon på eldre enheter. 0 deaktiverer B-rammer, mens -1 setter verdien automatisk.", "transcoding_max_bitrate": "Maksimal bithastighet", - "transcoding_max_bitrate_description": "Å sette en maksimal bithastighet kan gjøre filstørrelsene mer forutsigbare med en liten kostnad for kvaliteten. For 720p er typiske verdier 2600k for VP9 eller HEVC, eller 4500k for H.264. Deaktivert hvis satt til 0.", + "transcoding_max_bitrate_description": "Å sette en maksimal bithastighet kan gjøre filstørrelsene mer forutsigbare med en liten kostnad for kvaliteten. For 720p er typiske verdier 2600 kbit/s for VP9 eller HEVC, eller 4500 kbit/s for H.264. Deaktivert hvis satt til 0.", "transcoding_max_keyframe_interval": "Maksimal referansebilde intervall", "transcoding_max_keyframe_interval_description": "Setter maksimalt antall bilder mellom referansebilder. Lavere verdier reduserer kompresjonseffektiviteten, men forbedrer søketider og kan forbedre kvaliteten i scener med rask bevegelse. 0 setter verdien automatisk.", "transcoding_optimal_description": "Videoer som har høyere oppløsning enn målopppløsningen eller som ikke er i et akseptert format", @@ -391,11 +394,12 @@ "allow_edits": "Tillat redigering", "allow_public_user_to_download": "Tillat uautentiserte brukere å laste ned", "allow_public_user_to_upload": "Tillat uautentiserte brukere å laste opp", + "alt_text_qr_code": "QR-kodebilde", "anti_clockwise": "Mot klokken", "api_key": "API Nøkkel", "api_key_description": "Denne verdien vil vises kun én gang. Pass på å kopiere den før du lukker vinduet.", - "api_key_empty": "API Key-navnet bør ikke være tomt", - "api_keys": "API Nøkkler", + "api_key_empty": "API-nøkkelnavnet bør ikke være tomt", + "api_keys": "API-nøkler", "app_settings": "Appinstillinger", "appears_in": "Vises i", "archive": "Arkiver", @@ -481,6 +485,7 @@ "comments_are_disabled": "Kommentarer er deaktivert", "confirm": "Bekreft", "confirm_admin_password": "Bekreft administratorpassord", + "confirm_delete_face": "Er du sikker på at du vil slette {name} sitt ansikt fra ativia?", "confirm_delete_shared_link": "Er du sikker på at du vil slette denne delte lenken?", "confirm_keep_this_delete_others": "Alle andre ressurser i denne stabelen vil bli slettet bortsett fra denne ressursen. Er du sikker på at du vil fortsette?", "confirm_password": "Bekreft passord", @@ -533,6 +538,7 @@ "delete_album": "Slett album", "delete_api_key_prompt": "Er du sikker på at du vil slette denne API-nøkkelen?", "delete_duplicates_confirmation": "Er du sikker på at du vil slette disse duplikatene permanent?", + "delete_face": "Slett ansik", "delete_key": "Slett nøkkel", "delete_library": "Slett bibliotek", "delete_link": "Slett lenke", @@ -595,11 +601,12 @@ "editor_crop_tool_h2_rotation": "Rotasjon", "email": "E-postadresse", "empty_trash": "Tøm papirkurv", - "empty_trash_confirmation": "Er du sikker på at du vil tømme søppelbøtte ? Dette vil slette alle filene i søppelbøtta permanent fra Immich.\nDu kan ikke angre denne handlingen!", + "empty_trash_confirmation": "Er du sikker på at du vil tømme søppelbøtta? Dette vil slette alle filene i søppelbøtta permanent fra Immich.\nDu kan ikke angre denne handlingen!", "enable": "Aktivere", "enabled": "Aktivert", "end_date": "Slutt dato", "error": "Feil", + "error_delete_face": "Feil ved sletting av ansikt fra aktivia", "error_loading_image": "Feil ved lasting av bilde", "error_title": "Feil - Noe gikk galt", "errors": { @@ -766,8 +773,10 @@ "go_to_folder": "Gå til mappe", "go_to_search": "Gå til søk", "group_albums_by": "Grupper album etter...", + "group_country": "Grupper etter land", "group_no": "Ingen gruppering", "group_owner": "Grupper etter eiere", + "group_places_by": "Grupper plasser etter...", "group_year": "Grupper etter år", "has_quota": "Har kvote", "hi_user": "Hei {name} ({email})", @@ -800,6 +809,7 @@ "include_shared_albums": "Inkluder delte album", "include_shared_partner_assets": "Inkluder delte partnerfiler", "individual_share": "Individuell deling", + "individual_shares": "Individuelle delinger", "info": "Info", "interval": { "day_at_onepm": "Hver dag klokken 13:00", @@ -881,6 +891,7 @@ "month": "Måned", "more": "Mer", "moved_to_trash": "Flyttet til papirkurven", + "mute_memories": "Demp minner", "my_albums": "Mine album", "name": "Navn", "name_or_nickname": "Navn eller kallenavn", @@ -985,6 +996,7 @@ "pick_a_location": "Velg et sted", "place": "Sted", "places": "Plasseringer", + "places_count": "{count, plural, one {{count, number} Place} other {{count, number} Places}}", "play": "Spill av", "play_memories": "Spill av minner", "play_motion_photo": "Spill av bevegelsesbilde", @@ -1072,6 +1084,8 @@ "removed_from_archive": "Fjernet fra arkivet", "removed_from_favorites": "Fjernet fra favoritter", "removed_from_favorites_count": "{count, plural, other {Removed #}} fra favoritter", + "removed_memory": "Slettet minne", + "removed_photo_from_memory": "Slettet bilde fra minne", "removed_tagged_assets": "Fjern tag fra {count, plural, one {# asset} other {# assets}}", "rename": "Gi nytt navn", "repair": "Reparer", @@ -1080,6 +1094,7 @@ "repository": "Depot", "require_password": "Krev passord", "require_user_to_change_password_on_first_login": "Krev at brukeren endrer passord ved første pålogging", + "rescan": "Skann på nytt", "reset": "Tilbakestill", "reset_password": "Tilbakestill passord", "reset_people_visibility": "Tilbakestill personsynlighet", @@ -1108,6 +1123,8 @@ "search": "Søk", "search_albums": "Søk i album", "search_by_context": "Søk etter kontekst", + "search_by_description": "Søk etter beskrivelse", + "search_by_description_example": "Turdag i Sapa", "search_by_filename": "Søk etter filnavn og filtype", "search_by_filename_example": "f.eks. IMG_1234.JPG eller PNG", "search_camera_make": "Søk etter kameramerke...", @@ -1121,6 +1138,7 @@ "search_options": "Søke alternativer", "search_people": "Søk personer", "search_places": "Søk steder", + "search_rating": "Søk etter vurdering...", "search_settings": "Søke instillinger", "search_state": "Søk etter stat...", "search_tags": "Søk tags...", @@ -1167,6 +1185,7 @@ "shared_from_partner": "Bilder fra {partner}", "shared_link_options": "Alternativer for delte lenke", "shared_links": "Delte linker", + "shared_links_description": "Del bilder og videoer med lenke", "shared_photos_and_videos_count": "{assetCount, plural, other {# delte bilder og videoer.}}", "shared_with_partner": "Delt med {partner}", "sharing": "Deling", @@ -1189,6 +1208,7 @@ "show_person_options": "Vis personalternativer", "show_progress_bar": "Vis fremdriftslinje", "show_search_options": "Vis søkealternativer", + "show_shared_links": "Vis delte lenker", "show_slideshow_transition": "Vis overgang til lysbildefremvisning", "show_supporter_badge": "Supportermerke", "show_supporter_badge_description": "Vis et supportermerke", @@ -1242,6 +1262,7 @@ "tag_created": "Lag merke: {tag}", "tag_feature_description": "Bla gjennom bilder og videoer gruppert etter logiske merke-emner", "tag_not_found_question": "Finner du ikke en merke? Opprett en nytt merke.", + "tag_people": "Tag Folk", "tag_updated": "Oppdater merke: {tag}", "tagged_assets": "Merket {count, plural, one {# asset} other {# assets}}", "tags": "Merker", @@ -1276,11 +1297,13 @@ "unfavorite": "Fjern favoritt", "unhide_person": "Vis person", "unknown": "Ukjent", + "unknown_country": "Ukjent Land", "unknown_year": "Ukjent År", "unlimited": "Ubegrenset", "unlink_motion_video": "Koble fra bevegelsesvideo", "unlink_oauth": "Fjern kobling til OAuth", "unlinked_oauth_account": "Koblet fra OAuth-konto", + "unmute_memories": "Opphev demping av minner", "unnamed_album": "Navnløst album", "unnamed_album_delete_confirmation": "Er du sikker på at du vil slette dette albumet?", "unnamed_share": "Deling uten navn", @@ -1334,6 +1357,7 @@ "view_all": "Vis alle", "view_all_users": "Vis alle brukere", "view_in_timeline": "Vis i tidslinje", + "view_link": "Vis lenke", "view_links": "Vis lenker", "view_name": "Vis", "view_next_asset": "Vis neste fil", @@ -1350,4 +1374,4 @@ "yes": "Ja", "you_dont_have_any_shared_links": "Du har ingen delte lenker", "zoom_image": "Zoom Bilde" -} +} \ No newline at end of file diff --git a/i18n/nl.json b/i18n/nl.json index 43e205b52a..55de209224 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -41,6 +41,7 @@ "backup_settings": "Back-up instellingen", "backup_settings_description": "Database back-up instellingen beheren", "check_all": "Controleer het logboek", + "cleanup": "Opruimen", "cleared_jobs": "Taken gewist voor: {job}", "config_set_by_file": "Instellingen worden momenteel beheerd door een configuratiebestand", "confirm_delete_library": "Weet je zeker dat je de bibliotheek {library} wilt verwijderen?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Periodieke bibliotheekscan aanzetten", "library_settings": "Externe bibliotheek", "library_settings_description": "Externe bibliotheekinstellingen beheren", - "library_tasks_description": "Voer bibliotheek taken uit", + "library_tasks_description": "Scan externe bibliotheken op nieuwe en/of gewijzigde media", "library_watching_enable_description": "Externe bibliotheken monitoren op bestandswijzigingen", "library_watching_settings": "Bibliotheek monitoren (EXPERIMENTEEL)", "library_watching_settings_description": "Automatisch gewijzigde bestanden bijhouden", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Semantisch zoeken naar afbeeldingen met CLIP-embeddings", "machine_learning_smart_search_enabled": "Slim zoeken inschakelen", "machine_learning_smart_search_enabled_description": "Indien uitgeschakeld, worden afbeeldingen niet verwerkt voor slim zoeken.", - "machine_learning_url_description": "De URL van de machine learning server. Als er meer dan één URL is opgegeven, wordt elke server geprobeerd totdat er een succesvol reageert, op volgorde van eerste tot laatste.", + "machine_learning_url_description": "De URL van de machine learning server. Als er meer dan één URL is opgegeven, wordt elke server geprobeerd totdat er een succesvol reageert, op volgorde van eerste tot laatste. Servers die geen reactie geven zullen tijdelijk genegeerd worden tot zij terug online komen.", "manage_concurrency": "Beheer gelijktijdigheid", "manage_log_settings": "Beheer logboekinstellingen", "map_dark_style": "Donkere stijl", @@ -147,6 +148,8 @@ "map_settings": "Kaart", "map_settings_description": "Beheer kaartinstellingen", "map_style_description": "URL naar een style.json kaartthema", + "memory_cleanup_job": "Geheugen opschonen", + "memory_generate_job": "Geheugen genereren", "metadata_extraction_job": "Metadata ophalen", "metadata_extraction_job_description": "Metadata ophalen van iedere asset, zoals GPS, gezichten en resolutie", "metadata_faces_import_setting": "Gezichten importeren inschakelen", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Zet hashverificatie aan, schakel dit niet uit tenzij je zeker bent van de implicaties", "storage_template_migration": "Opslagtemplate migratie", "storage_template_migration_description": "Pas de huidige {template} toe op eerder geüploade assets", - "storage_template_migration_info": "Wijzigingen in de template worden alleen toegepast op nieuwe assets. Om de template met terugwerkende kracht toe te passen op eerder geüploade assets, voer de {job} uit.", + "storage_template_migration_info": "Wijzigingen in het opslag template worden alleen toegepast op nieuwe assets. Om de template met terugwerkende kracht toe te passen op eerder geüploade assets, voer je de {job} uit.", "storage_template_migration_job": "Opslagtemplate migratietaak", "storage_template_more_details": "Voor meer details over deze functie, bekijk de Opslagstemplate en de implicaties daarvan", "storage_template_onboarding_description": "Wanneer ingeschakeld, zal deze functie bestanden automatisch organiseren gebaseerd op een gebruiker-definieerd template. Gezien de stabiliteitsproblemen is de functie standaard uitgeschakeld. Voor meer informatie, bekijk de documentatie.", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Maximum B-Frames", "transcoding_max_b_frames_description": "Hogere waarden verbeteren de compressie efficiëntie, maar vertragen de codering. Is mogelijk niet compatibel met hardwareversnelling op oudere apparaten. 0 schakelt B-frames uit, terwijl -1 deze waarde automatisch instelt.", "transcoding_max_bitrate": "Maximum bitrate", - "transcoding_max_bitrate_description": "Het instellen van een maximale bitrate kan de bestandsgrootte voorspelbaarder maken, tegen geringe kosten voor de kwaliteit. Bij 720p zijn de typische waarden 2600k voor VP9 of HEVC, of 4500k voor H.264. Uitgeschakeld indien ingesteld op 0.", + "transcoding_max_bitrate_description": "Het instellen van een maximale bitrate kan de bestandsgrootte voorspelbaarder maken, tegen geringe kosten voor de kwaliteit. Bij 720p zijn de typische waarden 2600 kbit/s voor VP9 of HEVC, of 4500 kbit/s voor H.264. Uitgeschakeld indien ingesteld op 0.", "transcoding_max_keyframe_interval": "Maximum keyframe interval", "transcoding_max_keyframe_interval_description": "Stelt de maximale frameafstand tussen keyframes in. Lagere waarden verslechteren de compressie efficiëntie, maar verbeteren de zoektijden en kunnen de kwaliteit verbeteren in scènes met snelle bewegingen. 0 stelt deze waarde automatisch in.", "transcoding_optimal_description": "Video's met een hogere resolutie dan de doelresolutie of niet in een geaccepteerd formaat", @@ -391,11 +394,12 @@ "allow_edits": "Bewerkingen toestaan", "allow_public_user_to_download": "Sta openbare gebruiker toe om te downloaden", "allow_public_user_to_upload": "Sta openbare gebruiker toe om te uploaden", + "alt_text_qr_code": "QR-codeafbeelding", "anti_clockwise": "Linksom", - "api_key": "API sleutel", + "api_key": "API key", "api_key_description": "Deze waarde wordt slechts één keer getoond. Zorg ervoor dat je deze kopieert voordat je het venster sluit.", - "api_key_empty": "De naam van uw API sleutel mag niet leeg zijn", - "api_keys": "API sleutels", + "api_key_empty": "De naam van uw API key mag niet leeg zijn", + "api_keys": "API keys", "app_settings": "App instellingen", "appears_in": "Komt voor in", "archive": "Archief", @@ -442,7 +446,7 @@ "bulk_delete_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# duplicate asset} other {# duplicate assets}} in bulk wilt verwijderen? Dit zal de grootste asset van elke groep behouden en alle andere duplicaten permanent verwijderen. Je kunt deze actie niet ongedaan maken!", "bulk_keep_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# duplicate asset} other {# duplicate assets}} wilt behouden? Dit zal alle groepen met duplicaten oplossen zonder iets te verwijderen.", "bulk_trash_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# duplicate asset} other {# duplicate assets}} in bulk naar de prullenbak wilt verplaatsen? Dit zal de grootste asset van elke groep behouden en alle andere duplicaten naar de prullenbak verplaatsen.", - "buy": "Koop Immich", + "buy": "Immich kopen", "camera": "Camera", "camera_brand": "Cameramerk", "camera_model": "Cameramodel", @@ -452,9 +456,9 @@ "cannot_undo_this_action": "Je kunt deze actie niet ongedaan maken!", "cannot_update_the_description": "Kan de beschrijving niet bijwerken", "change_date": "Wijzig datum", - "change_expiration_time": "Wijzig verlooptijd", - "change_location": "Wijzig locatie", - "change_name": "Wijzig naam", + "change_expiration_time": "Verlooptijd wijzigen", + "change_location": "Locatie wijzigen", + "change_name": "Naam wijzigen", "change_name_successfully": "Naam succesvol gewijzigd", "change_password": "Wijzig wachtwoord", "change_password_description": "Dit is de eerste keer dat je inlogt op het systeem of er is een verzoek gedaan om je wachtwoord te wijzigen. Voer hieronder het nieuwe wachtwoord in.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Opmerkingen zijn uitgeschakeld", "confirm": "Bevestigen", "confirm_admin_password": "Bevestig beheerder wachtwoord", + "confirm_delete_face": "Weet je zeker dat je {name} gezicht wilt verwijderen uit de asset?", "confirm_delete_shared_link": "Weet je zeker dat je deze gedeelde link wilt verwijderen?", "confirm_keep_this_delete_others": "Alle andere assets in de stack worden verwijderd, behalve deze. Weet je zeker dat je wilt doorgaan?", "confirm_password": "Bevestig wachtwoord", @@ -531,9 +536,10 @@ "default_locale_description": "Formatteer datums en getallen op basis van de landinstellingen van je browser", "delete": "Verwijderen", "delete_album": "Album verwijderen", - "delete_api_key_prompt": "Weet je zeker dat je deze API sleutel wilt verwijderen?", + "delete_api_key_prompt": "Weet je zeker dat je deze API key wilt verwijderen?", "delete_duplicates_confirmation": "Weet je zeker dat je deze duplicaten permanent wilt verwijderen?", - "delete_key": "Verwijder sleutel", + "delete_face": "Gezicht verwijderen", + "delete_key": "Verwijder key", "delete_library": "Verwijder bibliotheek", "delete_link": "Verwijder link", "delete_others": "Andere verwijderen", @@ -579,7 +585,7 @@ "edit_faces": "Gezichten bewerken", "edit_import_path": "Import-pad bewerken", "edit_import_paths": "Import-paden bewerken", - "edit_key": "Sleutel bewerken", + "edit_key": "Key bewerken", "edit_link": "Link bewerken", "edit_location": "Locatie bewerken", "edit_name": "Naam bewerken", @@ -600,6 +606,7 @@ "enabled": "Ingeschakeld", "end_date": "Einddatum", "error": "Fout", + "error_delete_face": "Fout bij verwijderen gezicht uit asset", "error_loading_image": "Fout bij laden afbeelding", "error_title": "Fout - Er is iets misgegaan", "errors": { @@ -631,7 +638,7 @@ "failed_to_load_asset": "Kan asset niet laden", "failed_to_load_assets": "Kan assets niet laden", "failed_to_load_people": "Kan mensen niet laden", - "failed_to_remove_product_key": "Er is een fout opgetreden bij het verwijderen van de product sleutel", + "failed_to_remove_product_key": "Er is een fout opgetreden bij het verwijderen van de licentiesleutel", "failed_to_stack_assets": "Fout bij stapelen van assets", "failed_to_unstack_assets": "Fout bij ontstapelen van assets", "import_path_already_exists": "Dit import-pad bestaat al.", @@ -660,7 +667,7 @@ "unable_to_connect_to_server": "Kan geen verbinding maken met server", "unable_to_copy_to_clipboard": "Kan niet naar klembord kopiëren, zorg ervoor dat je de pagina via https opent", "unable_to_create_admin_account": "Kan beheerdersaccount niet aanmaken", - "unable_to_create_api_key": "Kan geen nieuwe API sleutel aanmaken", + "unable_to_create_api_key": "Kan geen nieuwe API key aanmaken", "unable_to_create_library": "Kan bibliotheek niet aanmaken", "unable_to_create_user": "Kan geen gebruiker aanmaken", "unable_to_delete_album": "Kan album niet verwijderen", @@ -693,7 +700,7 @@ "unable_to_reassign_assets_new_person": "Kan assets niet opnieuw toewijzen aan een nieuw persoon", "unable_to_refresh_user": "Kan gebruiker niet vernieuwen", "unable_to_remove_album_users": "Kan gebruiker niet van album verwijderen", - "unable_to_remove_api_key": "Kan API sleutel niet verwijderen", + "unable_to_remove_api_key": "Kan API key niet verwijderen", "unable_to_remove_assets_from_shared_link": "Kan assets niet verwijderen uit gedeelde link", "unable_to_remove_deleted_assets": "Kan offline bestanden niet verwijderen", "unable_to_remove_library": "Kan bibliotheek niet verwijderen", @@ -706,7 +713,7 @@ "unable_to_restore_trash": "Kan niet herstellen uit prullenbak", "unable_to_restore_user": "Kan gebruiker niet herstellen", "unable_to_save_album": "Kan album niet opslaan", - "unable_to_save_api_key": "Kan API sleutel niet opslaan", + "unable_to_save_api_key": "Kan API key niet opslaan", "unable_to_save_date_of_birth": "Kan geboortedatum niet opslaan", "unable_to_save_name": "Kan naam niet opslaan", "unable_to_save_profile": "Kan profiel niet opslaan", @@ -857,7 +864,7 @@ "manage_sharing_with_partners": "Beheer delen met partners", "manage_the_app_settings": "Beheer de appinstellingen", "manage_your_account": "Beheer je account", - "manage_your_api_keys": "Beheer je API sleutels", + "manage_your_api_keys": "Beheer je API keys", "manage_your_devices": "Beheer je ingelogde apparaten", "manage_your_oauth_connection": "Beheer je OAuth koppeling", "map": "Kaart", @@ -884,12 +891,13 @@ "month": "Maand", "more": "Meer", "moved_to_trash": "Naar de prullenbak verplaatst", + "mute_memories": "Herrinneringen dempen", "my_albums": "Mijn albums", "name": "Naam", "name_or_nickname": "Naam of gebruikersnaam", "never": "Nooit", "new_album": "Nieuw album", - "new_api_key": "Nieuwe API sleutel", + "new_api_key": "Nieuwe API key", "new_password": "Nieuw wachtwoord", "new_person": "Nieuw persoon", "new_user_created": "Nieuwe gebruiker aangemaakt", @@ -1008,19 +1016,19 @@ "purchase_account_info": "Supporter", "purchase_activated_subtitle": "Bedankt voor het ondersteunen van Immich en open-source software", "purchase_activated_time": "Geactiveerd op {date, date}", - "purchase_activated_title": "Je sleutel is succesvol geactiveerd", + "purchase_activated_title": "Je licentiesleutel is succesvol geactiveerd", "purchase_button_activate": "Activeren", "purchase_button_buy": "Kopen", "purchase_button_buy_immich": "Koop Immich", "purchase_button_never_show_again": "Nooit meer tonen", "purchase_button_reminder": "Herinner mij over 30 dagen", - "purchase_button_remove_key": "Sleutel verwijderen", + "purchase_button_remove_key": "Licentiesleutel verwijderen", "purchase_button_select": "Selecteren", - "purchase_failed_activation": "Activeren mislukt! Controleer je e-mail voor de juiste productsleutel!", + "purchase_failed_activation": "Activeren mislukt! Controleer je e-mail voor de juiste licentiesleutel!", "purchase_individual_description_1": "Voor een gebruiker", "purchase_individual_description_2": "Supporter badge", "purchase_individual_title": "Gebruiker", - "purchase_input_suggestion": "Heb je een productsleutel? Voer de sleutel hieronder in", + "purchase_input_suggestion": "Heb je een licentiesleutel? Voer deze hieronder in", "purchase_license_subtitle": "Koop Immich om de verdere ontwikkeling van de service te ondersteunen", "purchase_lifetime_description": "Levenslange aankoop", "purchase_option_title": "AANKOOP MOGELIJKHEDEN", @@ -1029,14 +1037,14 @@ "purchase_panel_title": "Steun het project", "purchase_per_server": "Per server", "purchase_per_user": "Per gebruiker", - "purchase_remove_product_key": "Verwijder product sleutel", - "purchase_remove_product_key_prompt": "Weet je zeker dat je de product sleutel wilt verwijderen?", - "purchase_remove_server_product_key": "Verwijder server product sleutel", - "purchase_remove_server_product_key_prompt": "Weet je zeker dat je de server product sleutel wilt verwijderen?", + "purchase_remove_product_key": "Verwijder licentiesleutel", + "purchase_remove_product_key_prompt": "Weet je zeker dat je de licentiesleutel wilt verwijderen?", + "purchase_remove_server_product_key": "Verwijder server licentiesleutel", + "purchase_remove_server_product_key_prompt": "Weet je zeker dat je de server licentiesleutel wilt verwijderen?", "purchase_server_description_1": "Voor de volledige server", "purchase_server_description_2": "Supporter badge", "purchase_server_title": "Server", - "purchase_settings_server_activated": "De productcode van de server wordt beheerd door de beheerder", + "purchase_settings_server_activated": "De licentiesleutel van de server wordt beheerd door de beheerder", "rating": "Ster waardering", "rating_clear": "Waardering verwijderen", "rating_count": "{count, plural, one {# ster} other {# sterren}}", @@ -1076,6 +1084,8 @@ "removed_from_archive": "Verwijderd uit archief", "removed_from_favorites": "Verwijderd uit favorieten", "removed_from_favorites_count": "{count, plural, other {# verwijderd}} uit favorieten", + "removed_memory": "Geheugen verwijderd", + "removed_photo_from_memory": "Foto verwijderd uit geheugen", "removed_tagged_assets": "Tag verwijderd van {count, plural, one {# asset} other {# assets}}", "rename": "Hernoemen", "repair": "Repareren", @@ -1084,6 +1094,7 @@ "repository": "Repository", "require_password": "Wachtwoord vereisen", "require_user_to_change_password_on_first_login": "Vereisen dat de gebruiker het wachtwoord wijzigt bij de eerste keer inloggen", + "rescan": "Herscannen", "reset": "Resetten", "reset_password": "Wachtwoord resetten", "reset_people_visibility": "Zichtbaarheid mensen resetten", @@ -1127,6 +1138,7 @@ "search_options": "Zoekopties", "search_people": "Zoek mensen", "search_places": "Zoek plaatsen", + "search_rating": "Zoeken op beoordeling...", "search_settings": "Zoek instellingen", "search_state": "Zoek staat...", "search_tags": "Tags zoeken...", @@ -1250,6 +1262,7 @@ "tag_created": "Tag aangemaakt: {tag}", "tag_feature_description": "Bladeren door foto's en video's gegroepeerd op tags", "tag_not_found_question": "Kun je een tag niet vinden? Maak een nieuwe tag.", + "tag_people": "Mensen taggen", "tag_updated": "Tag bijgewerkt: {tag}", "tagged_assets": "{count, plural, one {# asset} other {# assets}} getagd", "tags": "Tags", @@ -1290,6 +1303,7 @@ "unlink_motion_video": "Maak bewegende video los", "unlink_oauth": "Ontkoppel OAuth", "unlinked_oauth_account": "OAuth account ontkoppeld", + "unmute_memories": "Dempen van herrinneringen opheffen", "unnamed_album": "Naamloos album", "unnamed_album_delete_confirmation": "Weet je zeker dat je dit album wilt verwijderen?", "unnamed_share": "Naamloze deellink", @@ -1343,6 +1357,7 @@ "view_all": "Bekijk alle", "view_all_users": "Bekijk alle gebruikers", "view_in_timeline": "Bekijk in tijdlijn", + "view_link": "Bekijk link", "view_links": "Links bekijken", "view_name": "Bekijken", "view_next_asset": "Bekijk volgende asset", @@ -1359,4 +1374,4 @@ "yes": "Ja", "you_dont_have_any_shared_links": "Je hebt geen gedeelde links", "zoom_image": "Inzoomen" -} +} \ No newline at end of file diff --git a/i18n/nn.json b/i18n/nn.json index 4cda30dce2..6de0f3401b 100644 --- a/i18n/nn.json +++ b/i18n/nn.json @@ -120,8 +120,20 @@ "machine_learning_max_detection_distance": "Maksimal oppdagingsverdi", "machine_learning_max_detection_distance_description": "Den største skilnaden mellom to bilete for å rekne dei som duplikat, frå 0.001-0.1. Større verdiar finn fleire duplikat, men kan gje falske treff.", "machine_learning_max_recognition_distance": "Maksimal attkjenningsverdi", + "machine_learning_min_detection_score": "Minimum deteksjonsresultat", + "machine_learning_min_detection_score_description": "Minimum tillitspoeng for at eit ansikt skal bli oppdaga, på ein skala frå 0-1. Lågare verdiar vil oppdaga fleire ansikt, men kan føre til falske positive", + "machine_learning_min_recognized_faces": "Minimum gjenkjende ansikt", + "machine_learning_settings": "Innstillingar for maskinlæring", + "machine_learning_settings_description": "Administrer maskinlæringsfunksjonar og innstillingar", "machine_learning_smart_search": "Smart Søk", + "machine_learning_smart_search_enabled": "Aktiver smart søk", + "machine_learning_smart_search_enabled_description": "Hvis deaktivert, vil bilete ikkje bli enkoda for smart søk.", + "manage_concurrency": "Administrer samtidigheit", + "manage_log_settings": "Administrer logginnstillingar", "map_dark_style": "Mørk modus", + "map_enable_description": "Aktiver kartfunksjonar", + "map_gps_settings": "Kart og GPS innstillingar", + "map_gps_settings_description": "Administrer innstillingar for kart og GPS (Reversert geokoding)", "map_light_style": "Lys modus", "map_settings": "Kart", "metadata_extraction_job": "Hent ut metadata", @@ -365,19 +377,38 @@ "usage": "Bruk", "user": "Brukar", "user_purchase_settings": "Kjøp", + "user_usage_detail": "Detaljar av brukars forbruk", + "user_usage_stats": "Vis kontobruksstatistikk", + "user_usage_stats_description": "Vis kontobruksstatistikk", "username": "Brukarnamn", "users": "Brukarar", "utilities": "Verktøy", "validate": "Validere", "variables": "Variablar", "version": "Versjon", + "version_announcement_closing": "Din ven, Alex", + "version_history": "Versjonshistorie", + "version_history_item": "Installert {version} den {date}", "video": "Video", + "video_hover_setting": "Spel av førehandsvisining medan du held over musepeikaren", "videos": "Videoar", + "videos_count": "{count, plural, one {# Video} other {# Videoar}}", + "view": "Vis", + "view_album": "Sjå Album", + "view_all": "Sjå alle", + "view_all_users": "Sjå alle brukarar", + "view_in_timeline": "Sjå på tidslinja", + "view_links": "Vis lenkjer", + "view_name": "Vis", + "view_next_asset": "Vis neste fil", + "view_previous_asset": "Vis forrige fil", + "view_stack": "Syn stabel", "visibility_changed": "Synlegheit forandra for {count, plural, one {# person} other {# personar}}", "waiting": "Ventar", "warning": "Advarsel", "week": "Veke", "welcome": "Velkomen", + "welcome_to_immich": "Velkomen til Immich", "year": "År", "years_ago": "{years, plural, one {# År} other {# År}} sidan", "yes": "Ja", diff --git a/i18n/pl.json b/i18n/pl.json index b8303604f4..dacd054c89 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -41,6 +41,7 @@ "backup_settings": "Ustawienia kopii zapasowej", "backup_settings_description": "Zarządzaj ustawieniami kopii zapasowej bazy danych", "check_all": "Zaznacz Wszystko", + "cleanup": "Czyszczenie", "cleared_jobs": "Usunięto zadania dla: {job}", "config_set_by_file": "Konfiguracja pochodzi z pliku konfiguracyjnego", "confirm_delete_library": "Czy na pewno chcesz usunąć bibliotekę {library}?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Włącz okresowe skanowanie bibliotek", "library_settings": "Zewnętrzne Biblioteki", "library_settings_description": "Zarządzaj ustawieniami zewnętrznych bibliotek", - "library_tasks_description": "Wykonaj zadania biblioteki", + "library_tasks_description": "Wyszukiwanie nowych lub zmienionych pozycji w zewnętrznych bibliotekach", "library_watching_enable_description": "Przejrzyj zewnętrzne biblioteki w poszukiwaniu zmienionych plików", "library_watching_settings": "Obserwowanie bibliotek (Funkcja eksperymentalna)", "library_watching_settings_description": "Automatycznie obserwuj zmienione pliki", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Szukaj obrazów semantycznie za pomocą CLIP", "machine_learning_smart_search_enabled": "Włącz inteligentne wyszukiwanie", "machine_learning_smart_search_enabled_description": "Jeżeli wyłączone, obrazy nie będą przygotowywane do inteligentnego wyszukiwania.", - "machine_learning_url_description": "URL serwera uczenia maszynowego. Jeżeli podano więcej niż jeden URL, do każdego serwera będzie wysłane żądanie do tej pory dopóki chociaż jeden nie odpowie, w kolejności od pierwszego do ostatniego.", + "machine_learning_url_description": "URL serwera uczenia maszynowego. Jeżeli podano więcej niż jeden URL, do każdego serwera po kolei będzie wysłane żądanie dopóki chociaż jeden nie odpowie, w kolejności od pierwszego do ostatniego. Serwery które nie odpowiedzą, zostaną tymczasowo ignorowane aż do momentu ich przejścia w stan online.", "manage_concurrency": "Zarządzaj współbieżnością zadań", "manage_log_settings": "Zarządzaj ustawieniami logów", "map_dark_style": "Styl ciemny", @@ -147,6 +148,8 @@ "map_settings": "Ustawienia Mapy", "map_settings_description": "Zarządzaj ustawieniami mapy", "map_style_description": "URL do pliku style.json z motywem mapy", + "memory_cleanup_job": "Czyszczenie pamięci", + "memory_generate_job": "Generowanie pamięci", "metadata_extraction_job": "Wyodrębnij metadane", "metadata_extraction_job_description": "Wyodrębnij informacje o metadanych z każdego zasobu, takie jak GPS, twarze i rozdzielczość", "metadata_faces_import_setting": "Włącz import twarzy", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Maksymalne klatki B (B-Frames)", "transcoding_max_b_frames_description": "Wyższe wartości poprawiają wydajność kompresji, ale spowalniają kodowanie. Może nie być kompatybilny z akceleracją sprzętową na starszych urządzeniach. 0 wyłącza klatki B (B-frames), natomiast -1 ustawia tę wartość automatycznie.", "transcoding_max_bitrate": "Maksymalna szybkość transmisji", - "transcoding_max_bitrate_description": "Ustawienie maksymalnej szybkości transmisji może sprawić, że rozmiary plików będą bardziej przewidywalne przy niewielkim koszcie na jakość. Przy rozdzielczości 720p typowe wartości to 2600k dla VP9 lub HEVC, lub 4500k dla H.264. Wyłączone, jeśli ustawione na 0.", + "transcoding_max_bitrate_description": "Ustawienie maksymalnej szybkości transmisji może sprawić, że rozmiary plików będą bardziej przewidywalne przy niewielkim koszcie na jakość. Przy rozdzielczości 720p typowe wartości to 2600 kbit/s dla VP9 lub HEVC, lub 4500 kbit/s dla H.264. Wyłączone, jeśli ustawione na 0.", "transcoding_max_keyframe_interval": "Maksymalny interwał klatek kluczowych", "transcoding_max_keyframe_interval_description": "Ustawia maksymalny dystans między klatkami kluczowymi. Niższe wartości przyspieszają przeszukiwanie filmów i mogą poprawić jakość w scenach z dużą ilością ruchu, kosztem gorszej efektywności kompresji. 0 ustawia tą wartość automatycznie.", "transcoding_optimal_description": "Filmy w rozdzielczości wyższej niż docelowa lub w nieakceptowanym formacie", @@ -352,7 +355,7 @@ "version_check_enabled_description": "Włącz sprawdzanie wersji", "version_check_implications": "Funkcja sprawdzania wersji opiera się na okresowej komunikacji z github.com", "version_check_settings": "Sprawdzenie Wersji", - "version_check_settings_description": "Włącz/wyłącz powiadomienie o nowej wersji", + "version_check_settings_description": "Włącz/wyłącz powiadomienia o nowej wersji", "video_conversion_job": "Transkodowanie wideo", "video_conversion_job_description": "Transkodowanie wideo w celu zapewnienia szerokiej kompatybilności z przeglądarkami i urządzeniami" }, @@ -391,6 +394,7 @@ "allow_edits": "Pozwól edytować", "allow_public_user_to_download": "Zezwól użytkownikowi publicznemu na pobieranie", "allow_public_user_to_upload": "Zezwól użytkownikowi publicznemu na przesyłanie plików", + "alt_text_qr_code": "Obrazek kodu QR", "anti_clockwise": "Przeciwnie do ruchu wskazówek zegara", "api_key": "Klucz API", "api_key_description": "Widzisz tę wartość po raz pierwszy i ostatni, więc lepiej ją skopiuj przed zamknięciem okna.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Komentarze są wyłączone", "confirm": "Potwierdź", "confirm_admin_password": "Potwierdź Hasło Administratora", + "confirm_delete_face": "Czy na pewno chcesz usunąć twarz {name} z zasobów?", "confirm_delete_shared_link": "Czy na pewno chcesz usunąć ten udostępniony link?", "confirm_keep_this_delete_others": "Wszystkie inne zasoby zostaną usunięte poza tym zasobem. Czy jesteś pewien, że chcesz kontynuować?", "confirm_password": "Potwierdź hasło", @@ -522,7 +527,7 @@ "date_of_birth_saved": "Data urodzenia zapisana pomyślnie", "date_range": "Zakres dat", "day": "Dzień", - "deduplicate_all": "Usuń Zduplikowane", + "deduplicate_all": "Usuń duplikaty", "deduplication_criteria_1": "Rozmiar obrazu w bajtach", "deduplication_criteria_2": "Ilość plików EXIF", "deduplication_info": "Stan duplikatów", @@ -533,6 +538,7 @@ "delete_album": "Usuń album", "delete_api_key_prompt": "Czy na pewno chcesz usunąć ten klucz API?", "delete_duplicates_confirmation": "Czy na pewno chcesz trwale usunąć te duplikaty?", + "delete_face": "Usuń twarz", "delete_key": "Usuń klucz", "delete_library": "Usuń bibliotekę", "delete_link": "Usuń link", @@ -548,7 +554,7 @@ "direction": "Kierunek", "disabled": "Wyłączone", "disallow_edits": "Nie pozwalaj edytować", - "discord": "Konflikt", + "discord": "Discord", "discover": "Odkryj", "dismiss_all_errors": "Odrzuć wszystkie błędy", "dismiss_error": "Odrzuć błąd", @@ -600,6 +606,7 @@ "enabled": "Włączone", "end_date": "Do dnia", "error": "Błąd", + "error_delete_face": "Wystąpił błąd podczas usuwania twarzy z zasobów", "error_loading_image": "Błąd podczas ładowania zdjęcia", "error_title": "Błąd - Coś poszło nie tak", "errors": { @@ -766,8 +773,10 @@ "go_to_folder": "Idź do folderu", "go_to_search": "Przejdź do wyszukiwania", "group_albums_by": "Grupuj albumy...", + "group_country": "Grupuj według państwa", "group_no": "Brak grupowania", "group_owner": "Grupuj według właściciela", + "group_places_by": "Grupuj miejsca według...", "group_year": "Grupuj według roku", "has_quota": "Ma limit", "hi_user": "Cześć {name} ({email})", @@ -800,6 +809,7 @@ "include_shared_albums": "Uwzględnij udostępnione albumy", "include_shared_partner_assets": "Uwzględnij udostępnione zasoby partnera", "individual_share": "Udostępniony zasób", + "individual_shares": "Indywidualne udziały", "info": "Informacje", "interval": { "day_at_onepm": "Codziennie o 13:00", @@ -881,6 +891,7 @@ "month": "Miesiąc", "more": "Więcej...", "moved_to_trash": "Przeniesiono do kosza", + "mute_memories": "Wycisz wspomnienia", "my_albums": "Moje albumy", "name": "Nazwa", "name_or_nickname": "Nazwa lub pseudonim", @@ -985,6 +996,7 @@ "pick_a_location": "Oznacz lokalizację", "place": "Miejsce", "places": "Miejsca", + "places_count": "{count, plural, one {{count, number} Miejsce} few {{count, number} Miejsca}other {{count, number} Miejsc}}", "play": "Odtwórz", "play_memories": "Odtwórz wspomnienia", "play_motion_photo": "Odtwórz Ruchome Zdjęcie", @@ -1072,6 +1084,8 @@ "removed_from_archive": "Usunięto z archiwum", "removed_from_favorites": "Usunięto z ulubionych", "removed_from_favorites_count": "{count, plural, other {Usunięto #}} z ulubionych", + "removed_memory": "Pamięć została usunięta", + "removed_photo_from_memory": "Usunięto zdjęcie z pamięci", "removed_tagged_assets": "Usunięto etykietę z {count, plural, one {# zasobu} other {# zasobów}}", "rename": "Zmień nazwę", "repair": "Napraw", @@ -1080,6 +1094,7 @@ "repository": "Repozytorium", "require_password": "Wymagaj hasło", "require_user_to_change_password_on_first_login": "Zmuś użytkownika do zmiany hasła podczas następnego logowania", + "rescan": "Ponowne skanowanie", "reset": "Reset", "reset_password": "Resetuj hasło", "reset_people_visibility": "Zresetuj widoczność osób", @@ -1109,6 +1124,7 @@ "search_albums": "Przeszukaj albumy", "search_by_context": "Wyszukaj według treści", "search_by_description": "Wyszukaj według opisu", + "search_by_description_example": "Jednodniowa wycieczka górska w Bieszczady", "search_by_filename": "Szukaj według nazwy pliku lub rozszerzenia", "search_by_filename_example": "np. IMG_1234.JPG lub PNG", "search_camera_make": "Wyszukaj markę aparatu...", @@ -1122,6 +1138,7 @@ "search_options": "Opcje wyszukiwania", "search_people": "Wyszukaj osoby", "search_places": "Wyszukaj miejsca", + "search_rating": "Wyszukaj według ocen...", "search_settings": "Ustawienia przeszukiwania", "search_state": "Wyszukaj stan...", "search_tags": "Wyszukaj etykiety...", @@ -1168,6 +1185,7 @@ "shared_from_partner": "Zdjęcia od {partner}", "shared_link_options": "Opcje udostępniania linku", "shared_links": "Udostępnione linki", + "shared_links_description": "Udostępnij zdjęcia oraz filmy przez link", "shared_photos_and_videos_count": "{assetCount, plural, other {# udostępnione zdjęcia i filmy.}}", "shared_with_partner": "Dzielisz się z {partner}", "sharing": "Udostępnianie", @@ -1190,6 +1208,7 @@ "show_person_options": "Pokaż opcje osoby", "show_progress_bar": "Pokaż pasek postępu", "show_search_options": "Wyświetl opcje wyszukiwania", + "show_shared_links": "Pokaż udostępniane linki", "show_slideshow_transition": "Pokaż przejście pokazu slajdów", "show_supporter_badge": "Odznaka wspierającego", "show_supporter_badge_description": "Pokaż odznakę wspierającego", @@ -1243,6 +1262,7 @@ "tag_created": "Stworzono etykietę: {tag}", "tag_feature_description": "Przeglądanie zdjęć i filmów pogrupowanych według logicznych etykiet wskazujących temat", "tag_not_found_question": "Nie możesz znaleźć etykiety? Utwórz ją tutaj", + "tag_people": "Dodaj etykiety osób", "tag_updated": "Uaktualniono etykietę: {tag}", "tagged_assets": "Przypisano etykietę {count, plural, one {# zasobowi} other {# zasobom}}", "tags": "Etykiety", @@ -1277,11 +1297,13 @@ "unfavorite": "Usuń z ulubionych", "unhide_person": "Przywróć osobę", "unknown": "Nieznany", + "unknown_country": "Nieznane państwo", "unknown_year": "Rok nieznany", "unlimited": "Nieograniczony", "unlink_motion_video": "Rozłącz ruchome wideo", "unlink_oauth": "Odłącz OAuth", "unlinked_oauth_account": "Odłączone konto OAuth", + "unmute_memories": "Włącz dźwięk wspomnień", "unnamed_album": "Nienazwany album", "unnamed_album_delete_confirmation": "Czy jesteś pewna/pewien, że chcesz usunąć te album?", "unnamed_share": "Nienazwany udział", @@ -1335,6 +1357,7 @@ "view_all": "Pokaż wszystkie", "view_all_users": "Pokaż wszystkich użytkowników", "view_in_timeline": "Pokaż na osi czasu", + "view_link": "Zobacz link", "view_links": "Pokaż łącza", "view_name": "Widok", "view_next_asset": "Wyświetl następny zasób", @@ -1351,4 +1374,4 @@ "yes": "Tak", "you_dont_have_any_shared_links": "Nie masz żadnych udostępnionych linków", "zoom_image": "Powiększ obraz" -} +} \ No newline at end of file diff --git a/i18n/pt.json b/i18n/pt.json index 26359a86ab..fe987f9564 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -41,6 +41,7 @@ "backup_settings": "Definições de Cópia de Segurança", "backup_settings_description": "Gerir definições de cópia de segurança da base de dados", "check_all": "Selecionar Tudo", + "cleanup": "Limpeza", "cleared_jobs": "Eliminadas as tarefas de: {job}", "config_set_by_file": "A configuração está atualmente definida por um ficheiro de configuração", "confirm_delete_library": "Tem a certeza de que deseja eliminar a biblioteca {library} ?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Ativar análise periódica da biblioteca", "library_settings": "Biblioteca Externa", "library_settings_description": "Gerir definições de biblioteca externa", - "library_tasks_description": "Executa tarefas de biblioteca", + "library_tasks_description": "Pesquisa bibliotecas externas em busca de itens novos e/ou alterados", "library_watching_enable_description": "Analisar bibliotecas externas por alterações de ficheiros", "library_watching_settings": "Análise de biblioteca (EXPERIMENTAL)", "library_watching_settings_description": "Analise automaticamente por ficheiros alterados", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Pesquise imagens semanticamente utilizando embeddings CLIP", "machine_learning_smart_search_enabled": "Ativar a Pesquisa Inteligente", "machine_learning_smart_search_enabled_description": "Se desativado, as imagens não serão codificadas para Pesquisa Inteligente.", - "machine_learning_url_description": "A URL do servidor de aprendizagem de máquina. Se for fornecido mais do que um URL, cada servidor será testado, um a um, até um deles responder com sucesso, por ordem do primeiro ao último.", + "machine_learning_url_description": "A URL do servidor de aprendizagem de máquina. Se for fornecido mais do que um URL, cada servidor será testado, um a um, até um deles responder com sucesso, por ordem do primeiro ao último. Servidores que não responderem serão temporariamente ignorados até voltarem a estar online.", "manage_concurrency": "Gerir simultaneidade", "manage_log_settings": "Gerir definições de registo", "map_dark_style": "Tema Escuro", @@ -147,6 +148,8 @@ "map_settings": "Mapa", "map_settings_description": "Gerir definições do mapa", "map_style_description": "URL para um tema de mapa style.json", + "memory_cleanup_job": "Limpeza de memórias", + "memory_generate_job": "Geração de memórias", "metadata_extraction_job": "Extrair metadados", "metadata_extraction_job_description": "Extrai informações de metadados de cada ficheiro, como GPS, rostos e resolução", "metadata_faces_import_setting": "Ativar a importação facial", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Ativa a verificação de hash, não desative esta opção a menos que tenha a certeza das implicações", "storage_template_migration": "Migração de modelo de armazenamento", "storage_template_migration_description": "Aplica o {template} atual para ficheiros previamente carregados", - "storage_template_migration_info": "As mudanças do modelo apenas se aplicarão a novos ficheiros. Para aplicar o modelo retroativamente para os ficheiros carregados anteriormente, execute o {job}.", + "storage_template_migration_info": "O modelo de armazenamento irá converter todas as extensões para letra minúscula. As mudanças do modelo apenas se aplicarão a novos ficheiros. Para aplicar o modelo retroativamente para os ficheiros carregados anteriormente, execute o {job}.", "storage_template_migration_job": "Tarefa de Migração do Modelo de Armazenamento", "storage_template_more_details": "Para mais informações sobre esta funcionalidade, dirija-se a Modelo de Armazenamento e às suas implicações", "storage_template_onboarding_description": "Quando ativada, esta funcionalidade irá organizar os ficheiros automaticamente baseando-se num modelo definido pelo utilizador. Devido a problemas de estabilidade esta funcionalidade está desativada por padrão. Para mais informações, por favor leia a documentação.", @@ -391,6 +394,7 @@ "allow_edits": "Permitir edições", "allow_public_user_to_download": "Permitir que utilizadores públicos façam transferências", "allow_public_user_to_upload": "Permitir que utilizadores públicos façam carregamentos", + "alt_text_qr_code": "Imagem do código QR", "anti_clockwise": "Sentido anti-horário", "api_key": "Chave de API", "api_key_description": "Este valor será apresentado apenas uma única vez. Por favor, certifique-se que o copiou antes de fechar a janela.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Comentários estão desativados", "confirm": "Confirmar", "confirm_admin_password": "Confirmar palavra-passe de administrador", + "confirm_delete_face": "Tem a certeza de que deseja remover o rosto de {name} deste ficheiro?", "confirm_delete_shared_link": "Tem a certeza de que deseja eliminar este link partilhado?", "confirm_keep_this_delete_others": "Todos os outros ficheiros na pilha serão eliminados, exceto este ficheiro. Tem a certeza de que deseja continuar?", "confirm_password": "Confirmar a palavra-passe", @@ -533,6 +538,7 @@ "delete_album": "Eliminar álbum", "delete_api_key_prompt": "Tem a certeza de que deseja eliminar esta chave de API?", "delete_duplicates_confirmation": "Tem a certeza de que deseja eliminar permanentemente estes itens duplicados?", + "delete_face": "Remover rosto", "delete_key": "Eliminar chave", "delete_library": "Eliminar Biblioteca", "delete_link": "Eliminar link", @@ -600,6 +606,7 @@ "enabled": "Ativado", "end_date": "Data final", "error": "Erro", + "error_delete_face": "Falha ao remover rosto do ficheiro", "error_loading_image": "Erro ao carregar a imagem", "error_title": "Erro - Algo correu mal", "errors": { @@ -884,6 +891,7 @@ "month": "Mês", "more": "Mais", "moved_to_trash": "Enviado para a reciclagem", + "mute_memories": "Silenciar Memórias", "my_albums": "Os meus álbuns", "name": "Nome", "name_or_nickname": "Nome ou alcunha", @@ -1076,6 +1084,8 @@ "removed_from_archive": "Removido do arquivo", "removed_from_favorites": "Removido dos favoritos", "removed_from_favorites_count": "{count, plural, other {Removidos #}} dos favoritos", + "removed_memory": "Memória removida", + "removed_photo_from_memory": "Foto removida da memória", "removed_tagged_assets": "Removida a etiqueta de {count, plural, one {# ficheiro} other {# ficheiros}}", "rename": "Mudar o nome", "repair": "Reparar", @@ -1084,6 +1094,7 @@ "repository": "Repositório", "require_password": "Proteger com palavra-passe", "require_user_to_change_password_on_first_login": "Obrigar utilizador a alterar a palavra-passe após o primeiro início de sessão", + "rescan": "Reescanear", "reset": "Redefinir", "reset_password": "Redefinir palavra-passe", "reset_people_visibility": "Redefinir pessoas ocultas", @@ -1127,6 +1138,7 @@ "search_options": "Opções de pesquisa", "search_people": "Pesquisar pessoas", "search_places": "Pesquisar lugares", + "search_rating": "Pesquisar por classificação...", "search_settings": "Definições de pesquisa", "search_state": "Pesquisar estado/distrito...", "search_tags": "Pesquisar etiquetas...", @@ -1250,6 +1262,7 @@ "tag_created": "Criada a etiqueta {tag}", "tag_feature_description": "A mostrar fotos e videos agrupados por tópicos lógicos de etiquetas", "tag_not_found_question": "Não consegue encontrar a etiqueta? Crie uma nova etiqueta.", + "tag_people": "Etiquetar Pessoas", "tag_updated": "Atualizada a etiqueta: {tag}", "tagged_assets": "Etiquetado {count, plural, one {# ficheiros} other {# ficheiros}}", "tags": "Etiquetas", @@ -1290,6 +1303,7 @@ "unlink_motion_video": "Remover relação com video animado", "unlink_oauth": "Desvincular OAuth", "unlinked_oauth_account": "Conta OAuth desvinculada", + "unmute_memories": "Ativar som das memórias", "unnamed_album": "Álbum sem nome", "unnamed_album_delete_confirmation": "Tem a certeza de que pretende eliminar este álbum?", "unnamed_share": "Partilha sem nome", @@ -1343,6 +1357,7 @@ "view_all": "Ver tudo", "view_all_users": "Ver todos os utilizadores", "view_in_timeline": "Ver na linha do tempo", + "view_link": "Ver link", "view_links": "Ver links", "view_name": "Ver", "view_next_asset": "Ver próximo ficheiro", diff --git a/i18n/pt_BR.json b/i18n/pt_BR.json index d826997f5e..f9cf84476f 100644 --- a/i18n/pt_BR.json +++ b/i18n/pt_BR.json @@ -41,6 +41,7 @@ "backup_settings": "Configurações de backup", "backup_settings_description": "Gerenciar configurações de backup do banco de dados", "check_all": "Selecionar Tudo", + "cleanup": "Limpeza", "cleared_jobs": "Tarefas removidas de: {job}", "config_set_by_file": "A configuração está atualmente definida por um arquivo de configuração", "confirm_delete_library": "Você tem certeza que deseja excluir a biblioteca {library} ?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Habilitar verificação periódica da biblioteca", "library_settings": "Biblioteca Externa", "library_settings_description": "Gerenciar configurações de biblioteca externa", - "library_tasks_description": "Execute tarefas de biblioteca", + "library_tasks_description": "Escanear bibliotecas externas para ativos novos ou modificados", "library_watching_enable_description": "Observe bibliotecas externas para alterações de arquivos", "library_watching_settings": "Observação de biblioteca (EXPERIMENTAL)", "library_watching_settings_description": "Observe automaticamente os arquivos alterados", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Buscar imagens semanticamente usando integrações CLIP", "machine_learning_smart_search_enabled": "Habilitar a Pesquisa Inteligente", "machine_learning_smart_search_enabled_description": "Se desativado, as imagens não serão codificadas para pesquisa inteligente.", - "machine_learning_url_description": "A URL do servidor de aprendizado de máquina. Se mais de uma URL for fornecida, elas serão tentadas, uma de cada vez e na ordem indicada, até que uma responda com sucesso.", + "machine_learning_url_description": "A URL do servidor de aprendizado de máquina. Se mais de uma URL for fornecida, elas serão tentadas, uma de cada vez e na ordem indicada, até que uma responda com sucesso. Servidores que não responderem serão ignorados temporariamente até voltarem a estar conectados.", "manage_concurrency": "Gerenciar simultaneidade", "manage_log_settings": "Gerenciar configurações de registro", "map_dark_style": "Tema Escuro", @@ -147,6 +148,8 @@ "map_settings": "Mapa", "map_settings_description": "Gerenciar configurações do mapa", "map_style_description": "URL para um tema de mapa style.json", + "memory_cleanup_job": "Limpeza de memórias", + "memory_generate_job": "Criação de memórias", "metadata_extraction_job": "Extrair metadados", "metadata_extraction_job_description": "Extraia informações dos metadados de cada arquivo, como GPS, rostos e resolução", "metadata_faces_import_setting": "Ativar a importação de rostos", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Ativa a verificação de hash, não desative a menos que você tenha certeza das implicações", "storage_template_migration": "Migração de modelo de armazenamento", "storage_template_migration_description": "Aplique o {template} atual aos arquivos carregados anteriormente", - "storage_template_migration_info": "As mudanças no modelo serão aplicadas apenas aos novos arquivos. Para aplicar retroativamente o modelo aos arquivos carregados anteriormente, execute o {job}.", + "storage_template_migration_info": "O modelo altera todas extensões para minúsculo. As mudanças no modelo serão aplicadas apenas em novos arquivos. Para aplicar retroativamente o modelo aos arquivos carregados anteriormente, execute o {job}.", "storage_template_migration_job": "Tarefa de Migração de Modelo de Armazenamento", "storage_template_more_details": "Para mais detalhes sobre este recurso, consulte o Modelo de Armazenamento e suas implicações", "storage_template_onboarding_description": "Quando ativado, este recurso organizará automaticamente os arquivos com base em um modelo definido pelo usuário. Devido a problemas de estabilidade, o recurso está desativado por padrão. Para mais informações, consulte a documentação.", @@ -391,6 +394,7 @@ "allow_edits": "Permitir edições", "allow_public_user_to_download": "Permitir que usuários públicos baixem os arquivos", "allow_public_user_to_upload": "Permitir que usuários públicos enviem novos arquivos", + "alt_text_qr_code": "Imagem do código QR", "anti_clockwise": "Anti-horário", "api_key": "Chave de API", "api_key_description": "Este valor será mostrado apenas uma vez. Por favor, certifique-se de copiá-lo antes de fechar a janela.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Comentários estão desativados", "confirm": "Confirmar", "confirm_admin_password": "Confirmar senha de administrador", + "confirm_delete_face": "Tem certeza que deseja remover a face de {name} deste arquivo?", "confirm_delete_shared_link": "Tem certeza de que deseja excluir este link compartilhado?", "confirm_keep_this_delete_others": "Todos os outros arquivos da pilha serão excluídos, exceto este arquivo. Tem certeza de que deseja continuar?", "confirm_password": "Confirme a senha", @@ -533,6 +538,7 @@ "delete_album": "Excluir álbum", "delete_api_key_prompt": "Tem certeza de que deseja excluir esta chave de API?", "delete_duplicates_confirmation": "Tem certeza de que deseja excluir permanentemente estas duplicidades?", + "delete_face": "Remover face", "delete_key": "Excluir chave", "delete_library": "Excluir biblioteca", "delete_link": "Excluir link", @@ -600,6 +606,7 @@ "enabled": "Habilitado", "end_date": "Data final", "error": "Erro", + "error_delete_face": "Erro ao remover face do arquivo", "error_loading_image": "Erro ao carregar a página", "error_title": "Erro - Algo deu errado", "errors": { @@ -884,6 +891,7 @@ "month": "Mês", "more": "Mais", "moved_to_trash": "Enviado para a lixeira", + "mute_memories": "Silenciar memórias", "my_albums": "Meus Álbuns", "name": "Nome", "name_or_nickname": "Nome ou apelido", @@ -1076,6 +1084,8 @@ "removed_from_archive": "Removido do arquivo", "removed_from_favorites": "Removido dos favoritos", "removed_from_favorites_count": "{count, plural, one {# Removido} other {# Removidos}} dos favoritos", + "removed_memory": "Memória removida", + "removed_photo_from_memory": "Foto removida da memória", "removed_tagged_assets": "Tag removida de {count, plural, one {# arquivo} other {# arquivos}}", "rename": "Renomear", "repair": "Reparar", @@ -1084,6 +1094,7 @@ "repository": "Repositório", "require_password": "Proteger com senha", "require_user_to_change_password_on_first_login": "Obrigar usuário a alterar a senha após primeiro login", + "rescan": "Reescanear", "reset": "Resetar", "reset_password": "Resetar senha", "reset_people_visibility": "Resetar pessoas ocultas", @@ -1127,6 +1138,7 @@ "search_options": "Opções de pesquisa", "search_people": "Pesquisar pessoas", "search_places": "Pesquisar lugares", + "search_rating": "Pesquisar por classificação...", "search_settings": "Configurações de pesquisa", "search_state": "Pesquisar estado...", "search_tags": "Procurar tags...", @@ -1250,6 +1262,7 @@ "tag_created": "Tag foi criada: {tag}", "tag_feature_description": "Visualizar fotos e videos agrupados pelo tópico da tag", "tag_not_found_question": "Não consegue encontrar a tag? Crie uma tag nova aqui.", + "tag_people": "Marcar pessoas", "tag_updated": "Tag foi atualizada: {tag}", "tagged_assets": "{count, plural, one {# arquivo marcado} other {# arquivos marcados}} com a tag", "tags": "Tags", @@ -1290,6 +1303,7 @@ "unlink_motion_video": "Remover relação com video animado", "unlink_oauth": "Desvincular OAuth", "unlinked_oauth_account": "Conta OAuth desvinculada", + "unmute_memories": "Ativar Memórias", "unnamed_album": "Álbum sem nome", "unnamed_album_delete_confirmation": "Tem certeza que deseja excluir este álbum?", "unnamed_share": "Compartilhamento sem nome", @@ -1343,6 +1357,7 @@ "view_all": "Ver tudo", "view_all_users": "Ver todos usuários", "view_in_timeline": "Ver na linha do tempo", + "view_link": "Ver link", "view_links": "Ver links", "view_name": "Ver", "view_next_asset": "Ver próximo arquivo", diff --git a/i18n/ro.json b/i18n/ro.json index 135d647b54..c88b01cd5c 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -299,7 +299,7 @@ "transcoding_max_b_frames": "Număr maxim de cadre B", "transcoding_max_b_frames_description": "Valorile mai mari îmbunătățesc eficiența compresiei, dar încetinesc codarea. Este posibil să nu fie compatibile cu accelerarea hardware pe dispozitivele mai vechi. 0 dezactivează cadrele B, în timp ce -1 setează această valoare automat.", "transcoding_max_bitrate": "Rata de biți maximă", - "transcoding_max_bitrate_description": "Setarea unei rate maxime de biți poate face dimensiunile fișierelor mai previzibile, cu un cost minor asupra calității. La 720p, valorile tipice sunt 2600k pentru VP9 sau HEVC, sau 4500k pentru H.264. Dezactivat dacă este setat la 0.", + "transcoding_max_bitrate_description": "Setarea unei rate maxime de biți poate face dimensiunile fișierelor mai previzibile, cu un cost minor asupra calității. La 720p, valorile tipice sunt 2600 kbit/s pentru VP9 sau HEVC, sau 4500 kbit/s pentru H.264. Dezactivat dacă este setat la 0.", "transcoding_max_keyframe_interval": "Interval maxim între cadre cheie", "transcoding_max_keyframe_interval_description": "Setează distanța maximă între cadrele cheie. Valorile mai mici reduc eficiența compresiei, dar îmbunătățesc timpii de căutare și pot îmbunătăți calitatea în scenele cu mișcare rapidă. 0 setează această valoare automat.", "transcoding_optimal_description": "Videoclipuri cu rezoluție mai mare decât cea țintă sau care nu sunt într-un format acceptat", @@ -1358,4 +1358,4 @@ "yes": "Da", "you_dont_have_any_shared_links": "Nu aveți linkuri partajate", "zoom_image": "Măriți Imaginea" -} +} \ No newline at end of file diff --git a/i18n/ru.json b/i18n/ru.json index f2c191b004..b4a2eadace 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -41,6 +41,7 @@ "backup_settings": "Настройки резервного копирования", "backup_settings_description": "Управление настройками резервного копирования базы данных", "check_all": "Проверить все", + "cleanup": "Очистка", "cleared_jobs": "Очищены задачи для: {job}", "config_set_by_file": "Настроено с помощью файла конфигурации", "confirm_delete_library": "Вы действительно хотите удалить библиотеку \"{library}\"?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Включить периодическое сканирование библиотеки", "library_settings": "Внешняя библиотека", "library_settings_description": "Управление внешними библиотеками", - "library_tasks_description": "Выполняет задания библиотеки", + "library_tasks_description": "Сканирование внешних библиотек на наличие новых и/или изменённых объектов", "library_watching_enable_description": "Отслеживать изменения файлов внешней библиотеки", "library_watching_settings": "Слежение за библиотекой (ЭКСПЕРИМЕНТАЛЬНОЕ)", "library_watching_settings_description": "Автоматически следить за изменениями файлов", @@ -147,6 +148,8 @@ "map_settings": "Настройки карты", "map_settings_description": "Управление настройками карты", "map_style_description": "URL-адрес темы карты style.json", + "memory_cleanup_job": "Очистка воспоминаний", + "memory_generate_job": "Создание воспоминаний", "metadata_extraction_job": "Извлечение метаданных", "metadata_extraction_job_description": "Извлекает метаданные из каждого файла, такие как местоположение, лица и разрешение", "metadata_faces_import_setting": "Включить импорт лиц", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Включает проверку хэша, не отключайте ее, если вы не уверены в последствиях", "storage_template_migration": "Применение шаблона хранилища", "storage_template_migration_description": "Применяет текущий {template} к ранее загруженным ресурсам", - "storage_template_migration_info": "Изменения в шаблоне будут применяться только к новым ресурсам. Чтобы применить шаблон к ранее загруженным ресурсам, запустите {job}.", + "storage_template_migration_info": "Расширения файлов всегда будут сохраняться в нижнем регистре. Изменения в шаблоне будут применяться только к новым ресурсам. Чтобы применить шаблон к ранее загруженным ресурсам, запустите {job}.", "storage_template_migration_job": "Задание миграции шаблона хранилища", "storage_template_more_details": "Для получения дополнительной информации об этой функции обратитесь к Шаблону хранилища и месту его хранения", "storage_template_onboarding_description": "При включении этой функции файлы будут автоматически организованы в соответствии с пользовательским шаблоном. Из-за проблем со стабильностью функция по умолчанию отключена. Дополнительную информацию можно найти в документации.", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Максимально промежуточных кадров", "transcoding_max_b_frames_description": "Более высокие значения повышают эффективность сжатия, но замедляют кодирование. Может быть несовместимо с аппаратным ускорением на старых устройствах. 0 отключает B-кадры, а -1 устанавливает это значение автоматически.", "transcoding_max_bitrate": "Максимальный битрейт", - "transcoding_max_bitrate_description": "Установка максимального битрейта может сделать размер файла более предсказуемым при незначительном снижении качества. При 720p типичными значениями являются 2600k для VP9 или HEVC или 4500k для H.264. Отключено, если установлено значение 0.", + "transcoding_max_bitrate_description": "Установка максимального битрейта может сделать размер файла более предсказуемым при незначительном снижении качества. При 720p типичными значениями являются 2600 kbit/s для VP9 или HEVC или 4500 kbit/s для H.264. Отключено, если установлено значение 0.", "transcoding_max_keyframe_interval": "Максимальный интервал ключевых кадров", "transcoding_max_keyframe_interval_description": "Устанавливает максимальное расстояние между ключевыми кадрами. Более низкие значения ухудшают эффективность сжатия, но сокращают время поиска и могут улучшить качество в сценах с быстрым движением. 0 устанавливает это значение автоматически.", "transcoding_optimal_description": "Видео с разрешением выше целевого или не в принятом формате", @@ -358,7 +361,7 @@ }, "admin_email": "Электронная почта администратора", "admin_password": "Пароль администратора", - "administration": "Управление", + "administration": "Управление сервером", "advanced": "Расширенные", "age_months": "Возраст {months, plural, one {# месяц} few {# месяца} many {# месяцев} other {# месяца}}", "age_year_months": "Возраст 1 год, {months, plural, one {# месяц} few {# месяца} many {# месяцев} other {# месяца}}", @@ -391,6 +394,7 @@ "allow_edits": "Разрешить редактирование", "allow_public_user_to_download": "Разрешить скачивание публичным пользователям", "allow_public_user_to_upload": "Разрешить публичным пользователям загружать файлы", + "alt_text_qr_code": "QR-код", "anti_clockwise": "Против часовой", "api_key": "API Ключ", "api_key_description": "Это значение будет показано только один раз. Пожалуйста, убедитесь, что скопировали его перед закрытием окна.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Комментарии отключены", "confirm": "Подтвердить", "confirm_admin_password": "Подтвердите пароль Администратора", + "confirm_delete_face": "Вы точно хотите удалить лицо {name} из объекта?", "confirm_delete_shared_link": "Вы уверены, что хотите удалить эту публичную ссылку?", "confirm_keep_this_delete_others": "Все остальные объекты в группе будут удалены, кроме этого объекта. Вы уверены, что хотите продолжить?", "confirm_password": "Подтвердите пароль", @@ -533,6 +538,7 @@ "delete_album": "Удалить альбом", "delete_api_key_prompt": "Вы уверены, что хотите удалить этот ключ API?", "delete_duplicates_confirmation": "Вы уверены, что хотите навсегда удалить эти дубликаты?", + "delete_face": "Удалить лицо", "delete_key": "Удалить ключ", "delete_library": "Удалить библиотеку", "delete_link": "Удалить ссылку", @@ -600,6 +606,7 @@ "enabled": "Включено", "end_date": "Дата окончания", "error": "Ошибка", + "error_delete_face": "Ошибка при удалении лица из объекта", "error_loading_image": "Ошибка при загрузке изображения", "error_title": "Ошибка - Что-то пошло не так", "errors": { @@ -884,6 +891,7 @@ "month": "Месяц", "more": "Больше", "moved_to_trash": "Перенесено в корзину", + "mute_memories": "Отключить звук", "my_albums": "Мои альбомы", "name": "Имя", "name_or_nickname": "Имя или ник", @@ -979,6 +987,7 @@ "permanently_deleted_asset": "Удалить навсегда", "permanently_deleted_assets_count": "Безвозвратно удалено {count, plural, one {# файл} few {# файла} many {# файлов} other {# файлов}}", "person": "Человек", + "person_birthdate": "Дата рождения: {date}", "person_hidden": "{name}{hidden, select, true { (скрыт)} other {}}", "photo_shared_all_users": "Похоже, что вы поделились своими фотографиями со всеми пользователями или у вас нет пользователей, с которыми можно поделиться.", "photos": "Фото", @@ -1076,6 +1085,8 @@ "removed_from_archive": "Удален из архива", "removed_from_favorites": "Удалено из избранного", "removed_from_favorites_count": "{count, plural, other {Удалено #}} из избранного", + "removed_memory": "Удалить воспоминание", + "removed_photo_from_memory": "Удалить фото из воспоминания", "removed_tagged_assets": "Тег для {count, plural, one {# объекта} other {# объектов}} удален", "rename": "Переименовать", "repair": "Ремонт", @@ -1084,6 +1095,7 @@ "repository": "Репозиторий", "require_password": "Требуется пароль", "require_user_to_change_password_on_first_login": "Требовать у пользователя сменить пароль при первом входе", + "rescan": "Повторное сканирование", "reset": "Сброс", "reset_password": "Сброс пароля", "reset_people_visibility": "Восстановить видимость людей", @@ -1127,6 +1139,7 @@ "search_options": "Параметры поиска", "search_people": "Поиск людей", "search_places": "Поиск мест", + "search_rating": "Поиск по рейтингу...", "search_settings": "Настройки поиска", "search_state": "Поиск региона...", "search_tags": "Поиск по тегам...", @@ -1250,6 +1263,7 @@ "tag_created": "Тег {tag} создан", "tag_feature_description": "Просмотр фотографий и видео, сгруппированных по тегам", "tag_not_found_question": "Не удается найти тег? Создайте новый тег.", + "tag_people": "Тег людей", "tag_updated": "Тег {tag} изменен", "tagged_assets": "Помечено {count, plural, one {# объект} other {# объектов}}", "tags": "Теги", @@ -1290,6 +1304,7 @@ "unlink_motion_video": "Отсоединить движущееся видео", "unlink_oauth": "Отключить OAuth", "unlinked_oauth_account": "Отключить аккаунт OAuth", + "unmute_memories": "Включить звук", "unnamed_album": "Альбом без названия", "unnamed_album_delete_confirmation": "Вы уверены, что хотите удалить этот альбом?", "unnamed_share": "Общий доступ без названия", @@ -1343,6 +1358,7 @@ "view_all": "Посмотреть всё", "view_all_users": "Показать всех пользователей", "view_in_timeline": "Показать на временной шкале", + "view_link": "Показать ссылку", "view_links": "Показать ссылки", "view_name": "Посмотреть", "view_next_asset": "Показать следующий объект", @@ -1359,4 +1375,4 @@ "yes": "Да", "you_dont_have_any_shared_links": "У вас нет публичных ссылок", "zoom_image": "Приблизить" -} +} \ No newline at end of file diff --git a/i18n/sk.json b/i18n/sk.json index 5b958ab431..ab5c7d3f53 100644 --- a/i18n/sk.json +++ b/i18n/sk.json @@ -1,5 +1,5 @@ { - "about": "O Immich", + "about": "O aplikácii", "account": "Účet", "account_settings": "Nastavenia účtu", "acknowledge": "Rozumiem", @@ -20,7 +20,7 @@ "add_partner": "Pridať partnera", "add_path": "Pridať cestu", "add_photos": "Pridať fotografie", - "add_to": "Pridať do...", + "add_to": "Pridať do…", "add_to_album": "Pridať do albumu", "add_to_shared_album": "Pridať do zdieľaného albumu", "add_url": "Pridať URL", @@ -41,6 +41,7 @@ "backup_settings": "Nastavenia zálohovania", "backup_settings_description": "Spravovať nastavenia záloh", "check_all": "Skontrolovať všetko", + "cleanup": "Vyčistenie", "cleared_jobs": "Hotové úlohy pre: {job}", "config_set_by_file": "Konfigurácia je v súčasnosti nastavená konfiguračným súborom", "confirm_delete_library": "Naozaj chcete vymazať knižnicu {library}?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Zapnúť pravidelné skenovanie knižnice", "library_settings": "Externá knižnica", "library_settings_description": "Spravovať nastavenia externej knižnice", - "library_tasks_description": "Vykonať úlohy knižnice", + "library_tasks_description": "Vyhľadávanie nových alebo zmenených položiek v externých knižniciach", "library_watching_enable_description": "Sledovať externé knižnice pre zmeny v súboroch", "library_watching_settings": "Sledovanie knižnice (EXPERIMENTÁLNE)", "library_watching_settings_description": "Automaticky sledovať zmenené súbory", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Významové vyhľadávanie v obrázkoch pomocou CLIP vzorov", "machine_learning_smart_search_enabled": "Povoliť inteligentné vyhľadávanie", "machine_learning_smart_search_enabled_description": "Ak je vypnuté, obrázky nebudú spracované pre inteligentné vyhľadávanie.", - "machine_learning_url_description": "URL adresa machine-learning servera. Ak je poskytnutých viacero URL adries, budú servery postupne testované od prvého po posledný, až kým jeden z nich úspešne odpovie.", + "machine_learning_url_description": "URL adresa servera strojového učenia. Ak je zadaných viacero adries URL, každý server bude testovaný postupne, kým jeden z nich neodpovie úspešne, v poradí od prvého po posledný. Servery, ktoré neodpovedajú, budú dočasne ignorované, kým nebudú opäť online.", "manage_concurrency": "Správa súbežnosti", "manage_log_settings": "Spravovať nastavenia logovania", "map_dark_style": "Tmavý štýl", @@ -147,6 +148,8 @@ "map_settings": "Mapa", "map_settings_description": "Spravovať nastavenia mapy", "map_style_description": "URL na motív style.json", + "memory_cleanup_job": "Vymazávanie spomienok", + "memory_generate_job": "Vytváranie spomienok", "metadata_extraction_job": "Extrahovať metadáta", "metadata_extraction_job_description": "Vytiahne metadáta z každej položky, ako napríklad GPS, tváre a rozlíšenie", "metadata_faces_import_setting": "Povoliť import tváre", @@ -219,7 +222,7 @@ "reset_settings_to_default": "Obnoviť pôvodné nastavenia", "reset_settings_to_recent_saved": "Obnoviť naposledy uložené nastavenia", "scanning_library": "Knižnica sa skenuje", - "search_jobs": "Vyhľadať úlohy...", + "search_jobs": "Vyhľadať úlohy…", "send_welcome_email": "Odoslať uvítací e-mail", "server_external_domain_settings": "Externá doména", "server_external_domain_settings_description": "Verejná doména pre zdieľané odkazy, vrátane http(s)://", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Maximálny počet B-snímkov", "transcoding_max_b_frames_description": "Vyššie hodnoty zvyšujú účinnosť kompresie, ale spomaľujú kódovanie. Nemusí byť kompatibilný s hardvérovou akceleráciou na starších zariadeniach. Hodnota 0 zakáže B-snímky, zatiaľ čo -1 nastaví túto hodnotu automaticky.", "transcoding_max_bitrate": "Maximálna bitová rýchlosť", - "transcoding_max_bitrate_description": "Nastavenie maximálneho dátového toku môže zvýšiť predvídateľnosť veľkosti súborov za cenu menšieho zníženia kvality. Pri rozlíšení 720p sú typické hodnoty 2600k pre VP9 alebo HEVC alebo 4500k pre H.264. Zakázané, ak je nastavená hodnota 0.", + "transcoding_max_bitrate_description": "Nastavenie maximálneho dátového toku môže zvýšiť predvídateľnosť veľkosti súborov za cenu menšieho zníženia kvality. Pri rozlíšení 720p sú typické hodnoty 2600 kbit/s pre VP9 alebo HEVC alebo 4500 kbit/s pre H.264. Zakázané, ak je nastavená hodnota 0.", "transcoding_max_keyframe_interval": "Maximálny interval medzi kľúčovými snímkami", "transcoding_max_keyframe_interval_description": "Nastavuje maximálnu vzdialenosť medzi kľúčovými snímkami. Nižšie hodnoty zhoršujú účinnosť kompresie, ale zlepšujú časy vyhľadávania a môžu zlepšiť kvalitu v scénach s rýchlym pohybom. Hodnota 0 nastavuje túto hodnotu automaticky.", "transcoding_optimal_description": "Videá s vyšším ako cieľovým rozlíšením alebo videá, ktoré nie sú v prijateľnom formáte", @@ -391,6 +394,7 @@ "allow_edits": "Povoliť úpravy", "allow_public_user_to_download": "Povoľte verejnému používateľovi sťahovať", "allow_public_user_to_upload": "Umožniť verejnému používateľovi nahrávať", + "alt_text_qr_code": "Obrázok QR kódu", "anti_clockwise": "Proti smeru hodinových ručičiek", "api_key": "API Klúč", "api_key_description": "Táto hodnota sa zobrazí iba raz. Pred zatvorením okna ju určite skopírujte.", @@ -406,17 +410,17 @@ "are_these_the_same_person": "Ide o tú istú osobu?", "are_you_sure_to_do_this": "Ste si istý, že to chcete urobiť?", "asset_added_to_album": "Pridané do albumu", - "asset_adding_to_album": "Pridáva sa do albumu...", + "asset_adding_to_album": "Pridáva sa do albumu…", "asset_description_updated": "Popis média bol aktualizovaný", "asset_filename_is_offline": "Médium {filename} je offline", "asset_has_unassigned_faces": "Položka má nepriradené tváre", - "asset_hashing": "Hašovanie...", + "asset_hashing": "Hašovanie…", "asset_offline": "Médium je offline", "asset_offline_description": "Toto externý obsah sa už nenachádza na disku. Požiadajte o pomoc svojho správcu Immich.", "asset_skipped": "Preskočené", "asset_skipped_in_trash": "V koši", "asset_uploaded": "Nahrané", - "asset_uploading": "Nahráva sa...", + "asset_uploading": "Nahráva sa…", "assets": "Položky", "assets_added_count": "{count, plural, one {Pridaná # položka} few {Pridané # položky} other {Pridaných # položek}}", "assets_added_to_album_count": "Do albumu {count, plural, one {bola pridaná # položka} few {boli pridané # položky} other {bolo pridaných # položiek}}", @@ -481,6 +485,7 @@ "comments_are_disabled": "Komentáre sú vypnuté", "confirm": "Potvrdiť", "confirm_admin_password": "Potvrdiť Administrátorské Heslo", + "confirm_delete_face": "Naozaj chcete z položky odstrániť tvár osoby {name}?", "confirm_delete_shared_link": "Ste si istý, že chcete odstrániť tento zdieľaný odkaz?", "confirm_keep_this_delete_others": "Všetky ostatné položky v zásobníku budú odstránené okrem tejto položky. Naozaj chcete pokračovať?", "confirm_password": "Potvrdiť heslo", @@ -533,6 +538,7 @@ "delete_album": "Odstrániť album", "delete_api_key_prompt": "Naozaj chcete odstrániť tento API kľúč?", "delete_duplicates_confirmation": "Naozaj chcete nenávratne odstrániť tieto duplikáty?", + "delete_face": "Odstrániť tvár", "delete_key": "Odstrániť kľúč", "delete_library": "Vymazať knižnicu", "delete_link": "Odstrániť odkaz", @@ -600,6 +606,7 @@ "enabled": "Aktivovaný", "end_date": "Koncový dátum", "error": "Chyba", + "error_delete_face": "Chyba pri odstraňovaní tváre z položky", "error_loading_image": "Nepodarilo sa načítať obrázok", "error_title": "Chyba - niečo sa pokazilo", "errors": { @@ -766,8 +773,10 @@ "go_to_folder": "Prejsť do priečinka", "go_to_search": "Prejsť na vyhľadávanie", "group_albums_by": "Zoskupiť albumy podľa...", + "group_country": "Zoskupenie podľa krajiny", "group_no": "Nezoskupovať", "group_owner": "Zoskupiť podľa vlastníka", + "group_places_by": "Zoskupte miesta podľa...", "group_year": "Zoskupiť podľa roku", "has_quota": "Má kvótu", "hi_user": "Ahoj {name} ({email})", @@ -800,6 +809,7 @@ "include_shared_albums": "Zahrnúť zdieľané albumy", "include_shared_partner_assets": "Vrátane zdieľaných položiek partnera", "individual_share": "Zdieľanie jednotlivých položiek", + "individual_shares": "Individuálne zdieľanie", "info": "Informácie", "interval": { "day_at_onepm": "Každý deň v 13:00", @@ -881,6 +891,7 @@ "month": "Mesiac", "more": "Viac", "moved_to_trash": "Presunuté do koša", + "mute_memories": "Vyblednutie spomienok", "my_albums": "Moje albumy", "name": "Meno", "name_or_nickname": "Meno alebo prezývka", @@ -985,6 +996,7 @@ "pick_a_location": "Vyberte miesto", "place": "Miesto", "places": "Miesta", + "places_count": "{count, plural, one {{count, number} miesto} few {{count, number} miesta} other {{count, number} miest}}", "play": "Prehrať", "play_memories": "Prehrať spomienky", "play_motion_photo": "Prehrať pohyblivú fotku", @@ -1072,6 +1084,8 @@ "removed_from_archive": "Odstránené z archívu", "removed_from_favorites": "Odstránené z obľúbených", "removed_from_favorites_count": "{count, plural, other {Odstránených #}} z obľúbených", + "removed_memory": "Odstránená pamäť", + "removed_photo_from_memory": "Fotografia odstránená z pamäte", "removed_tagged_assets": "Odstránená značka z {count, plural, one {# položky} other {# položiek}}", "rename": "Premenovať", "repair": "Opraviť", @@ -1080,6 +1094,7 @@ "repository": "Repozitár", "require_password": "Vyžadovať heslo", "require_user_to_change_password_on_first_login": "Vyžadovať zmenu hesla po prvom prihlásení", + "rescan": "Opätovné vyhľadávanie", "reset": "Resetovať", "reset_password": "Obnoviť heslo", "reset_people_visibility": "Resetovať viditeľnosť ľudí", @@ -1108,6 +1123,8 @@ "search": "Hľadať", "search_albums": "Hľadať albumy", "search_by_context": "Hľadať s kontextom", + "search_by_description": "Vyhľadávanie podľa popisu", + "search_by_description_example": "Pešia turistika v Sape", "search_by_filename": "Hľadať s názvom alebo príponou súboru", "search_by_filename_example": "napr. IMG_1234.JPG alebo PNG", "search_camera_make": "Hľadať značku fotoaparátu...", @@ -1121,6 +1138,7 @@ "search_options": "Možnosti hľadania", "search_people": "Hľadať osoby", "search_places": "Hľadať miesta", + "search_rating": "Vyhľadávanie podľa hodnotenia...", "search_settings": "Hľadať v nastaveniach", "search_state": "Hľadať štáty...", "search_tags": "Hľadať štítky...", @@ -1167,6 +1185,7 @@ "shared_from_partner": "Fotky od {partner}", "shared_link_options": "Možnosti zdieľaných odkazov", "shared_links": "Zdieľané odkazy", + "shared_links_description": "Zdieľanie fotografií a videí pomocou odkazu", "shared_photos_and_videos_count": "{assetCount, plural, other {# zdieľané fotky a videá.}}", "shared_with_partner": "Zdieľané s {partner}", "sharing": "Zdieľanie", @@ -1189,6 +1208,7 @@ "show_person_options": "Zobrazí možnosti osoby", "show_progress_bar": "Zobrazí ukazovateľ priebehu", "show_search_options": "Zobraziť možnosti vyhľadávania", + "show_shared_links": "Zobraziť zdieľané odkazy", "show_slideshow_transition": "Zobrazí prechody v prezentácii", "show_supporter_badge": "Odznak podporovateľa", "show_supporter_badge_description": "Zobraziť odznak podporovateľa", @@ -1220,7 +1240,7 @@ "stacktrace": "Výpis zásobníku", "start": "Štart", "start_date": "Začiatočný dátum", - "state": "Stav", + "state": "Štát", "status": "Stav", "stop_motion_photo": "Stopmotion fotka", "stop_photo_sharing": "Zastaviť zdieľanie vašich fotiek?", @@ -1242,6 +1262,7 @@ "tag_created": "Vytvorená značka: {tag}", "tag_feature_description": "Prehliadanie fotiek a videá zoskupených podľa tematických značiek", "tag_not_found_question": "Neviete nájsť značku? Vytvorte novú značku.", + "tag_people": "Označiť ľudí", "tag_updated": "Upravená značka: {tag}", "tagged_assets": "Značka priradená {count, plural, one {# položke} other {# položkám}}", "tags": "Štítky", @@ -1276,11 +1297,13 @@ "unfavorite": "Odznačiť ako obľúbené", "unhide_person": "Odkryť osobu", "unknown": "Neznáme", + "unknown_country": "Neznámy štát", "unknown_year": "Neznámy rok", "unlimited": "Neobmedzené", "unlink_motion_video": "Odpojiť pohyblivé video", "unlink_oauth": "Odpojiť OAuth", "unlinked_oauth_account": "Odpojiť OAuth účet", + "unmute_memories": "Zrušenie stlmenia spomienok", "unnamed_album": "Nepomenovaný album", "unnamed_album_delete_confirmation": "Ste si istý, že chcete zmazať tento album?", "unnamed_share": "Nepomenované zdieľanie", @@ -1334,6 +1357,7 @@ "view_all": "Zobraziť všetky", "view_all_users": "Zobraziť všetkých používateľov", "view_in_timeline": "Zobraziť v časovej osi", + "view_link": "Zobraziť odkaz", "view_links": "Zobraziť odkazy", "view_name": "Zobraziť", "view_next_asset": "Zobraziť nasledujúci súbor", @@ -1350,4 +1374,4 @@ "yes": "Áno", "you_dont_have_any_shared_links": "Nemáte žiadne zdielané linky", "zoom_image": "Priblížiť obrázok" -} +} \ No newline at end of file diff --git a/i18n/sl.json b/i18n/sl.json index 2e576dc5ec..c14c3ca3c9 100644 --- a/i18n/sl.json +++ b/i18n/sl.json @@ -41,6 +41,7 @@ "backup_settings": "Nastavitve varnostnega kopiranja", "backup_settings_description": "Upravljanje nastavitev varnostnih kopij", "check_all": "Označi vse", + "cleanup": "Čiščenje", "cleared_jobs": "Razčiščeno opravilo za: {job}", "config_set_by_file": "Konfiguracija je trenutno nastavljena s konfiguracijsko datoteko", "confirm_delete_library": "Ali ste prepričani, da želite izbrisati knjižnico {library}?", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Semantično poiščite slike z uporabo vdelav CLIP", "machine_learning_smart_search_enabled": "Omogoči pametno iskanje", "machine_learning_smart_search_enabled_description": "Če je onemogočeno, slike ne bodo kodirane za pametno iskanje.", - "machine_learning_url_description": "URL strežnika za strojno učenje. Če je na voljo več kot en URL, bo vsak strežnik poskusen posamično, dokler se eden ne odzove uspešno, v vrstnem redu od prvega do zadnjega.", + "machine_learning_url_description": "URL strežnika za strojno učenje. Če je na voljo več kot en URL, bo vsak strežnik poskusen posamično, dokler se eden ne odzove uspešno, v vrstnem redu od prvega do zadnjega. Strežniki, ki se ne odzovejo, bodo začasno prezrti, dokler se spet ne vzpostavijo.", "manage_concurrency": "Upravljanje sočasnosti", "manage_log_settings": "Upravljanje nastavitev dnevnika", "map_dark_style": "Temni način", @@ -147,6 +148,8 @@ "map_settings": "Zemljevid", "map_settings_description": "Upravljanje nastavitev zemljevida", "map_style_description": "URL do teme zemljevida style.json", + "memory_cleanup_job": "Čiščenje pomnilnika", + "memory_generate_job": "Generiranje spomina", "metadata_extraction_job": "Izvleči metapodatke", "metadata_extraction_job_description": "Izvleči informacije iz metapodatkov iz vseh virov, kot so GPS, obrazi in resolucija", "metadata_faces_import_setting": "Omogoči uvoz obraza", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Največji B-okvirji", "transcoding_max_b_frames_description": "Višje vrednosti izboljšajo učinkovitost stiskanja, vendar upočasnijo kodiranje. Morda ni združljivo s strojnim pospeševanjem na starejših napravah. 0 onemogoči okvirje B, medtem ko -1 samodejno nastavi to vrednost.", "transcoding_max_bitrate": "Največja bitna hitrost", - "transcoding_max_bitrate_description": "Z nastavitvijo največje bitne hitrosti so lahko velikosti datotek bolj predvidljive ob manjši ceni kakovosti. Pri 720p so tipične vrednosti 2600k za VP9 ali HEVC ali 4500k za H.264. Onemogočeno, če je nastavljeno na 0.", + "transcoding_max_bitrate_description": "Z nastavitvijo največje bitne hitrosti so lahko velikosti datotek bolj predvidljive ob manjši ceni kakovosti. Pri 720p so tipične vrednosti 2600 kbit/s za VP9 ali HEVC ali 4500 kbit/s za H.264. Onemogočeno, če je nastavljeno na 0.", "transcoding_max_keyframe_interval": "Največji interval ključnih sličic", "transcoding_max_keyframe_interval_description": "Nastavi največjo razdaljo med ključnimi slikami. Nižje vrednosti poslabšajo učinkovitost stiskanja, vendar izboljšajo čas iskanja in lahko izboljšajo kakovost prizorov s hitrim gibanjem. 0 samodejno nastavi to vrednost.", "transcoding_optimal_description": "Videoposnetki, ki so višji od ciljne ločljivosti ali niso v sprejemljivem formatu", @@ -391,6 +394,7 @@ "allow_edits": "Dovoli urejanja", "allow_public_user_to_download": "Dovoli javnemu uporabniku prenos", "allow_public_user_to_upload": "Dovolite javnemu uporabniku nalaganje", + "alt_text_qr_code": "Slika QR kode", "anti_clockwise": "V nasprotni smeri urnega kazalca", "api_key": "API ključ", "api_key_description": "Ta vrednost bo prikazana samo enkrat. Ne pozabite jo kopirati, preden zaprete okno.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Komentarji so onemogočeni", "confirm": "Potrdi", "confirm_admin_password": "Potrdite skrbniško geslo", + "confirm_delete_face": "Ali ste prepričani, da želite izbrisati obraz osebe {name} iz sredstva?", "confirm_delete_shared_link": "Ali ste prepričani, da želite izbrisati to skupno povezavo?", "confirm_keep_this_delete_others": "Vsa druga sredstva v skladu bodo izbrisana, razen tega sredstva. Ste prepričani, da želite nadaljevati?", "confirm_password": "Potrdi geslo", @@ -533,6 +538,7 @@ "delete_album": "Izbriši album", "delete_api_key_prompt": "Ali ste prepričani, da želite izbrisati ta API ključ?", "delete_duplicates_confirmation": "Ali ste prepričani, da želite trajno izbrisati te dvojnike?", + "delete_face": "Izbriši obraz", "delete_key": "Izbriši ključ", "delete_library": "Izbriši knjižnico", "delete_link": "Izbriši povezavo", @@ -600,6 +606,7 @@ "enabled": "Omogočeno", "end_date": "Končni datum", "error": "Napaka", + "error_delete_face": "Napaka pri brisanju obraza iz sredstva", "error_loading_image": "Napaka pri nalaganju slike", "error_title": "Napaka - nekaj je šlo narobe", "errors": { @@ -884,6 +891,7 @@ "month": "Mesec", "more": "Več", "moved_to_trash": "Premaknjeno v smetnjak", + "mute_memories": "Utišaj spomine", "my_albums": "Moji albumi", "name": "Ime", "name_or_nickname": "Ime ali vzdevek", @@ -979,6 +987,7 @@ "permanently_deleted_asset": "Trajno izbrisano sredstvo", "permanently_deleted_assets_count": "Trajno izbrisano {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", "person": "Oseba", + "person_birthdate": "Rojen dne {date}", "person_hidden": "{name}{hidden, select, true { (skrita)} other {}}", "photo_shared_all_users": "Videti je, da ste svoje fotografije delili z vsemi uporabniki ali pa nimate nobenega uporabnika, s katerim bi jih delili.", "photos": "Slike", @@ -1076,6 +1085,8 @@ "removed_from_archive": "Odstranjeno iz arhiva", "removed_from_favorites": "Odstranjeno iz priljubljenih", "removed_from_favorites_count": "{count, plural, other {odstranen/ih #}} iz priljubljenih", + "removed_memory": "Odstranjen spomin", + "removed_photo_from_memory": "Odstranjena fotografija iz spomina", "removed_tagged_assets": "Odstranjena oznaka iz {count, plural, one {# sredstva} other {# sredstev}}", "rename": "Preimenuj", "repair": "Popravi", @@ -1084,6 +1095,7 @@ "repository": "Repozitorij", "require_password": "Zahtevaj geslo", "require_user_to_change_password_on_first_login": "Od uporabnika zahtevajte spremembo gesla ob prvi prijavi", + "rescan": "Ponovno skeniraj", "reset": "Ponastavi", "reset_password": "Ponastavi geslo", "reset_people_visibility": "Ponastavi vidnost ljudi", @@ -1127,6 +1139,7 @@ "search_options": "Možnosti iskanja", "search_people": "Iskanje oseb", "search_places": "Iskanje krajev", + "search_rating": "Išči po oceni ...", "search_settings": "Nastavitve iskanja", "search_state": "Iskanje dežele...", "search_tags": "Iskanje oznak...", @@ -1250,6 +1263,7 @@ "tag_created": "Ustvarjena oznaka: {tag}", "tag_feature_description": "Brskanje po fotografijah in videoposnetkih, razvrščenih po temah logičnih oznak", "tag_not_found_question": "Ne najdete oznake? Ustvarite novo oznako.", + "tag_people": "Označi osebe", "tag_updated": "Posodobljena oznaka: {tag}", "tagged_assets": "Označeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", "tags": "Oznake", @@ -1290,6 +1304,7 @@ "unlink_motion_video": "Prekini povezavo videoposnetka gibanja", "unlink_oauth": "Prekini povezavo OAuth", "unlinked_oauth_account": "Nepovezan račun OAuth", + "unmute_memories": "Vklopi zvok spominov", "unnamed_album": "Neimenovan album", "unnamed_album_delete_confirmation": "Ali ste prepričani, da želite izbrisati ta album?", "unnamed_share": "Neimenovana skupna raba", @@ -1343,6 +1358,7 @@ "view_all": "Poglej vse", "view_all_users": "Ogled vseh uporabnikov", "view_in_timeline": "Ogled na časovnici", + "view_link": "Odpri povezavo", "view_links": "Ogled povezav", "view_name": "Pogled", "view_next_asset": "Ogled naslednjega sredstva", @@ -1359,4 +1375,4 @@ "yes": "Da", "you_dont_have_any_shared_links": "Nimate nobenih skupnih povezav", "zoom_image": "Povečava slike" -} +} \ No newline at end of file diff --git a/i18n/sr_Cyrl.json b/i18n/sr_Cyrl.json index 6254b53c9a..1771f9c4aa 100644 --- a/i18n/sr_Cyrl.json +++ b/i18n/sr_Cyrl.json @@ -41,6 +41,7 @@ "backup_settings": "Подешавања резервне копије", "backup_settings_description": "Управљајте поставкама резервне копије базе података", "check_all": "Провери све", + "cleanup": "Чишћење", "cleared_jobs": "Очишћени послови за {job}", "config_set_by_file": "Конфигурацију тренутно поставља конфигурациони фајл", "confirm_delete_library": "Да ли стварно желите да избришете библиотеку {library} ?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Омогућите периодично скенирање библиотеке", "library_settings": "Спољна библиотека", "library_settings_description": "Управљајте подешавањима спољне библиотеке", - "library_tasks_description": "Обављај задатке библиотеке", + "library_tasks_description": "Скенирајте спољне библиотеке у потрази за новим и/или промењеним средствима", "library_watching_enable_description": "Пратите спољне библиотеке за промене датотека", "library_watching_settings": "Надгледање библиотеке (ЕКСПЕРИМЕНТАЛНО)", "library_watching_settings_description": "Аутоматски пратите промењене датотеке", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Потражите слике семантички користећи уграђени ЦЛИП", "machine_learning_smart_search_enabled": "Омогућите паметну претрагу", "machine_learning_smart_search_enabled_description": "Ако је oneмогућено, слике неће бити кодиране за паметну претрагу.", - "machine_learning_url_description": "URL сервера за машинско учење. Ако је наведено више од једне URL адресе, сваки сервер ће се покушавати један по један док један не одговори успешно, редом од првог до последњег.", + "machine_learning_url_description": "URL сервера за машинско учење. Ако је наведено више од једне URL адресе, сваки сервер ће се покушавати један по један док један не одговори успешно, редом од првог до последњег. Сервери који не реагују биће привремено занемарени док се не врате на мрежу.", "manage_concurrency": "Управљање паралелношћу", "manage_log_settings": "Управљајте подешавањима евиденције", "map_dark_style": "Тамни стил", @@ -147,6 +148,8 @@ "map_settings": "Подешавање мапе", "map_settings_description": "Управљајте подешавањима мапе", "map_style_description": "УРЛ до style.json мапе тема изгледа", + "memory_cleanup_job": "Чишћење меморије", + "memory_generate_job": "Генерација меморије", "metadata_extraction_job": "Извод метаподатака", "metadata_extraction_job_description": "Извуците информације о метаподацима из сваке датотеке, као што су GPS, лица и резолуција", "metadata_faces_import_setting": "Омогући (enable) увоз лица", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Омогућава хеш верификацију, не oneмогућавајте ово осим ако нисте сигурни у последице", "storage_template_migration": "Миграција шаблона за складиштење", "storage_template_migration_description": "Примените тренутни {template} на претходно отпремљене елементе", - "storage_template_migration_info": "Промене шаблона ће се применити само на нове датотеке. Да бисте ретроактивно применили шаблон на претходно отпремљене датотеке, покрените {job}.", + "storage_template_migration_info": "Шаблон за складиштење ће претворити све екстензије у мала слова. Промене шаблона ће се применити само на нове датотеке. Да бисте ретроактивно применили шаблон на претходно отпремљене датотеке, покрените {job}.", "storage_template_migration_job": "Посао миграције складишта", "storage_template_more_details": "За више детаља о овој функцији погледајте Шаблон за складиште и његове импликације", "storage_template_onboarding_description": "Када је омогућена, ова функција ће аутоматски организовати датотеке на основу шаблона који дефинише корисник. Због проблема са стабилношћу ова функција је подразумевано искључена. За више информација погледајте документацију.", @@ -391,6 +394,7 @@ "allow_edits": "Дозволи уређење", "allow_public_user_to_download": "Дозволите јавном кориснику да преузме (download-uje)", "allow_public_user_to_upload": "Дозволи јавном кориснику да отпреми (уплоад-ује)", + "alt_text_qr_code": "Слика QR кода", "anti_clockwise": "У смеру супротном од казаљке на сату", "api_key": "АПИ кључ (key)", "api_key_description": "Ова вредност ће бити приказана само једном. Обавезно копирајте пре него што затворите прозор.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Коментари су oneмогућени", "confirm": "Потврдите", "confirm_admin_password": "Потврди Административну Лозинку", + "confirm_delete_face": "Да ли сте сигурни да желите да избришете особу {name} из дела?", "confirm_delete_shared_link": "Да ли сте сигурни да желите да избришете овај дељени link?", "confirm_keep_this_delete_others": "Свe осталe датотекe у групи ће бити избрисанe осим овe датотекe. Да ли сте сигурни да желите да наставите?", "confirm_password": "Поново унеси шифру", @@ -533,6 +538,7 @@ "delete_album": "Обриши албум", "delete_api_key_prompt": "Да ли сте сигурни да желите да избришете овај АПИ кључ (key)?", "delete_duplicates_confirmation": "Да ли сте сигурни да желите да трајно избришете ове дупликате?", + "delete_face": "Избриши особу", "delete_key": "Избриши кључ", "delete_library": "Обриши библиотеку", "delete_link": "Обриши везу", @@ -600,6 +606,7 @@ "enabled": "Омогућено (enabled)", "end_date": "Крајњи датум", "error": "Грешка", + "error_delete_face": "Грешка при брисању особе из дела", "error_loading_image": "Грешка при учитавању слике", "error_title": "Грешка – Нешто је пошло наопако", "errors": { @@ -884,6 +891,7 @@ "month": "Месец", "more": "Више", "moved_to_trash": "Премештено у смеће", + "mute_memories": "Пригуши сећања", "my_albums": "Моји албуми", "name": "Име", "name_or_nickname": "Име или надимак", @@ -1076,6 +1084,8 @@ "removed_from_archive": "Уклоњено из архиве", "removed_from_favorites": "Уклоњено из омиљених (фаворитес)", "removed_from_favorites_count": "{count, plural, other {Уклоњено #}} из омиљених", + "removed_memory": "Уклоњена меморија", + "removed_photo_from_memory": "Слика је уклоњена из меморије", "removed_tagged_assets": "Уклоњена ознака (tag) из {count, plural, one {# датотеке} other {# датотека}}", "rename": "Преименуј", "repair": "Поправи", @@ -1084,6 +1094,7 @@ "repository": "Репозиторијум (Repository)", "require_password": "Потребна лозинка", "require_user_to_change_password_on_first_login": "Захтевати од корисника да промени лозинку при првом пријављивању", + "rescan": "Поново скенирај", "reset": "Ресетовати", "reset_password": "Ресетовати лозинку", "reset_people_visibility": "Ресетујте видљивост особа", @@ -1127,6 +1138,7 @@ "search_options": "Опције претраге", "search_people": "Претражи особе", "search_places": "Претражи места", + "search_rating": "Претрага по оцени...", "search_settings": "Претрага подешавања", "search_state": "Тражи регион...", "search_tags": "Претражи ознаке (tags)...", @@ -1250,6 +1262,7 @@ "tag_created": "Направљена ознака (tag): {tag}", "tag_feature_description": "Прегледавање фотографија и видео снимака груписаних по логичним темама ознака", "tag_not_found_question": "Не можете да пронађете ознаку (tag)? Направите нову ознаку", + "tag_people": "Означите људе", "tag_updated": "Ажурирана ознака (tag): {tag}", "tagged_assets": "Означено (tagged) {count, plural, one {# датотека} other {# датотеке}}", "tags": "Ознаке (tags)", @@ -1290,6 +1303,7 @@ "unlink_motion_video": "Прекините везу са видео снимком", "unlink_oauth": "Прекини везу са Oauth-om", "unlinked_oauth_account": "Опозвана веза OAuth налога", + "unmute_memories": "Укључи успомене", "unnamed_album": "Неименовани албум", "unnamed_album_delete_confirmation": "Да ли сте сигурни да желите да избришете овај албум?", "unnamed_share": "Неименовано делење", @@ -1343,6 +1357,7 @@ "view_all": "Прикажи Све", "view_all_users": "Прикажи све кориснике", "view_in_timeline": "Прикажи у временској линији", + "view_link": "Погледај везу", "view_links": "Прикажи везе", "view_name": "Погледати", "view_next_asset": "Погледајте следећу датотеку", diff --git a/i18n/sr_Latn.json b/i18n/sr_Latn.json index 6efa67c9a4..f6743c012b 100644 --- a/i18n/sr_Latn.json +++ b/i18n/sr_Latn.json @@ -41,6 +41,7 @@ "backup_settings": "Podešavanja rezervne kopije", "backup_settings_description": "Upravljajte postavkama rezervne kopije baze podataka", "check_all": "Proveri sve", + "cleanup": "Čišćenje", "cleared_jobs": "Očišćeni poslovi za: {job}", "config_set_by_file": "Konfiguraciju trenutno postavlja konfiguracioni fajl", "confirm_delete_library": "Da li stvarno želite da izbrišete biblioteku {library} ?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Omogućite periodično skeniranje biblioteke", "library_settings": "Spoljna biblioteka", "library_settings_description": "Upravljajte podešavanjima spoljne biblioteke", - "library_tasks_description": "Obavljaj zadatke biblioteke", + "library_tasks_description": "Skenirajte spoljne biblioteke u potrazi za novim i/ili promenjenim sredstvima", "library_watching_enable_description": "Pratite spoljne biblioteke za promene datoteka", "library_watching_settings": "Nadgledanje biblioteke (EKSPERIMENTALNO)", "library_watching_settings_description": "Automatski pratite promenjene datoteke", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Potražite slike semantički koristeći ugrađeni CLIP", "machine_learning_smart_search_enabled": "Omogućite pametnu pretragu", "machine_learning_smart_search_enabled_description": "Ako je onemogućeno, slike neće biti kodirane za pametnu pretragu.", - "machine_learning_url_description": "URL servera za mašinsko učenje. Ako je obezbeđeno više URL-ova, svaki server će biti pokušan redom, jedan po jedan, dok jedan ne odgovori uspešno, po redosledu od prvog do poslednjeg.", + "machine_learning_url_description": "URL servera za mašinsko učenje. Ako je obezbeđeno više URL-ova, svaki server će biti pokušan redom, jedan po jedan, dok jedan ne odgovori uspešno, po redosledu od prvog do poslednjeg. Serveri koji ne reaguju biće privremeno zanemareni dok se ne vrate na mrežu.", "manage_concurrency": "Upravljanje paralelnošću", "manage_log_settings": "Upravljajte podešavanjima evidencije", "map_dark_style": "Tamni stil", @@ -147,6 +148,8 @@ "map_settings": "Podešavanje mape", "map_settings_description": "Upravljajte podešavanjima mape", "map_style_description": "URL do style.json mape tema izgleda", + "memory_cleanup_job": "Čišćenje memorije", + "memory_generate_job": "Generacija memorije", "metadata_extraction_job": "Izvod metapodataka", "metadata_extraction_job_description": "Izvucite informacije o metapodacima iz svake datoteke, kao što su GPS, lica i rezolucija", "metadata_faces_import_setting": "Omogućite (enable) dodavanje lica", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Omogućava heš verifikaciju, ne onemogućavajte ovo osim ako niste sigurni u posledice", "storage_template_migration": "Migracija šablona za skladištenje", "storage_template_migration_description": "Primenite trenutni {template} na prethodno otpremljene elemente", - "storage_template_migration_info": "Promene šablona će se primeniti samo na nove datoteke. Da biste retroaktivno primenili šablon na prethodno otpremljene datoteke, pokrenite {job}.", + "storage_template_migration_info": "Šablon za skladištenje će pretvoriti sve ekstenzije u mala slova. Promene šablona će se primeniti samo na nove datoteke. Da biste retroaktivno primenili šablon na prethodno otpremljene datoteke, pokrenite {job}.", "storage_template_migration_job": "Posao migracije skladišta", "storage_template_more_details": "Za više detalja o ovoj funkciji pogledajte Šablon za skladište i njegove implikacije", "storage_template_onboarding_description": "Kada je omogućena, ova funkcija će automatski organizovati datoteke na osnovu šablona koji definiše korisnik. Zbog problema sa stabilnošću ova funkcija je podrazumevano isključena. Za više informacija pogledajte dokumentaciju.", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Maksimalni B-kadri", "transcoding_max_b_frames_description": "Više vrednosti poboljšavaju efikasnost kompresije, ali usporavaju kodiranje. Možda nije kompatibilno sa hardverskim ubrzanjem na starijim uređajima. 0 onemogućava B-kadre, dok -1 automatski postavlja ovu vrednost.", "transcoding_max_bitrate": "Maksimalni bitrate", - "transcoding_max_bitrate_description": "Podešavanje maksimalnog bitrate-a može učiniti veličine datoteka predvidljivijim uz manju cenu kvaliteta. Pri 720p, tipične vrednosti su 2600k za VP9 ili HEVC, ili 4500k za H.264. Onemogućeno ako je postavljeno na 0.", + "transcoding_max_bitrate_description": "Podešavanje maksimalnog bitrate-a može učiniti veličine datoteka predvidljivijim uz manju cenu kvaliteta. Pri 720p, tipične vrednosti su 2600 kbit/s za VP9 ili HEVC, ili 4500 kbit/s za H.264. Onemogućeno ako je postavljeno na 0.", "transcoding_max_keyframe_interval": "Maksimalni interval keyframe-a", "transcoding_max_keyframe_interval_description": "Postavlja maksimalnu udaljenost kadrova između ključnih kadrova. Niže vrednosti pogoršavaju efikasnost kompresije, ali poboljšavaju vreme traženja i mogu poboljšati kvalitet scena sa brzim kretanjem. 0 automatski postavlja ovu vrednost.", "transcoding_optimal_description": "Video snimci veći od ciljne rezolucije ili nisu u prihvaćenom formatu", @@ -391,6 +394,7 @@ "allow_edits": "Dozvoli uređenje", "allow_public_user_to_download": "Dozvolite javnom korisniku da preuzme (download-uje)", "allow_public_user_to_upload": "Dozvoli javnom korisniku da otpremi (upload-uje)", + "alt_text_qr_code": "Slika QR koda", "anti_clockwise": "U smeru suprotnom od kazaljke na satu", "api_key": "API ključ (key)", "api_key_description": "Ova vrednost će biti prikazana samo jednom. Obavezno kopirajte pre nego što zatvorite prozor.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Komentari su onemogućeni", "confirm": "Potvrdi", "confirm_admin_password": "Potvrdi Administrativnu Lozinku", + "confirm_delete_face": "Da li ste sigurni da želite da izbrišete osobu {name} iz dela?", "confirm_delete_shared_link": "Da li ste sigurni da želite da izbrišete ovaj deljeni link?", "confirm_keep_this_delete_others": "Sve ostale datoteke u grupi će biti izbrisane osim ove datoteke. Da li ste sigurni da želite da nastavite?", "confirm_password": "Ponovo unesi šifru", @@ -533,6 +538,7 @@ "delete_album": "Obriši album", "delete_api_key_prompt": "Da li ste sigurni da želite da izbrišete ovaj API ključ (key)?", "delete_duplicates_confirmation": "Da li ste sigurni da želite da trajno izbrišete ove duplikate?", + "delete_face": "Izbriši osobu", "delete_key": "Izbriši ključ", "delete_library": "Obriši biblioteku", "delete_link": "Obriši vezu", @@ -600,6 +606,7 @@ "enabled": "Omogućeno (Enabled)", "end_date": "Krajnji datum", "error": "Greška", + "error_delete_face": "Greška pri brisanju osobe iz dela", "error_loading_image": "Greška pri učitavanju slike", "error_title": "Greška – Nešto je pošlo naopako", "errors": { @@ -884,6 +891,7 @@ "month": "Mesec", "more": "Više", "moved_to_trash": "Premešteno u smeće", + "mute_memories": "Priguši sećanja", "my_albums": "Moji albumi", "name": "Ime", "name_or_nickname": "Ime ili nadimak", @@ -1076,6 +1084,8 @@ "removed_from_archive": "Uklonjeno iz arhive", "removed_from_favorites": "Uklonjeno iz omiljenih (favorites)", "removed_from_favorites_count": "{count, plural, other {Uklonjeno #}} iz omiljenih", + "removed_memory": "Uklonjena memorija", + "removed_photo_from_memory": "Slika je uklonjena iz memorije", "removed_tagged_assets": "Uklonjena oznaka iz {count, plural, one {# datoteke} other {# datoteka}}", "rename": "Preimenuj", "repair": "Popravi", @@ -1084,6 +1094,7 @@ "repository": "Repozitorijum (Repository)", "require_password": "Potrebna lozinka", "require_user_to_change_password_on_first_login": "Zahtevati od korisnika da promeni lozinku pri prvom prijavljivanju", + "rescan": "Ponovo skeniraj", "reset": "Resetovati", "reset_password": "Resetovati lozinku", "reset_people_visibility": "Resetujte vidljivost osoba", @@ -1127,6 +1138,7 @@ "search_options": "Opcije pretrage", "search_people": "Pretraži osobe", "search_places": "Pretraži mesta", + "search_rating": "Pretraga po oceni...", "search_settings": "Pretraga podešavanja", "search_state": "Traži region...", "search_tags": "Pretraži oznake (tags)...", @@ -1250,6 +1262,7 @@ "tag_created": "Napravljena oznaka (tag): {tag}", "tag_feature_description": "Pregledavanje fotografija i video snimaka grupisanih po logičnim temama oznaka", "tag_not_found_question": "Ne možete da pronađete oznaku (tag)? Napravite novu oznaku", + "tag_people": "Označite ljude", "tag_updated": "Ažurirana oznaka (tag): {tag}", "tagged_assets": "Označeno (tagged) {count, plural, one {# datoteka} other {# datoteke}}", "tags": "Oznake (tags)", @@ -1290,6 +1303,7 @@ "unlink_motion_video": "Odveži video od slike", "unlink_oauth": "Prekini vezu sa Oauth-om", "unlinked_oauth_account": "Opozvana veza OAuth naloga", + "unmute_memories": "Uključi uspomene", "unnamed_album": "Neimenovani album", "unnamed_album_delete_confirmation": "Da li ste sigurni da želite da izbrišete ovaj album?", "unnamed_share": "Neimenovano delenje", @@ -1343,6 +1357,7 @@ "view_all": "Prikaži Sve", "view_all_users": "Prikaži sve korisnike", "view_in_timeline": "Prikaži u vremenskoj liniji", + "view_link": "Pogledaj vezu", "view_links": "Prikaži veze", "view_name": "Pogledati", "view_next_asset": "Pogledajte sledeću datoteku", @@ -1359,4 +1374,4 @@ "yes": "Da", "you_dont_have_any_shared_links": "Nemate nijedno deljenje veze", "zoom_image": "Zumiraj sliku" -} +} \ No newline at end of file diff --git a/i18n/sv.json b/i18n/sv.json index 8cb756bef3..f57d112c34 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -20,7 +20,7 @@ "add_partner": "Lägg till partner", "add_path": "Lägg till sökväg", "add_photos": "Lägg till foton", - "add_to": "Lägg till i...…", + "add_to": "Lägg till i…", "add_to_album": "Lägg till i album", "add_to_shared_album": "Lägg till i delat album", "add_url": "Lägg till URL", @@ -41,6 +41,7 @@ "backup_settings": "Säkerhetskopieringsinställningar", "backup_settings_description": "Hantera inställningar för säkerhetskopiering av databas", "check_all": "Välj alla", + "cleanup": "Uppstädning", "cleared_jobs": "Rensade jobben för:{job}", "config_set_by_file": "Konfigurationen är satt av en konfigurationsfil", "confirm_delete_library": "Är du säker på att du vill radera {library} album?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Aktivera periodisk biblioteksskanning", "library_settings": "Externa bibliotek", "library_settings_description": "Hantera inställningar för externa bibliotek", - "library_tasks_description": "Kör biblioteksjobb", + "library_tasks_description": "Sök igenom externa bibliotek efter nya och/eller ändrade objekt", "library_watching_enable_description": "Titta på externa bibliotek för filändringar", "library_watching_settings": "Titta på bibliotek (EXPERIMENTELLT)", "library_watching_settings_description": "Titta automatiskt efter ändrade filer", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Sök semantiskt efter bilder med hjälp av CLIP-inbäddningar", "machine_learning_smart_search_enabled": "Aktivera smart sökning", "machine_learning_smart_search_enabled_description": "Om inaktiverat kommer bilder inte att kodas för smart sökning.", - "machine_learning_url_description": "Maskininlärningsserverns URL. Om det är mer än en URL tillagd så kommer ett försök per URL att utföras tills någon av dom svarar, försöken görs i kronologisk ordning.", + "machine_learning_url_description": "Maskininlärningsserverns URL. Om det är mer än en URL tillagd så kommer ett försök per URL att utföras tills någon av dom svarar, försöken görs i kronologisk ordning. Servrar som inte svarar kommer tillfälligt ignoreras tills de är nåbara igen.", "manage_concurrency": "Hantera samtidighet", "manage_log_settings": "Hantera logginställningar", "map_dark_style": "Mörk stil", @@ -147,6 +148,8 @@ "map_settings": "Karta", "map_settings_description": "Hantera kartinställningar", "map_style_description": "URL till en style.json-karto tema", + "memory_cleanup_job": "Rensa minnen", + "memory_generate_job": "Generera minnen", "metadata_extraction_job": "Extrahera metadata", "metadata_extraction_job_description": "Läs in metadata (t.ex. GPS, ansikten och upplösning) för varje resurs", "metadata_faces_import_setting": "Aktivera import av ansikten", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Aktiverar hash-verifiering, deaktiviera inte om du inte är säker på implikationerna", "storage_template_migration": "Migrering av Lagringsmallar", "storage_template_migration_description": "Applicera aktiv {template} till tidigare uppladdade resurser", - "storage_template_migration_info": "Ändringar i mall gäller endast nya resurser. För att retoaktivt tillämpa mallen på tidigare uppladdade resurser kör {job}.", + "storage_template_migration_info": "Lagringsmallen kommer konvertera alla filändelser till gemena bokstäver. Ändringar gäller endast för nya resurser, för att retoaktivt tillämpa mallen på befintliga resurser kör {job}.", "storage_template_migration_job": "Lagringsmall migreringsjobb", "storage_template_more_details": "För mer information om den här funktionen se Lagringsmall och dess konsekvenser", "storage_template_onboarding_description": "Vid aktivering organiserar denna funktion automatiskt filer baserat på en användardefinierad mall. På grunda av stabilitetsproblem är denna funktion avstängd som standard, för mer information se dokumentation.", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "Max B-ramar", "transcoding_max_b_frames_description": "Högre värden förbättrar kompressionseffektiviteten, men saktar ner kodningen. Kan vara inkompatibel med hårdvaruacceleration på äldre enheter. 0 avaktiverar B-frames, medan -1 anger detta värde automatiskt.", "transcoding_max_bitrate": "Max bithastighet", - "transcoding_max_bitrate_description": "En maximal bitrate kan göra filstorlekar mer förutsägbara till en liten kostnad på kvalitet. Vid 720p är typiska värden 2600k för VP9 eller HEVC, eller 4500k för H.264. Inaktiverad om satt till 0.", + "transcoding_max_bitrate_description": "En maximal bitrate kan göra filstorlekar mer förutsägbara till en liten kostnad på kvalitet. Vid 720p är typiska värden 2600 kbit/s för VP9 eller HEVC, eller 4500 kbit/s för H.264. Inaktiverad om satt till 0.", "transcoding_max_keyframe_interval": "Max nyckelbildruteintervall", "transcoding_max_keyframe_interval_description": "Sätter det maximala bildruteavståndet mellan nyckelbildrutor. Lägre värden försämrar kompressionseffektiviteten, men förbättrar söktiderna och kan förbättra kvaliteten i scener med snabb rörelse. 0 ställer in detta värde automatiskt.", "transcoding_optimal_description": "Videor som är högre än mållösning eller inte i ett accepterat format", @@ -391,6 +394,7 @@ "allow_edits": "Tillåt redigeringar", "allow_public_user_to_download": "Tillåt offentlig användare att ladda ner", "allow_public_user_to_upload": "Tillåt en offentlig användare att ladda upp", + "alt_text_qr_code": "QR-kod", "anti_clockwise": "Moturs", "api_key": "API Nyckel", "api_key_description": "Detta värde kommer bara att visas en gång. Se till att kopiera det innan du stänger fönstret.", @@ -420,7 +424,7 @@ "assets": "Objekt", "assets_added_count": "La till {count, plural, one {# asset} other {# assets}}", "assets_added_to_album_count": "Lade till {count, plural, one {# asset} other {# assets}} i albumet", - "assets_added_to_name_count": "Lade till {count, plural, one {# asset} other {# assets}} till {hasName, select, true {{name}} other {new album}}", + "assets_added_to_name_count": "Lade till {count, plural, one {# objekt} other {# objekt}} till {hasName, select, true {{name}} other {nytt album}}", "assets_count": "{count, plural, one {# objekt} other {# objekt}}", "assets_moved_to_trash_count": "Flyttade {count, plural, one {# asset} other {# assets}} till papperskorgen", "assets_permanently_deleted_count": "Raderad permanent {count, plural, one {# asset} other {# assets}}", @@ -481,6 +485,7 @@ "comments_are_disabled": "Kommentarer är avstängda", "confirm": "Bekräfta", "confirm_admin_password": "Bekräfta administratörslösenord", + "confirm_delete_face": "Är du säker på att du vill ta bort {name}'s ansikte från objektet?", "confirm_delete_shared_link": "Är du säker på att du vill ta bort den här delade länken?", "confirm_keep_this_delete_others": "Alla tillgångar förutom den här tas bort från stacken. Är du säker på att du vill fortsätta?", "confirm_password": "Bekräfta lösenord", @@ -533,6 +538,7 @@ "delete_album": "Ta bort album", "delete_api_key_prompt": "Är du säker på att du vill ta bort denna API-nyckel?", "delete_duplicates_confirmation": "Är du säker på att du vill ta bort dessa dubbletter permanent?", + "delete_face": "Ta bort ansikte", "delete_key": "Ta bort nyckel", "delete_library": "Ta bort bibliotek", "delete_link": "Ta bort länk", @@ -600,13 +606,14 @@ "enabled": "Aktiverad", "end_date": "Slutdatum", "error": "Fel", + "error_delete_face": "Fel uppstod när ansikte skulle tas bort från objektet", "error_loading_image": "Fel vid bildladdning", "error_title": "Fel – något gick fel", "errors": { "cannot_navigate_next_asset": "Det går inte att navigera till nästa objekt", "cannot_navigate_previous_asset": "Det går inte att navigera till föregående objekt", "cant_apply_changes": "Det går inte att tillämpa ändringar", - "cant_change_activity": "Kan inte {enabled, select, true {disable} other {enable}} aktivitet", + "cant_change_activity": "Kan inte {enabled, select, true {avaktivera} other {aktivera}} aktivitet", "cant_change_asset_favorite": "Det går inte att byta favorit mot objekt", "cant_change_metadata_assets_count": "Det går inte att ändra metadata för {count, plural, one {# asset} other {# assets}}", "cant_get_faces": "Kan inte få ansikten", @@ -646,7 +653,7 @@ "unable_to_add_exclusion_pattern": "Det gick inte att lägga till uteslutningsmönster", "unable_to_add_import_path": "Det gick inte att lägga till importsökväg", "unable_to_add_partners": "Kunde inte lägga till partners", - "unable_to_add_remove_archive": "Det går inte att {archived, select, true {remove asset from} other {add asset to}} arkiv", + "unable_to_add_remove_archive": "Det går inte att {archived, select, true {ta bort objekt från} other {lägga till objekt till}} arkiv", "unable_to_add_remove_favorites": "Det går inte att {favorite, select, true {add asset to} other {remove asset from}} favoriter", "unable_to_archive_unarchive": "Det går inte att {archived, select, true {archive} other {archive}}", "unable_to_change_album_user_role": "Kunde inte ändra albumanvändarens roll", @@ -782,7 +789,7 @@ "host": "Värd", "hour": "Timme", "image": "Bild", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} tagen {date}", + "image_alt_text_date": "{isVideo, select, true {Video} other {Bild}} tagen {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} tagen med {person1} den {date}", "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} tagen med {person1} och {person2} den {date}", "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} tagen med {person1}, {person2}, och {person3} den {date}", @@ -812,7 +819,7 @@ }, "invite_people": "Bjud in personer", "invite_to_album": "Bjuder in till album", - "items_count": "{count, plural, one {# item} other {# items}}", + "items_count": "{count, plural, one {# objekt} other {# objekt}}", "jobs": "Jobb", "keep": "Behåll", "keep_all": "Behåll alla", @@ -884,6 +891,7 @@ "month": "Månad", "more": "Mer", "moved_to_trash": "Flyttad till papperskorgen", + "mute_memories": "Tysta minnen", "my_albums": "Mina album", "name": "Namn", "name_or_nickname": "Namn eller smeknamn", @@ -979,6 +987,7 @@ "permanently_deleted_asset": "Permanent raderad tillgång", "permanently_deleted_assets_count": "Permanent borttagning av {count, plural, one {# asset} other {# assets}}", "person": "Person", + "person_birthdate": "Född {date}", "person_hidden": "{name}{hidden, select, true { (dold)} other {}}", "photo_shared_all_users": "Du har antingen delat dina foton med alla användare eller så har du inga användare att dela dem med.", "photos": "Foton", @@ -1041,7 +1050,7 @@ "rating_clear": "Ta bort betyg", "rating_count": "{count, plural, one {# stjärna} other {# stjärnor}}", "rating_description": "Visa EXIF betyget i informationspanelen", - "reaction_options": "Hovringstext för knappen som visar åtgärder man kan utföra på en reaktion.", + "reaction_options": "Alternativ för reaktion", "read_changelog": "Läs ändringslogg", "reassign": "Omfördela", "reassigned_assets_to_existing_person": "Tilldelade om {count, plural, one {# objekt} other {# objekt}} till {name, select, null {an existing person} other {{name}}}", @@ -1076,6 +1085,8 @@ "removed_from_archive": "Borttagen från arkivet", "removed_from_favorites": "Borttagen från favoriter", "removed_from_favorites_count": "{count, plural, other {Tog bort #}} från favoriter", + "removed_memory": "Tog bort minne", + "removed_photo_from_memory": "Tog bort foto från minnet", "removed_tagged_assets": "Tog bort tagg från {count, plural, one {# objekt} other {# objekt}}", "rename": "Döp om", "repair": "Reparera", @@ -1084,6 +1095,7 @@ "repository": "Förvar", "require_password": "Kräver lösenord", "require_user_to_change_password_on_first_login": "Kräv att användaren ändrar lösenord vid första inloggning", + "rescan": "Skanna igen", "reset": "Återställ", "reset_password": "Nollställ lösenord", "reset_people_visibility": "Återställ personers synlighet", @@ -1127,6 +1139,7 @@ "search_options": "Sökinställningar", "search_people": "Sök personer", "search_places": "Sök platser", + "search_rating": "Sök efter betyg...", "search_settings": "Sök inställningar", "search_state": "Sök stat...", "search_tags": "Sök taggar...", @@ -1149,7 +1162,7 @@ "select_photos": "Välj foton", "select_trash_all": "Släng alla", "selected": "Valda", - "selected_count": "{count, plural, other {# selected}}", + "selected_count": "{count, plural, other {# valda}}", "send_message": "Skicka meddelande", "send_welcome_email": "Skicka välkomstmejl", "server_offline": "Servern offline", @@ -1250,6 +1263,7 @@ "tag_created": "Skapade tagg: {tag}", "tag_feature_description": "Bläddra bland foton och videor grupperade efter logiska taggar", "tag_not_found_question": "Kan du inte hitta en tagg? Skapa en ny tagg.", + "tag_people": "Tagga Personer", "tag_updated": "Uppdaterade tagg: {tag}", "tagged_assets": "Taggade {count, plural, one {# objekt} other {# objekt}}", "tags": "Taggar", @@ -1290,6 +1304,7 @@ "unlink_motion_video": "Ta bort länken till rörlig video", "unlink_oauth": "Ta bort länken till OAuth", "unlinked_oauth_account": "Olänkat OAuth-konto", + "unmute_memories": "Slå på ljud för minnen", "unnamed_album": "Namnlöst Album", "unnamed_album_delete_confirmation": "Är du säker på att du vill ta bort detta album?", "unnamed_share": "Namnlös delning", @@ -1343,6 +1358,7 @@ "view_all": "Visa alla", "view_all_users": "Visa alla användare", "view_in_timeline": "Visa i tidslinjen", + "view_link": "Visa länk", "view_links": "Visa länkar", "view_name": "Visa", "view_next_asset": "Visa nästa objekt", @@ -1359,4 +1375,4 @@ "yes": "Ja", "you_dont_have_any_shared_links": "Du har inga delade länkar", "zoom_image": "Zooma bild" -} +} \ No newline at end of file diff --git a/i18n/ta.json b/i18n/ta.json index c3d13dbdf0..2c58867226 100644 --- a/i18n/ta.json +++ b/i18n/ta.json @@ -297,7 +297,7 @@ "transcoding_max_b_frames": "அதிகபட்ச பி-பிரேம்கள்", "transcoding_max_b_frames_description": "அதிக மதிப்புகள் சுருக்க செயல்திறனை மேம்படுத்துகின்றன, ஆனால் குறியாக்கத்தை மெதுவாக்குகின்றன. பழைய சாதனங்களில் வன்பொருள் முடுக்கம் உடன் பொருந்தாது. 0 பி -பிரேம்களை முடக்குகிறது, அதே நேரத்தில் -1 இந்த மதிப்பை தானாக அமைக்கிறது.", "transcoding_max_bitrate": "அதிகபட்ச பிட்ரேட்", - "transcoding_max_bitrate_description": "அதிகபட்ச பிட்ரேட்டை அமைப்பது கோப்பு அளவுகளை ஒரு சிறிய செலவில் தரத்திற்கு கணிக்கக்கூடியதாக மாற்றும். 720p இல், வழக்கமான மதிப்புகள் VP9 அல்லது HEVC க்கு 2600K அல்லது H.264 க்கு 4500K ஆகும். 0 என அமைக்கப்பட்டால் முடக்கப்பட்டது.", + "transcoding_max_bitrate_description": "அதிகபட்ச பிட்ரேட்டை அமைப்பது கோப்பு அளவுகளை ஒரு சிறிய செலவில் தரத்திற்கு கணிக்கக்கூடியதாக மாற்றும். 720p இல், வழக்கமான மதிப்புகள் VP9 அல்லது HEVC க்கு 2600 kbit/s அல்லது H.264 க்கு 4500 kbit/s ஆகும். 0 என அமைக்கப்பட்டால் முடக்கப்பட்டது.", "transcoding_max_keyframe_interval": "அதிகபட்ச கீஃப்ரேம் இடைவெளி", "transcoding_max_keyframe_interval_description": "கீஃப்ரேம்களுக்கு இடையில் அதிகபட்ச பிரேம் தூரத்தை அமைக்கிறது. குறைந்த மதிப்புகள் சுருக்க செயல்திறனை மோசமாக்குகின்றன, ஆனால் தேடல் நேரங்களை மேம்படுத்துகின்றன, மேலும் வேகமான இயக்கத்துடன் காட்சிகளில் தரத்தை மேம்படுத்தலாம். 0 இந்த மதிப்பை தானாக அமைக்கிறது.", "transcoding_optimal_description": "இலக்கு தீர்மானத்தை விட உயர்ந்த வீடியோக்கள் அல்லது ஏற்றுக்கொள்ளப்பட்ட வடிவத்தில் இல்லை", @@ -1339,4 +1339,4 @@ "yes": "ஆம்", "you_dont_have_any_shared_links": "உங்களிடம் பகிரப்பட்ட இணைப்புகள் எதுவும் இல்லை", "zoom_image": "பெரிதாக்க படம்" -} +} \ No newline at end of file diff --git a/i18n/th.json b/i18n/th.json index 3e0523c886..f86ea9cd55 100644 --- a/i18n/th.json +++ b/i18n/th.json @@ -299,7 +299,7 @@ "transcoding_max_b_frames": "B-frames สูงสุด", "transcoding_max_b_frames_description": "ค่าที่สูงขึ้นจะช่วยเพิ่มประสิทธิภาพในการบีบอัด แต่จะทำให้การเข้ารหัสช้าลง อาจไม่สามารถใช้งานร่วมกับการเร่งความเร็วฮาร์ดแวร์บนอุปกรณ์เก่าได้ ค่าที่เป็น 0 จะปิดการใช้งาน B-frame ในขณะที่ค่า -1 จะตั้งค่าค่านี้โดยอัตโนมัติ", "transcoding_max_bitrate": "bitrate สูงสุด", - "transcoding_max_bitrate_description": "การตั้งค่า bitrate สูงสุดจะสามารถคาดเดาขนาดไฟล์ได้มากขึ้นโดยไม่กระทบคุณภาพ สำหรับความคมชัด 720p ค่าทั่วไปคือ 2600k สําหรับ VP9 หรือ HEVC, 4500k สําหรับ H.264 ปิดการตั้งค่าเมี่อตั้งค่าเป็น 0", + "transcoding_max_bitrate_description": "การตั้งค่า bitrate สูงสุดจะสามารถคาดเดาขนาดไฟล์ได้มากขึ้นโดยไม่กระทบคุณภาพ สำหรับความคมชัด 720p ค่าทั่วไปคือ 2600 kbit/s สําหรับ VP9 หรือ HEVC, 4500 kbit/s สําหรับ H.264 ปิดการตั้งค่าเมี่อตั้งค่าเป็น 0", "transcoding_max_keyframe_interval": "ช่วงเวลาสูงสุดระหว่างกราฟฟ์เคลื่อนไหว", "transcoding_max_keyframe_interval_description": "ตั้งค่าระยะห่างสูงสุดระหว่างคีย์เฟรม (keyframes) ค่าที่ต่ำลงจะทำให้ประสิทธิภาพการบีบอัดแย่ลง แต่จะช่วยปรับปรุงเวลาในการค้นหาภาพ (seek times) และอาจช่วยปรับปรุงคุณภาพในฉากที่มีการเคลื่อนไหวเร็ว ค่า 0 จะตั้งค่านี้โดยอัตโนมัติ", "transcoding_optimal_description": "วีดิโอมีความคมชัดสูงกว่าเป้าหมายหรืออยู่ในรูปแบบที่รับไม่ได้", @@ -342,7 +342,7 @@ "user_delete_immediately": "บัญชีและสื่อของ {user} จะอยู่ในคิวสำหรับการลบถาวร โดยทันที", "user_delete_immediately_checkbox": "คิวผู้ใช้และสื่อสำหรับการลบทันที", "user_management": "การจัดการผู้ใช้", - "user_password_has_been_reset": "รหัสผ่านของผู้ใช้ {user} ถูกตั้งค่าใหม่แล้ว", + "user_password_has_been_reset": "รหัสผ่านของผู้ใช้ถูกตั้งค่าใหม่แล้ว:", "user_password_reset_description": "รหัสผ่านของผู้ใช้จะถูกตั้งค่าใหม่และส่งไปยังอีเมลที่ลงทะเบียน", "user_restore_description": "บัญชีของ {user} จะได้รับการคืนค่า", "user_restore_scheduled_removal": "กู้คืนผู้ใช้ - กำหนดการลบในวันที่ {date, date,long}", @@ -404,7 +404,7 @@ "are_these_the_same_person": "เป็นคนเดียวกันหรือไม่?", "are_you_sure_to_do_this": "คุณแน่ใจว่าต้องการทำสิ่งนี้หรือไม่?", "asset_added_to_album": "เพิ่มไปยังอัลบั้มแล้ว", - "asset_adding_to_album": "กำลังเพิ่มไปยังอัลบั้ม...", + "asset_adding_to_album": "กำลังเพิ่มไปยังอัลบั้ม…", "asset_description_updated": "อัปเดตรายละเอียดสำเร็จ", "asset_filename_is_offline": "สื่อ {filename} ออฟไลน์อยู่", "asset_has_unassigned_faces": "สื่อไม่ได้ระบุใบหน้า", @@ -413,7 +413,7 @@ "asset_skipped": "ข้ามแล้ว", "asset_skipped_in_trash": "ในถังขยะ", "asset_uploaded": "อัปโหลดแล้ว", - "asset_uploading": "กำลังอัปโหลด...", + "asset_uploading": "กำลังอัปโหลด…", "assets": "สื่อ", "assets_added_to_album_count": "เพิ่ม {count, plural, one {# asset} other {# assets}} ไปยังอัลบั้ม", "assets_added_to_name_count": "เพิ่ม {count, plural, one {# asset} other {# assets}} ไปยัง {hasName, select, true {{name}} other {new album}}", @@ -1311,4 +1311,4 @@ "yes": "ใช่", "you_dont_have_any_shared_links": "คุณไม่ได้มีลิงก์ที่แชร์", "zoom_image": "ซูมรูปภาพ" -} +} \ No newline at end of file diff --git a/i18n/tr.json b/i18n/tr.json index 3f766362b2..4727be7fde 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -41,6 +41,7 @@ "backup_settings": "Yedekleme Ayarları", "backup_settings_description": "Veritabanı Yedekleme Ayarlarını Yönet", "check_all": "Hepsini Kontrol Et", + "cleanup": "Temizle", "cleared_jobs": "{job} için işler temizlendi", "config_set_by_file": "Ayarlar şuanda config dosyası tarafından ayarlanmıştır", "confirm_delete_library": "{library} kütüphanesini silmek istediğinize emin misiniz?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Periyodik kütüphane taramasını etkinleştir", "library_settings": "Harici kütüphane", "library_settings_description": "Harici kütüphane ayarlarını yönet", - "library_tasks_description": "Kütüphane görevleri gerçekleştir", + "library_tasks_description": "Yeni yada değiştirilmiş varlıklar için dış kütüphaneleri tara", "library_watching_enable_description": "Harici kütüphanelerdeki dosya değişikliklerini izle", "library_watching_settings": "Kütüphane izleme (DENEYSEL)", "library_watching_settings_description": "Değişen dosyalar için otomatik olarak izle", @@ -114,7 +115,7 @@ "machine_learning_facial_recognition": "Yüz Tanıma", "machine_learning_facial_recognition_description": "Fotoğraflardaki yüzleri tara, tanı ve gruplandır", "machine_learning_facial_recognition_model": "Yüz Tanıma Modeli", - "machine_learning_facial_recognition_model_description": "Modeller boyutlarına göre sıralanmıştır. Büyük modeller daha yavaş çalışır ve boyutu daha yüksektir fakat daha iyi sonuçlar üretir. Modeli değiştirdikten sonra Yüz Tarama işini tüm fotoğraflar için tekrar çalıştırmalısınız.", + "machine_learning_facial_recognition_model_description": "Modeller, azalan boyut sırasına göre listelenmiştir. Daha büyük modeller daha yavaştır ve daha fazla bellek kullanır, ancak daha iyi sonuçlar üretir. Bir modeli değiştirdikten sonra tüm görüntüler için yüz algılama işini yeniden çalıştırmanız gerektiğini unutmayın.", "machine_learning_facial_recognition_setting": "Yüz Tanımayı etkinleştir", "machine_learning_facial_recognition_setting_description": "Devre dışı bırakıldığında fotoğraflar yüz tanıma için işlenmeyecek ve Keşfet sayfasındaki Kişiler sekmesini doldurmayacak.", "machine_learning_max_detection_distance": "Maksimum tespit uzaklığı", @@ -147,6 +148,8 @@ "map_settings": "Harita", "map_settings_description": "Harita ayarlarını yönet", "map_style_description": "style.json Harita ayarlarının URL'si", + "memory_cleanup_job": "Anı temizliği", + "memory_generate_job": "Anı oluşturma", "metadata_extraction_job": "Meta verilerinden Ayıkla", "metadata_extraction_job_description": "GPS ve çözünürlük gibi ger bir varlığın meta veri bilgilerini ayıklayın", "metadata_faces_import_setting": "Yüz içe aktarmayı etkinleştir", @@ -391,6 +394,7 @@ "allow_edits": "Düzenlemeye izin ver", "allow_public_user_to_download": "Genel kullanıcının indirmesine aç", "allow_public_user_to_upload": "Genel kullanıcının yüklemesine aç", + "alt_text_qr_code": "QR kodu görseli", "anti_clockwise": "Saat yönünün tersine", "api_key": "API Anahtarı", "api_key_description": "Bu değer sadece bir kere gösterilecek. Lütfen bu pencereyi kapatmadan önce kopyaladığınıza emin olun.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Yorumlar devre dışı", "confirm": "Onayla", "confirm_admin_password": "Yönetici Şifresini Onayla", + "confirm_delete_face": "Varlıktan {name} yüzünü silmek istediğinizden emin misiniz?", "confirm_delete_shared_link": "Bu paylaşılan bağlantıyı silmek istediğinizden emin misiniz?", "confirm_keep_this_delete_others": "Yığındaki diğer tüm öğeler bu varlık haricinde silinecektir. Devam etmek istediğinizden emin misiniz?", "confirm_password": "Şifreyi onayla", @@ -533,6 +538,7 @@ "delete_album": "Albümü sil", "delete_api_key_prompt": "Bu API anahtarını silmek istediğinizden emin misiniz?", "delete_duplicates_confirmation": "Bu kopyaları kalıcı olarak silmek istediğinizden emin misiniz?", + "delete_face": "Yüzü sil", "delete_key": "Anahtarı sil", "delete_library": "Kütüphaneyi sil", "delete_link": "Bağlantıyı sil", @@ -600,6 +606,7 @@ "enabled": "Etkinleştirildi", "end_date": "Bitiş tarihi", "error": "Hata", + "error_delete_face": "Yüzü varlıktan silme hatası", "error_loading_image": "Resim yüklenirken hata oluştu", "error_title": "Bir Hata Oluştu - Bir şeyler ters gitti", "errors": { @@ -884,6 +891,7 @@ "month": "Ay", "more": "Daha fazla", "moved_to_trash": "Çöp kutusuna taşındı", + "mute_memories": "Anıları sessize al", "my_albums": "Albümlerim", "name": "İsim", "name_or_nickname": "İsim veya takma isim", @@ -1076,6 +1084,8 @@ "removed_from_archive": "Arşivden çıkarıldı", "removed_from_favorites": "Favorilerden kaldırıldı", "removed_from_favorites_count": "{count, plural, other {#}} favorilerden çıkarıldı", + "removed_memory": "Anı kaldırıldı", + "removed_photo_from_memory": "Fotoğraf anıdan kaldırıldı", "removed_tagged_assets": "{count, plural, one {# dosya} other {# dosya}} etiketleri kaldırıldı", "rename": "Yeniden adlandır", "repair": "Onar", @@ -1084,6 +1094,7 @@ "repository": "Depo", "require_password": "Şifre gerekli", "require_user_to_change_password_on_first_login": "Kullanıcı ilk girişte şifreyi değiştirmeli", + "rescan": "Yeniden tara", "reset": "Sıfırla", "reset_password": "Şifreyi sıfırla", "reset_people_visibility": "Kişilerin görünürlüğünü sıfırla", @@ -1127,6 +1138,7 @@ "search_options": "Arama seçenekleri", "search_people": "Kişilere göre ara", "search_places": "Yerleri ara", + "search_rating": "Derecelendirerek arayın...", "search_settings": "Ayarları ara", "search_state": "Eyalet/İl ara...", "search_tags": "Etiketleri ara...", @@ -1250,6 +1262,7 @@ "tag_created": "Etiket oluşturuldu: {tag}", "tag_feature_description": "Etiket temalarına göre gruplandırılmış fotoğraf ve videoları keşfedin", "tag_not_found_question": "Etiket bulunamadı mı? Yeni bir etiket oluşturun.", + "tag_people": "İnsanları etiketle", "tag_updated": "Etiket güncellendi: {tag}", "tagged_assets": "{count, plural, one {# dosya} other {# dosya}} etiketlendi", "tags": "Etiketler", @@ -1290,6 +1303,7 @@ "unlink_motion_video": "Hareketli video bağlantısını kaldır", "unlink_oauth": "OAuth bağlantısını kaldır", "unlinked_oauth_account": "Bağlantısı kaldırılmış OAuth hesabı", + "unmute_memories": "Anıların sesini aç", "unnamed_album": "İsimsiz Albüm", "unnamed_album_delete_confirmation": "Bu albümü silmek istediğinizden emin misiniz?", "unnamed_share": "İsimsiz paylaşım", @@ -1343,6 +1357,7 @@ "view_all": "Tümünü gör", "view_all_users": "Tüm kullanıcıları görüntüle", "view_in_timeline": "Zaman çizelgesinde görüntüle", + "view_link": "Bağlantıyı göster", "view_links": "Bağlantıları göster", "view_name": "Göster", "view_next_asset": "Sonraki dosyayı görüntüle", diff --git a/i18n/uk.json b/i18n/uk.json index 1cb6b0e5a6..2815b4dac6 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -41,6 +41,7 @@ "backup_settings": "Налаштування резервного копіювання", "backup_settings_description": "Керування налаштуваннями резервного копіювання бази даних", "check_all": "Перевірити все", + "cleanup": "Очищення", "cleared_jobs": "Очищені завдання для: {job}", "config_set_by_file": "Налаштовано за допомогою конфіг-файлу", "confirm_delete_library": "Ви дійсно бажаєте видалити бібліотеку \"{library}\"?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "Увімкнути періодичне сканування бібліотеки", "library_settings": "Зовнішня бібліотека", "library_settings_description": "Керування налаштуваннями зовнішніх бібліотек", - "library_tasks_description": "Виконуйте завдання бібліотеки", + "library_tasks_description": "Сканувати зовнішні бібліотеки на наявність нових і/або змінених ресурсів", "library_watching_enable_description": "Слідкуйте за змінами файлів у зовнішніх бібліотеках", "library_watching_settings": "Спостереження за бібліотекою (ЕКСПЕРИМЕНТАЛЬНЕ)", "library_watching_settings_description": "Автоматичне спостереження за зміненими файлами", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "Пошук зображень за допомогою семантичних вбудовувань CLIP", "machine_learning_smart_search_enabled": "Увімкнути розумний пошук", "machine_learning_smart_search_enabled_description": "Якщо ця функція вимкнена, зображення не будуть кодуватися для розумного пошуку.", - "machine_learning_url_description": "URL сервера машинного навчання. Якщо надано більше одного URL, сервери будуть опитуватися по черзі, поки один з них не відповість успішно, у порядку від першого до останнього.", + "machine_learning_url_description": "URL сервера машинного навчання. Якщо надано більше одного URL, сервери будуть опитуватися по черзі, поки один з них не відповість успішно, у порядку від першого до останнього. Сервери, які не відповідають, будуть тимчасово ігноруватися, поки не з'являться онлайн.", "manage_concurrency": "Керування паралельністю завдань", "manage_log_settings": "Керування налаштуваннями журналу", "map_dark_style": "Темний стиль", @@ -147,6 +148,8 @@ "map_settings": "Мапа", "map_settings_description": "Управління налаштуваннями мапи", "map_style_description": "URL до теми мапи у форматі style.json", + "memory_cleanup_job": "Очищення пам'яті", + "memory_generate_job": "Покоління пам'яті", "metadata_extraction_job": "Витягнути метадані", "metadata_extraction_job_description": "Витягни метадані з кожного об'єкта, таку як GPS, обличчя та роздільна здатність", "metadata_faces_import_setting": "Увімкни імпорт облич", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "Увімкнути перевірку хеша. Не вимикайте це, якщо ви не впевнені в наслідках", "storage_template_migration": "Міграція шаблонів сховища", "storage_template_migration_description": "Застосувати поточний {template} до раніше завантажених ресурсів", - "storage_template_migration_info": "Зміни шаблону будуть застосовуватися тільки до нових ресурсів. Для ретроспективного застосування шаблону до ресурсів, завантажених раніше, запустіть {job}.", + "storage_template_migration_info": "Шаблон зберігання конвертуватиме всі розширення у нижній регістр. Зміни шаблону застосовуватимуться лише до нових ресурсів. Щоб застосувати шаблон до раніше завантажених ресурсів, запустіть {job}.", "storage_template_migration_job": "Завдання міграції шаблону зберігання", "storage_template_more_details": "Для отримання детальнішої інформації про цю функцію, звертайтесь до Шаблону зберігання та його наслідків", "storage_template_onboarding_description": "Після увімкнення ця функція автоматично організовуватиме файли за користувацьким шаблоном. З міркувань стабільності функцію за замовчуванням вимкнено. Докладніша інформація доступна в документації.", @@ -391,6 +394,7 @@ "allow_edits": "Дозволити редагування", "allow_public_user_to_download": "Дозволити публічному користувачеві завантажувати файли", "allow_public_user_to_upload": "Дозволити публічним користувачам завантажувати", + "alt_text_qr_code": "Зображення QR-коду", "anti_clockwise": "Проти годинникової стрілки", "api_key": "Ключ API", "api_key_description": "Це значення буде показане лише один раз. Будь ласка, обов'язково скопіюйте його перед закриттям вікна.", @@ -481,6 +485,7 @@ "comments_are_disabled": "Коментарі вимкнено", "confirm": "Підтвердіть", "confirm_admin_password": "Підтвердити пароль адміністратора", + "confirm_delete_face": "Ви впевнені, що хочете видалити обличчя {name} з активу?", "confirm_delete_shared_link": "Ви впевнені, що хочете видалити це спільне посилання?", "confirm_keep_this_delete_others": "Усі інші ресурси в стеку буде видалено, окрім цього ресурсу. Ви впевнені, що хочете продовжити?", "confirm_password": "Підтвердити пароль", @@ -533,6 +538,7 @@ "delete_album": "Видалити альбом", "delete_api_key_prompt": "Ви впевнені, що хочете видалити цей ключ API?", "delete_duplicates_confirmation": "Ви впевнені, що хочете назавжди видалити ці дублікати?", + "delete_face": "Видалити обличчя", "delete_key": "Видалити ключ", "delete_library": "Видалити бібліотеку", "delete_link": "Видалити посилання", @@ -600,6 +606,7 @@ "enabled": "Увімкнено", "end_date": "Дата завершення", "error": "Помилка", + "error_delete_face": "Помилка при видаленні обличчя з активу", "error_loading_image": "Помилка завантаження зображення", "error_title": "Помилка: щось пішло не так", "errors": { @@ -884,6 +891,7 @@ "month": "Місяць", "more": "Більше", "moved_to_trash": "Перенесено до смітника", + "mute_memories": "Приглушити спогади", "my_albums": "Мої альбоми", "name": "Ім'я", "name_or_nickname": "Ім'я або псевдонім", @@ -979,6 +987,7 @@ "permanently_deleted_asset": "Видалити назавжди", "permanently_deleted_assets_count": "Видалено остаточно {count, plural, one {# ресурс} few {# ресурси} many {# ресурсів} other {# ресурсів}}", "person": "Людина", + "person_birthdate": "Народився {date}", "person_hidden": "{name}{hidden, select, true { (приховано)} other {}}", "photo_shared_all_users": "Виглядає так, що ви поділилися своїми фотографіями з усіма користувачами або у вас немає жодного користувача, з яким можна поділитися.", "photos": "Знімки", @@ -1076,6 +1085,8 @@ "removed_from_archive": "Видалено з архіву", "removed_from_favorites": "Видалено з обраного", "removed_from_favorites_count": "{count, plural, other {Видалено #}} з обраних", + "removed_memory": "Видалена пам'ять", + "removed_photo_from_memory": "Фото видалене з пам'яті", "removed_tagged_assets": "Видалено тег із {count, plural, one {# активу} other {# активів}}", "rename": "Перейменувати", "repair": "Ремонт", @@ -1084,6 +1095,7 @@ "repository": "Репозиторій", "require_password": "Вимагати пароль", "require_user_to_change_password_on_first_login": "Вимагати від користувача змінювати пароль при першому вході", + "rescan": "Пересканування", "reset": "Скидання", "reset_password": "Скинути пароль", "reset_people_visibility": "Відновити видимість людей", @@ -1127,6 +1139,7 @@ "search_options": "Опції пошуку", "search_people": "Шукати людей", "search_places": "Пошук місць", + "search_rating": "Пошук за рейтингом...", "search_settings": "Налаштування пошуку", "search_state": "Пошук регіону...", "search_tags": "Пошук тегів...", @@ -1250,6 +1263,7 @@ "tag_created": "Створено тег: {tag}", "tag_feature_description": "Перегляд фотографій та відео, згрупованих за логічними темами тегів", "tag_not_found_question": "Не вдається знайти тег? Створити новий тег.", + "tag_people": "Тег людей", "tag_updated": "Оновлено тег: {tag}", "tagged_assets": "Позначено тегом {count, plural, one {# актив} other {# активи}}", "tags": "Теги", @@ -1290,6 +1304,7 @@ "unlink_motion_video": "Від'єднати рухоме відео", "unlink_oauth": "Від'єднайте OAuth", "unlinked_oauth_account": "Відключити акаунт OAuth", + "unmute_memories": "Увімкнути звук спогадів", "unnamed_album": "Альбом без назви", "unnamed_album_delete_confirmation": "Ви впевнені, що бажаєте видалити цей альбом?", "unnamed_share": "Спільний доступ без назви", @@ -1343,6 +1358,7 @@ "view_all": "Переглянути усі", "view_all_users": "Переглянути всіх користувачів", "view_in_timeline": "Переглянути в хронології", + "view_link": "Переглянути посилання", "view_links": "Переглянути посилання", "view_name": "Переглянути", "view_next_asset": "Переглянути наступний ресурс", diff --git a/i18n/ur.json b/i18n/ur.json index 0967ef424b..296a8eec25 100644 --- a/i18n/ur.json +++ b/i18n/ur.json @@ -1 +1,3 @@ -{} +{ + "about": "متعلق" +} diff --git a/i18n/vi.json b/i18n/vi.json index 9983f5a057..d84a588614 100644 --- a/i18n/vi.json +++ b/i18n/vi.json @@ -284,7 +284,7 @@ "transcoding_max_b_frames": "Số B-frame tối đa", "transcoding_max_b_frames_description": "Giá trị cao hơn cải thiện hiệu quả nén, nhưng làm chậm mã hóa. Có thể không tương thích với tăng tốc phần cứng trên các thiết bị cũ. Giá trị 0 để tắt B-frames, trong khi giá trị -1 để tự động thiết lập giá trị này.", "transcoding_max_bitrate": "Bitrate tối đa", - "transcoding_max_bitrate_description": "Cài đặt giới hạn bitrate tối đa có thể giúp kích thước video dễ dự đoán hơn, với một chút hy sinh về chất lượng. Ở độ phân giải 720p, giá trị điển hình là 2600k cho VP9 hoặc HEVC, hoặc 4500k cho H.264. Nếu đặt thành 0, chức năng này sẽ bị vô hiệu hóa.", + "transcoding_max_bitrate_description": "Cài đặt giới hạn bitrate tối đa có thể giúp kích thước video dễ dự đoán hơn, với một chút hy sinh về chất lượng. Ở độ phân giải 720p, giá trị điển hình là 2600 kbit/s cho VP9 hoặc HEVC, hoặc 4500 kbit/s cho H.264. Nếu đặt thành 0, chức năng này sẽ bị vô hiệu hóa.", "transcoding_max_keyframe_interval": "Khoảng cách tối đa giữa các khung hình chính", "transcoding_max_keyframe_interval_description": "Thiết lập khoảng thời gian tối đa giữa các khung hình chính. Giá trị thấp hơn làm giảm hiệu suất nén, nhưng cải thiện thời gian tìm kiếm và có thể cải thiện chất lượng trong các cảnh có chuyển động nhanh. Giá trị 0 để tự động thiết lập giá trị này.", "transcoding_optimal_description": "Video có độ phân giải cao hơn mục tiêu hoặc không ở định dạng được chấp nhận", @@ -1317,4 +1317,4 @@ "yes": "Có", "you_dont_have_any_shared_links": "Bạn không có liên kết chia sẻ nào", "zoom_image": "Thu phóng ảnh" -} +} \ No newline at end of file diff --git a/i18n/zh_Hant.json b/i18n/zh_Hant.json index 443fa2cd71..bcd543c78a 100644 --- a/i18n/zh_Hant.json +++ b/i18n/zh_Hant.json @@ -41,6 +41,7 @@ "backup_settings": "備份設定", "backup_settings_description": "管理資料庫備份設定", "check_all": "全選", + "cleanup": "清理", "cleared_jobs": "已刪除「{job}」任務", "config_set_by_file": "已透過設定檔更新設定", "confirm_delete_library": "確定要刪除 {library} 相簿嗎?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "啟用圖庫定期掃描", "library_settings": "外部圖庫", "library_settings_description": "管理外部圖庫設定", - "library_tasks_description": "執行圖庫任務", + "library_tasks_description": "掃描外部資料庫以尋找新增或更改的資源", "library_watching_enable_description": "監控外部圖庫的檔案變化", "library_watching_settings": "圖庫監控(實驗中)", "library_watching_settings_description": "自動監控檔案的變化", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "使用 CLIP 嵌入進行語義圖像搜尋", "machine_learning_smart_search_enabled": "啟用智慧搜尋", "machine_learning_smart_search_enabled_description": "如果停用,圖片將不會被編碼以進行智能搜尋。", - "machine_learning_url_description": "機器學習伺服器的網址,如果提供多個 URL,則將按依序嘗試連接每個伺服器,直到有一個伺服器成功回應為止。", + "machine_learning_url_description": "機器學習伺服器的 URL。如果提供多個 URL,將依序嘗試每個伺服器,直到有一個成功回應,從第一個到最後一個。未回應的伺服器將暫時被忽略,直到它們恢復線上。", "manage_concurrency": "管理並行", "manage_log_settings": "管理日誌設定", "map_dark_style": "深色樣式", @@ -147,6 +148,8 @@ "map_settings": "地圖", "map_settings_description": "管理地圖設定", "map_style_description": "地圖主題(style.json)的網址", + "memory_cleanup_job": "記憶體清理", + "memory_generate_job": "記憶體生成", "metadata_extraction_job": "擷取詮釋資料", "metadata_extraction_job_description": "擷取所有檔案的 GPS、臉孔、解析度等原始詳細資料", "metadata_faces_import_setting": "啟用臉孔匯入", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "最大 B 幀數", "transcoding_max_b_frames_description": "較高的數值可提升壓縮效率,但會降低編碼速度。可能與較舊設備的硬體加速不相容。設定為 0 時會停用 B-frames,而 -1 則會自動設定此數值。", "transcoding_max_bitrate": "最大位元速率", - "transcoding_max_bitrate_description": "設定最大位元速率可以使檔案大小更具可預測性,但會稍微降低品質。在 720p 解析度下,典型值為 VP9 或 HEVC 的 2600k,或 H.264 的 4500k。設置為 0 停用此功能。", + "transcoding_max_bitrate_description": "設定最大位元速率可以使檔案大小更具可預測性,但會稍微降低品質。在 720p 解析度下,典型值為 VP9 或 HEVC 的 2600 kbit/s,或 H.264 的 4500 kbit/s。設置為 0 停用此功能。", "transcoding_max_keyframe_interval": "最大關鍵幀間隔", "transcoding_max_keyframe_interval_description": "設置關鍵幀之間的最大幀距。較低的值會降低壓縮效率,但可以改善搜尋時間,並有可能會改善快速變動場景的品質。0 會自動設置此值。", "transcoding_optimal_description": "高於目標解析度或格式不被支援的影片", @@ -391,6 +394,7 @@ "allow_edits": "允許編輯", "allow_public_user_to_download": "開放給使用者下載", "allow_public_user_to_upload": "開放讓使用者上傳", + "alt_text_qr_code": "QR 碼圖片", "anti_clockwise": "逆時針", "api_key": "API 金鑰", "api_key_description": "此值僅顯示一次。請確保在關閉窗口之前複製它。", @@ -481,6 +485,7 @@ "comments_are_disabled": "留言已停用", "confirm": "確認", "confirm_admin_password": "確認管理者密碼", + "confirm_delete_face": "您確定要從資產中刪除 {name} 的臉部嗎?", "confirm_delete_shared_link": "確定刪除連結嗎?", "confirm_keep_this_delete_others": "所有的其他堆疊項目將被刪除。確定繼續嗎?", "confirm_password": "確認密碼", @@ -533,6 +538,7 @@ "delete_album": "刪除相簿", "delete_api_key_prompt": "您確定要刪除這個 API Key嗎?", "delete_duplicates_confirmation": "您確定要永久刪除這些重複項嗎?", + "delete_face": "刪除臉部", "delete_key": "刪除密鑰", "delete_library": "刪除圖庫", "delete_link": "刪除鏈結", @@ -600,6 +606,7 @@ "enabled": "己啟用", "end_date": "結束日期", "error": "錯誤", + "error_delete_face": "從資產中刪除臉部時發生錯誤", "error_loading_image": "載入圖片時出錯", "error_title": "錯誤 - 出問題了", "errors": { @@ -884,6 +891,7 @@ "month": "月", "more": "更多", "moved_to_trash": "已丟進垃圾桶", + "mute_memories": "靜音回憶", "my_albums": "我的相簿", "name": "名稱", "name_or_nickname": "名稱或暱稱", @@ -1076,6 +1084,8 @@ "removed_from_archive": "從封存中移除", "removed_from_favorites": "已從收藏中移除", "removed_from_favorites_count": "已移除收藏的 {count, plural, other {# 個項目}}", + "removed_memory": "已移除記憶", + "removed_photo_from_memory": "已從記憶中移除照片", "removed_tagged_assets": "已移除 {count, plural, other {# 個檔案}}的標記", "rename": "改名", "repair": "糾正", @@ -1084,6 +1094,7 @@ "repository": "儲存庫", "require_password": "需要密碼", "require_user_to_change_password_on_first_login": "要求使用者在首次登入時更改密碼", + "rescan": "重新掃描", "reset": "重設", "reset_password": "重設密碼", "reset_people_visibility": "重設人物可見性", @@ -1127,6 +1138,7 @@ "search_options": "搜尋選項", "search_people": "搜尋人物", "search_places": "搜尋地點", + "search_rating": "按評分搜尋...", "search_settings": "搜尋設定", "search_state": "搜尋地區…", "search_tags": "搜尋標籤...", @@ -1250,6 +1262,7 @@ "tag_created": "已建立標記:{tag}", "tag_feature_description": "以邏輯標記要旨分組瀏覽照片和影片", "tag_not_found_question": "找不到標記?建立新標記吧。", + "tag_people": "標記人物", "tag_updated": "已更新標記:{tag}", "tagged_assets": "已標記 {count, plural, other {# 個檔案}}", "tags": "標籤", @@ -1290,6 +1303,7 @@ "unlink_motion_video": "取消鏈結動態影片", "unlink_oauth": "取消連接 OAuth", "unlinked_oauth_account": "已解除連接 OAuth 帳號", + "unmute_memories": "取消靜音回憶", "unnamed_album": "未命名相簿", "unnamed_album_delete_confirmation": "確定要刪除這本相簿嗎?", "unnamed_share": "未命名分享", @@ -1343,6 +1357,7 @@ "view_all": "瀏覽全部", "view_all_users": "查看所有使用者", "view_in_timeline": "在時間軸中查看", + "view_link": "查看連結", "view_links": "檢視鏈結", "view_name": "檢視分類", "view_next_asset": "查看下一項", @@ -1359,4 +1374,4 @@ "yes": "是", "you_dont_have_any_shared_links": "您沒有任何共享連結", "zoom_image": "縮放圖片" -} +} \ No newline at end of file diff --git a/i18n/zh_SIMPLIFIED.json b/i18n/zh_SIMPLIFIED.json index 332386067a..f6ef01e353 100644 --- a/i18n/zh_SIMPLIFIED.json +++ b/i18n/zh_SIMPLIFIED.json @@ -41,6 +41,7 @@ "backup_settings": "备份设置", "backup_settings_description": "管理数据库备份设置", "check_all": "检查全部", + "cleanup": "清理", "cleared_jobs": "已清理任务:{job}", "config_set_by_file": "当前配置已通过配置文件设置", "confirm_delete_library": "确定要删除图库“{library}”吗?", @@ -96,7 +97,7 @@ "library_scanning_enable_description": "启用定期扫描图库", "library_settings": "外部图库", "library_settings_description": "管理外部图库设置", - "library_tasks_description": "执行图库任务", + "library_tasks_description": "扫描外部库,查找新增或修改的项目", "library_watching_enable_description": "监控外部图库文件变化", "library_watching_settings": "监控图库(实验性)", "library_watching_settings_description": "自动监控文件变化", @@ -131,7 +132,7 @@ "machine_learning_smart_search_description": "使用 CLIP 以文搜图、智能搜图", "machine_learning_smart_search_enabled": "启用智能搜索", "machine_learning_smart_search_enabled_description": "如果禁用,则不会对图像编码以用于智能搜索。", - "machine_learning_url_description": "机器学习服务器的 URL。如果提供多个 URL,则将按依次尝试连接每个服务器,直到有一个服务器成功响应为止。", + "machine_learning_url_description": "机器学习服务器的 URL。如果提供多个 URL,则将按依次尝试连接每个服务器,直到有一个服务器成功响应为止。不响应的服务器将被暂时忽略,直到它们重新联机。", "manage_concurrency": "管理任务并发", "manage_log_settings": "管理日志设置", "map_dark_style": "深色模式", @@ -147,6 +148,8 @@ "map_settings": "地图", "map_settings_description": "管理地图设置", "map_style_description": "地图主题 style.json 的 URL", + "memory_cleanup_job": "清空回忆", + "memory_generate_job": "生成回忆", "metadata_extraction_job": "提取元数据", "metadata_extraction_job_description": "从每个项目中提取元数据信息,如 GPS、人脸和分辨率", "metadata_faces_import_setting": "启用人脸导入", @@ -240,7 +243,7 @@ "storage_template_hash_verification_enabled_description": "启用哈希校验,如果您不知道此项的作用请不要禁用此功能", "storage_template_migration": "存储模板转换", "storage_template_migration_description": "应用当前的{template}到之前上传的项目", - "storage_template_migration_info": "模板修改将只作用于新的项目。如也需应用此模板到之前上传的项目,请运行{job}。", + "storage_template_migration_info": "存储模板会将所有扩展名转换为小写。模板修改只会作用于新的项目,如需应用此模板到之前上传的项目,请运行{job}。", "storage_template_migration_job": "存储模板转换任务", "storage_template_more_details": "关于本功能的更多细节,请参见存储模板及其实现方式", "storage_template_onboarding_description": "启用后,本功能将根据用户定义的模板自动整理文件。出于稳定性考虑,本功能默认是禁用的。更多详细信息请参见 文档。", @@ -299,7 +302,7 @@ "transcoding_max_b_frames": "最大B帧数", "transcoding_max_b_frames_description": "较高的值可以提高压缩效率,但会减慢编码速度。可能与旧设备上的硬件加速不兼容。0表示将禁用B帧,-1表示将自动设置此参数。", "transcoding_max_bitrate": "最高码率", - "transcoding_max_bitrate_description": "设置最大比特率可在对输出质量影响较小的情况下,使文件体积更为可控。720p 下,VP9 或 HEVC 普遍将其设为 2600K,H.264 则为 4500K。如果此项设置为 0,则不限制最大比特率。", + "transcoding_max_bitrate_description": "设置最大比特率可在对输出质量影响较小的情况下,使文件体积更为可控。720p 下,VP9 或 HEVC 普遍将其设为 2600 kbit/s,H.264 则为 4500 kbit/s。如果此项设置为 0,则不限制最大比特率。", "transcoding_max_keyframe_interval": "最大关键帧间隔", "transcoding_max_keyframe_interval_description": "设置关键帧之间的最大帧距离。较低的值会降低压缩效率,但可以提高搜索速度,并且可能在快速运动的场景中提高画质。0 表示将自动设置此参数。", "transcoding_optimal_description": "视频超过目标分辨率或格式不支持", @@ -391,6 +394,7 @@ "allow_edits": "允许编辑", "allow_public_user_to_download": "允许所有用户下载", "allow_public_user_to_upload": "允许所有用户上传", + "alt_text_qr_code": "二维码图片", "anti_clockwise": "逆时针", "api_key": "API 密钥", "api_key_description": "该应用密钥只会显示一次。请确保在关闭窗口前复制下来。", @@ -481,6 +485,7 @@ "comments_are_disabled": "评论已禁用", "confirm": "确认", "confirm_admin_password": "确认管理员密码", + "confirm_delete_face": "您确定要从资产中删除 {name} 的脸吗?", "confirm_delete_shared_link": "你确定要删除此共享链接吗?", "confirm_keep_this_delete_others": "除此项目外,堆叠中的所有其它项目都将被删除。你确定要继续吗?", "confirm_password": "确认密码", @@ -533,6 +538,7 @@ "delete_album": "删除相册", "delete_api_key_prompt": "确定删除此API key吗?", "delete_duplicates_confirmation": "你要永久删除这些重复项吗?", + "delete_face": "删除人脸", "delete_key": "删除密钥", "delete_library": "删除图库", "delete_link": "删除链接", @@ -600,6 +606,7 @@ "enabled": "已启用", "end_date": "结束日期", "error": "错误", + "error_delete_face": "删除人脸失败", "error_loading_image": "加载图片时出错", "error_title": "错误 - 好像出了问题", "errors": { @@ -884,6 +891,7 @@ "month": "月", "more": "更多", "moved_to_trash": "已放入回收站", + "mute_memories": "静音回忆", "my_albums": "我的相册", "name": "姓名", "name_or_nickname": "名称或昵称", @@ -1076,6 +1084,8 @@ "removed_from_archive": "从归档中移除", "removed_from_favorites": "从收藏中移除", "removed_from_favorites_count": "从收藏中移除{count, plural, other {#项}}", + "removed_memory": "已删除的回忆", + "removed_photo_from_memory": "从回忆中删除的照片", "removed_tagged_assets": "从 {count, plural, one {# 个项目} other {# 个项目}}中删除标签", "rename": "重命名", "repair": "修复", @@ -1084,6 +1094,7 @@ "repository": "库", "require_password": "需要密码", "require_user_to_change_password_on_first_login": "要求用户在首次登录时更改密码", + "rescan": "重新扫描", "reset": "重置", "reset_password": "重置密码", "reset_people_visibility": "重置人物可见性", @@ -1111,10 +1122,10 @@ "scanning_for_album": "扫描相册中...", "search": "搜索", "search_albums": "搜索相册", - "search_by_context": "搜索内容", - "search_by_description": "通过描述搜索", + "search_by_context": "按照片情景搜索", + "search_by_description": "按描述搜索", "search_by_description_example": "在沙巴徒步的日子", - "search_by_filename": "通过文件名搜索", + "search_by_filename": "按文件名搜索", "search_by_filename_example": "如 IMG_1234.JPG 或 PNG", "search_camera_make": "搜索相机品牌...", "search_camera_model": "搜索相机型号...", @@ -1127,6 +1138,7 @@ "search_options": "搜索选项", "search_people": "搜索人物", "search_places": "搜索地点", + "search_rating": "按星级搜索...", "search_settings": "搜索设置", "search_state": "搜索省份...", "search_tags": "搜索标签…", @@ -1250,6 +1262,7 @@ "tag_created": "已创建标签:{tag}", "tag_feature_description": "按逻辑标签分组并浏览照片和视频", "tag_not_found_question": "找不到标签吗?创建新标签", + "tag_people": "命名人物", "tag_updated": "已更新标签:{tag}", "tagged_assets": "{count, plural, one {# 个项目} other {# 个项目}}被加上标签", "tags": "标签", @@ -1290,6 +1303,7 @@ "unlink_motion_video": "取消链接动态视频", "unlink_oauth": "解绑 OAuth", "unlinked_oauth_account": "解绑 OAuth 账户", + "unmute_memories": "取消静音回忆", "unnamed_album": "未命名相册", "unnamed_album_delete_confirmation": "您确定要删除该相册吗?", "unnamed_share": "未命名共享", @@ -1343,6 +1357,7 @@ "view_all": "查看全部", "view_all_users": "查看全部用户", "view_in_timeline": "在时间轴中查看", + "view_link": "查看链接", "view_links": "查看链接", "view_name": "查看", "view_next_asset": "查看下一项", @@ -1359,4 +1374,4 @@ "yes": "是", "you_dont_have_any_shared_links": "您没有任何共享链接", "zoom_image": "缩放图像" -} +} \ No newline at end of file diff --git a/localizely.yml b/localizely.yml index 6da9423b49..ad3d9b26f3 100644 --- a/localizely.yml +++ b/localizely.yml @@ -66,8 +66,8 @@ download: locale_code: es-MX - file: mobile/assets/i18n/sv-FI.json locale_code: sv-FI - - file: mobile/assets/i18n/ca-CA.json - locale_code: ca-CA + - file: mobile/assets/i18n/ca.json + locale_code: ca - file: mobile/assets/i18n/hu-HU.json locale_code: hu-HU - file: mobile/assets/i18n/lv-LV.json diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile index 8761586de7..800a5ae9e6 100644 --- a/machine-learning/Dockerfile +++ b/machine-learning/Dockerfile @@ -20,19 +20,16 @@ FROM builder-${DEVICE} AS builder ARG DEVICE ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ - PIP_NO_CACHE_DIR=true \ - VIRTUAL_ENV="/opt/venv" \ - PATH="/opt/venv/bin:${PATH}" + VIRTUAL_ENV=/opt/venv +WORKDIR /usr/src/app RUN apt-get update && apt-get install -y --no-install-recommends g++ -RUN pip install --upgrade pip && pip install poetry -RUN poetry config installer.max-workers 10 && \ - poetry config virtualenvs.create false -RUN python3 -m venv /opt/venv - -COPY poetry.lock pyproject.toml ./ -RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev +COPY --from=ghcr.io/astral-sh/uv:latest@sha256:562193a4a9d398f8aedddcb223e583da394ee735de36b5815f8f1d22cb49be15 /uv /uvx /bin/ +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --extra ${DEVICE} --no-dev --no-editable --no-install-project --compile-bytecode --no-progress --active --link-mode copy FROM python:3.11-slim-bookworm@sha256:614c8691ab74150465ec9123378cd4dde7a6e57be9e558c3108df40664667a4c AS prod-cpu @@ -95,7 +92,8 @@ ENV TRANSFORMERS_CACHE=/cache \ PYTHONUNBUFFERED=1 \ PATH="/opt/venv/bin:$PATH" \ PYTHONPATH=/usr/src \ - DEVICE=${DEVICE} + DEVICE=${DEVICE} \ + VIRTUAL_ENV=/opt/venv # prevent core dumps RUN echo "hard core 0" >> /etc/security/limits.conf && \ diff --git a/machine-learning/README.md b/machine-learning/README.md index d7d099a87e..4146a6f9de 100644 --- a/machine-learning/README.md +++ b/machine-learning/README.md @@ -5,13 +5,12 @@ # Setup -This project uses [Poetry](https://python-poetry.org/docs/#installation), so be sure to install it first. -Running `poetry install --no-root --with dev --with cpu` will install everything you need in an isolated virtual environment. -CUDA and OpenVINO are supported as acceleration APIs. To use them, you can replace `--with cpu` with either of `--with cuda` or `--with openvino`. In the case of CUDA, a [compute capability](https://developer.nvidia.com/cuda-gpus) of 5.2 or higher is required. - -To add or remove dependencies, you can use the commands `poetry add $PACKAGE_NAME` and `poetry remove $PACKAGE_NAME`, respectively. -Be sure to commit the `poetry.lock` and `pyproject.toml` files with `poetry lock --no-update` to reflect any changes in dependencies. +This project uses [uv](https://docs.astral.sh/uv/getting-started/installation/), so be sure to install it first. +Running `uv sync --extra cpu` will install everything you need in an isolated virtual environment. +CUDA and OpenVINO are supported as acceleration APIs. To use them, you can replace `--group cpu` with either of `--group cuda` or `--group openvino`. In the case of CUDA, a [compute capability](https://developer.nvidia.com/cuda-gpus) of 5.2 or higher is required. +To add or remove dependencies, you can use the commands `uv add $PACKAGE_NAME` and `uv remove $PACKAGE_NAME`, respectively. +Be sure to commit the `uv.lock` and `pyproject.toml` files with `uv lock` to reflect any changes in dependencies. # Load Testing @@ -19,22 +18,25 @@ To measure inference throughput and latency, you can use [Locust](https://locust Locust works by querying the model endpoints and aggregating their statistics, meaning the app must be deployed. You can change the models or adjust options like score thresholds through the Locust UI. -To get started, you can simply run `locust --web-host 127.0.0.1` and open `localhost:8089` in a browser to access the UI. See the [Locust documentation](https://docs.locust.io/en/stable/index.html) for more info on running Locust. +To get started, you can simply run `locust --web-host 127.0.0.1` and open `localhost:8089` in a browser to access the UI. See the [Locust documentation](https://docs.locust.io/en/stable/index.html) for more info on running Locust. Note that in Locust's jargon, concurrency is measured in `users`, and each user runs one task at a time. To achieve a particular per-endpoint concurrency, multiply that number by the number of endpoints to be queried. For example, if there are 3 endpoints and you want each of them to receive 8 requests at a time, you should set the number of users to 24. # Facial Recognition ## Acknowledgements + This project utilizes facial recognition models from the [InsightFace](https://github.com/deepinsight/insightface/tree/master/model_zoo) project. We appreciate the work put into developing these models, which have been beneficial to the machine learning part of this project. ### Used Models -* antelopev2 -* buffalo_l -* buffalo_m -* buffalo_s + +- antelopev2 +- buffalo_l +- buffalo_m +- buffalo_s ## License and Use Restrictions + We have received permission to use the InsightFace facial recognition models in our project, as granted via email by Jia Guo (guojia@insightface.ai) on 18th March 2023. However, it's important to note that this permission does not extend to the redistribution or commercial use of their models by third parties. Users and developers interested in using these models should review the licensing terms provided in the InsightFace GitHub repository. -For more information on the capabilities of the InsightFace models and to ensure compliance with their license, please refer to their [official repository](https://github.com/deepinsight/insightface). Adhering to the specified licensing terms is crucial for the respectful and lawful use of their work. \ No newline at end of file +For more information on the capabilities of the InsightFace models and to ensure compliance with their license, please refer to their [official repository](https://github.com/deepinsight/insightface). Adhering to the specified licensing terms is crucial for the respectful and lawful use of their work. diff --git a/machine-learning/poetry.lock b/machine-learning/poetry.lock deleted file mode 100644 index 809e1082f9..0000000000 --- a/machine-learning/poetry.lock +++ /dev/null @@ -1,3738 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. - -[[package]] -name = "aiocache" -version = "0.12.3" -description = "multi backend asyncio cache" -optional = false -python-versions = "*" -files = [ - {file = "aiocache-0.12.3-py2.py3-none-any.whl", hash = "sha256:889086fc24710f431937b87ad3720a289f7fc31c4fd8b68e9f918b9bacd8270d"}, - {file = "aiocache-0.12.3.tar.gz", hash = "sha256:f528b27bf4d436b497a1d0d1a8f59a542c153ab1e37c3621713cb376d44c4713"}, -] - -[package.extras] -memcached = ["aiomcache (>=0.5.2)"] -msgpack = ["msgpack (>=0.5.5)"] -redis = ["redis (>=4.2.0)"] - -[[package]] -name = "albumentations" -version = "1.3.1" -description = "Fast image augmentation library and easy to use wrapper around other libraries" -optional = false -python-versions = ">=3.7" -files = [ - {file = "albumentations-1.3.1-py3-none-any.whl", hash = "sha256:6b641d13733181d9ecdc29550e6ad580d1bfa9d25e2213a66940062f25e291bd"}, - {file = "albumentations-1.3.1.tar.gz", hash = "sha256:a6a38388fe546c568071e8c82f414498e86c9ed03c08b58e7a88b31cf7a244c6"}, -] - -[package.dependencies] -numpy = ">=1.11.1" -opencv-python-headless = ">=4.1.1" -PyYAML = "*" -qudida = ">=0.0.4" -scikit-image = ">=0.16.1" -scipy = ">=1.1.0" - -[package.extras] -develop = ["imgaug (>=0.4.0)", "pytest"] -imgaug = ["imgaug (>=0.4.0)"] -tests = ["pytest"] - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anyio" -version = "4.2.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.8" -files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] - -[[package]] -name = "black" -version = "25.1.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.9" -files = [ - {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, - {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, - {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, - {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, - {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, - {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, - {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, - {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, - {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, - {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, - {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, - {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, - {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, - {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, - {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, - {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, - {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, - {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, - {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, - {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, - {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, - {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.10)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "blinker" -version = "1.7.0" -description = "Fast, simple object-to-object and broadcast signaling" -optional = false -python-versions = ">=3.8" -files = [ - {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"}, - {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, -] - -[[package]] -name = "brotli" -version = "1.1.0" -description = "Python bindings for the Brotli compression library" -optional = false -python-versions = "*" -files = [ - {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, - {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec"}, - {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, - {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, - {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, - {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b"}, - {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, - {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839"}, - {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, - {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, - {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5"}, - {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7"}, - {file = "Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0"}, - {file = "Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b"}, - {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52"}, - {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, - {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, - {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c"}, - {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, - {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, - {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, - {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a"}, - {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, - {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, - {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, - {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb"}, - {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, - {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, - {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, -] - -[[package]] -name = "certifi" -version = "2023.11.17" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, -] - -[[package]] -name = "cffi" -version = "1.17.1" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "coloredlogs" -version = "15.0.1" -description = "Colored terminal output for Python's logging module" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, - {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, -] - -[package.dependencies] -humanfriendly = ">=9.1" - -[package.extras] -cron = ["capturer (>=2.4)"] - -[[package]] -name = "configargparse" -version = "1.7" -description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables." -optional = false -python-versions = ">=3.5" -files = [ - {file = "ConfigArgParse-1.7-py3-none-any.whl", hash = "sha256:d249da6591465c6c26df64a9f73d2536e743be2f244eb3ebe61114af2f94f86b"}, - {file = "ConfigArgParse-1.7.tar.gz", hash = "sha256:e7067471884de5478c58a511e529f0f9bd1c66bfef1dea90935438d6c23306d1"}, -] - -[package.extras] -test = ["PyYAML", "mock", "pytest"] -yaml = ["PyYAML"] - -[[package]] -name = "contourpy" -version = "1.2.0" -description = "Python library for calculating contours of 2D quadrilateral grids" -optional = false -python-versions = ">=3.9" -files = [ - {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, - {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, - {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, - {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, - {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, - {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, - {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, - {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, - {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, - {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, - {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, -] - -[package.dependencies] -numpy = ">=1.20,<2.0" - -[package.extras] -bokeh = ["bokeh", "selenium"] -docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.6.1)", "types-Pillow"] -test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] - -[[package]] -name = "coverage" -version = "7.6.4" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"}, - {file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"}, - {file = "coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72"}, - {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51"}, - {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491"}, - {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b"}, - {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea"}, - {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a"}, - {file = "coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa"}, - {file = "coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172"}, - {file = "coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b"}, - {file = "coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25"}, - {file = "coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546"}, - {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b"}, - {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e"}, - {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718"}, - {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db"}, - {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522"}, - {file = "coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf"}, - {file = "coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19"}, - {file = "coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2"}, - {file = "coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117"}, - {file = "coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613"}, - {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27"}, - {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52"}, - {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2"}, - {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1"}, - {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5"}, - {file = "coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17"}, - {file = "coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08"}, - {file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"}, - {file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"}, - {file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"}, - {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"}, - {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"}, - {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"}, - {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"}, - {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"}, - {file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"}, - {file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"}, - {file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"}, - {file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"}, - {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"}, - {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"}, - {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"}, - {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"}, - {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"}, - {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"}, - {file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"}, - {file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"}, - {file = "coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3"}, - {file = "coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c"}, - {file = "coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076"}, - {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376"}, - {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0"}, - {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858"}, - {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111"}, - {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901"}, - {file = "coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09"}, - {file = "coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f"}, - {file = "coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e"}, - {file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"}, -] - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "cycler" -version = "0.12.1" -description = "Composable style cycles" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, - {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, -] - -[package.extras] -docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] -tests = ["pytest", "pytest-cov", "pytest-xdist"] - -[[package]] -name = "cython" -version = "3.0.8" -description = "The Cython compiler for writing C extensions in the Python language." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "Cython-3.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a846e0a38e2b24e9a5c5dc74b0e54c6e29420d88d1dafabc99e0fc0f3e338636"}, - {file = "Cython-3.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45523fdc2b78d79b32834cc1cc12dc2ca8967af87e22a3ee1bff20e77c7f5520"}, - {file = "Cython-3.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa0b7f3f841fe087410cab66778e2d3fb20ae2d2078a2be3dffe66c6574be39"}, - {file = "Cython-3.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e87294e33e40c289c77a135f491cd721bd089f193f956f7b8ed5aa2d0b8c558f"}, - {file = "Cython-3.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a1df7a129344b1215c20096d33c00193437df1a8fcca25b71f17c23b1a44f782"}, - {file = "Cython-3.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:13c2a5e57a0358da467d97667297bf820b62a1a87ae47c5f87938b9bb593acbd"}, - {file = "Cython-3.0.8-cp310-cp310-win32.whl", hash = "sha256:96b028f044f5880e3cb18ecdcfc6c8d3ce9d0af28418d5ab464509f26d8adf12"}, - {file = "Cython-3.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:8140597a8b5cc4f119a1190f5a2228a84f5ca6d8d9ec386cfce24663f48b2539"}, - {file = "Cython-3.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aae26f9663e50caf9657148403d9874eea41770ecdd6caf381d177c2b1bb82ba"}, - {file = "Cython-3.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:547eb3cdb2f8c6f48e6865d5a741d9dd051c25b3ce076fbca571727977b28ac3"}, - {file = "Cython-3.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a567d4b9ba70b26db89d75b243529de9e649a2f56384287533cf91512705bee"}, - {file = "Cython-3.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d1426263b0e82fb22bda8ea60dc77a428581cc19e97741011b938445d383f1"}, - {file = "Cython-3.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c26daaeccda072459b48d211415fd1e5507c06bcd976fa0d5b8b9f1063467d7b"}, - {file = "Cython-3.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:289ce7838208211cd166e975865fd73b0649bf118170b6cebaedfbdaf4a37795"}, - {file = "Cython-3.0.8-cp311-cp311-win32.whl", hash = "sha256:c8aa05f5e17f8042a3be052c24f2edc013fb8af874b0bf76907d16c51b4e7871"}, - {file = "Cython-3.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:000dc9e135d0eec6ecb2b40a5b02d0868a2f8d2e027a41b0fe16a908a9e6de02"}, - {file = "Cython-3.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90d3fe31db55685d8cb97d43b0ec39ef614fcf660f83c77ed06aa670cb0e164f"}, - {file = "Cython-3.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e24791ddae2324e88e3c902a765595c738f19ae34ee66bfb1a6dac54b1833419"}, - {file = "Cython-3.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f020fa1c0552052e0660790b8153b79e3fc9a15dbd8f1d0b841fe5d204a6ae6"}, - {file = "Cython-3.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18bfa387d7a7f77d7b2526af69a65dbd0b731b8d941aaff5becff8e21f6d7717"}, - {file = "Cython-3.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fe81b339cffd87c0069c6049b4d33e28bdd1874625ee515785bf42c9fdff3658"}, - {file = "Cython-3.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:80fd94c076e1e1b1ee40a309be03080b75f413e8997cddcf401a118879863388"}, - {file = "Cython-3.0.8-cp312-cp312-win32.whl", hash = "sha256:85077915a93e359a9b920280d214dc0cf8a62773e1f3d7d30fab8ea4daed670c"}, - {file = "Cython-3.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:0cb2dcc565c7851f75d496f724a384a790fab12d1b82461b663e66605bec429a"}, - {file = "Cython-3.0.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:870d2a0a7e3cbd5efa65aecdb38d715ea337a904ea7bb22324036e78fb7068e7"}, - {file = "Cython-3.0.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e8f2454128974905258d86534f4fd4f91d2f1343605657ecab779d80c9d6d5e"}, - {file = "Cython-3.0.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1949d6aa7bc792554bee2b67a9fe41008acbfe22f4f8df7b6ec7b799613a4b3"}, - {file = "Cython-3.0.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9f2c6e1b8f3bcd6cb230bac1843f85114780bb8be8614855b1628b36bb510e0"}, - {file = "Cython-3.0.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:05d7eddc668ae7993643f32c7661f25544e791edb745758672ea5b1a82ecffa6"}, - {file = "Cython-3.0.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bfabe115deef4ada5d23c87bddb11289123336dcc14347011832c07db616dd93"}, - {file = "Cython-3.0.8-cp36-cp36m-win32.whl", hash = "sha256:0c38c9f0bcce2df0c3347285863621be904ac6b64c5792d871130569d893efd7"}, - {file = "Cython-3.0.8-cp36-cp36m-win_amd64.whl", hash = "sha256:6c46939c3983217d140999de7c238c3141f56b1ea349e47ca49cae899969aa2c"}, - {file = "Cython-3.0.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:115f0a50f752da6c99941b103b5cb090da63eb206abbc7c2ad33856ffc73f064"}, - {file = "Cython-3.0.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c0f29246734561c90f36e70ed0506b61aa3d044e4cc4cba559065a2a741fae"}, - {file = "Cython-3.0.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ab75242869ff71e5665fe5c96f3378e79e792fa3c11762641b6c5afbbbbe026"}, - {file = "Cython-3.0.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6717c06e9cfc6c1df18543cd31a21f5d8e378a40f70c851fa2d34f0597037abc"}, - {file = "Cython-3.0.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9d3f74388db378a3c6fd06e79a809ed98df3f56484d317b81ee762dbf3c263e0"}, - {file = "Cython-3.0.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae7ac561fd8253a9ae96311e91d12af5f701383564edc11d6338a7b60b285a6f"}, - {file = "Cython-3.0.8-cp37-cp37m-win32.whl", hash = "sha256:97b2a45845b993304f1799664fa88da676ee19442b15fdcaa31f9da7e1acc434"}, - {file = "Cython-3.0.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9e2be2b340fea46fb849d378f9b80d3c08ff2e81e2bfbcdb656e2e3cd8c6b2dc"}, - {file = "Cython-3.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2cde23c555470db3f149ede78b518e8274853745289c956a0e06ad8d982e4db9"}, - {file = "Cython-3.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7990ca127e1f1beedaf8fc8bf66541d066ef4723ad7d8d47a7cbf842e0f47580"}, - {file = "Cython-3.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b983c8e6803f016146c26854d9150ddad5662960c804ea7f0c752c9266752f0"}, - {file = "Cython-3.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a973268d7ca1a2bdf78575e459a94a78e1a0a9bb62a7db0c50041949a73b02ff"}, - {file = "Cython-3.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:61a237bc9dd23c7faef0fcfce88c11c65d0c9bb73c74ccfa408b3a012073c20e"}, - {file = "Cython-3.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3a3d67f079598af49e90ff9655bf85bd358f093d727eb21ca2708f467c489cae"}, - {file = "Cython-3.0.8-cp38-cp38-win32.whl", hash = "sha256:17a642bb01a693e34c914106566f59844b4461665066613913463a719e0dd15d"}, - {file = "Cython-3.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:2cdfc32252f3b6dc7c94032ab744dcedb45286733443c294d8f909a4854e7f83"}, - {file = "Cython-3.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa97893d99385386925d00074654aeae3a98867f298d1e12ceaf38a9054a9bae"}, - {file = "Cython-3.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05c0bf9d085c031df8f583f0d506aa3be1692023de18c45d0aaf78685bbb944"}, - {file = "Cython-3.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de892422582f5758bd8de187e98ac829330ec1007bc42c661f687792999988a7"}, - {file = "Cython-3.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:314f2355a1f1d06e3c431eaad4708cf10037b5e91e4b231d89c913989d0bdafd"}, - {file = "Cython-3.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:78825a3774211e7d5089730f00cdf7f473042acc9ceb8b9eeebe13ed3a5541de"}, - {file = "Cython-3.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:df8093deabc55f37028190cf5e575c26aad23fc673f34b85d5f45076bc37ce39"}, - {file = "Cython-3.0.8-cp39-cp39-win32.whl", hash = "sha256:1aca1b97e0095b3a9a6c33eada3f661a4ed0d499067d121239b193e5ba3bb4f0"}, - {file = "Cython-3.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:16873d78be63bd38ffb759da7ab82814b36f56c769ee02b1d5859560e4c3ac3c"}, - {file = "Cython-3.0.8-py2.py3-none-any.whl", hash = "sha256:171b27051253d3f9108e9759e504ba59ff06e7f7ba944457f94deaf9c21bf0b6"}, - {file = "Cython-3.0.8.tar.gz", hash = "sha256:8333423d8fd5765e7cceea3a9985dd1e0a5dfeb2734629e1a2ed2d6233d39de6"}, -] - -[[package]] -name = "easydict" -version = "1.11" -description = "Access dict values as attributes (works recursively)." -optional = false -python-versions = "*" -files = [ - {file = "easydict-1.11.tar.gz", hash = "sha256:dcb1d2ed28eb300c8e46cd371340373abc62f7c14d6dea74fdfc6f1069061c78"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.0" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "fastapi" -version = "0.115.8" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fastapi-0.115.8-py3-none-any.whl", hash = "sha256:753a96dd7e036b34eeef8babdfcfe3f28ff79648f86551eb36bfc1b0bf4a8cbf"}, - {file = "fastapi-0.115.8.tar.gz", hash = "sha256:0ce9111231720190473e222cdf0f07f7206ad7e53ea02beb1d2dc36e2f0741e9"}, -] - -[package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.40.0,<0.46.0" -typing-extensions = ">=4.8.0" - -[package.extras] -all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] -standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] - -[[package]] -name = "filelock" -version = "3.13.1" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.8" -files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] -typing = ["typing-extensions (>=4.8)"] - -[[package]] -name = "flask" -version = "3.0.0" -description = "A simple framework for building complex web applications." -optional = false -python-versions = ">=3.8" -files = [ - {file = "flask-3.0.0-py3-none-any.whl", hash = "sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638"}, - {file = "flask-3.0.0.tar.gz", hash = "sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58"}, -] - -[package.dependencies] -blinker = ">=1.6.2" -click = ">=8.1.3" -itsdangerous = ">=2.1.2" -Jinja2 = ">=3.1.2" -Werkzeug = ">=3.0.0" - -[package.extras] -async = ["asgiref (>=3.2)"] -dotenv = ["python-dotenv"] - -[[package]] -name = "flask-cors" -version = "4.0.1" -description = "A Flask extension adding a decorator for CORS support" -optional = false -python-versions = "*" -files = [ - {file = "Flask_Cors-4.0.1-py2.py3-none-any.whl", hash = "sha256:f2a704e4458665580c074b714c4627dd5a306b333deb9074d0b1794dfa2fb677"}, - {file = "flask_cors-4.0.1.tar.gz", hash = "sha256:eeb69b342142fdbf4766ad99357a7f3876a2ceb77689dc10ff912aac06c389e4"}, -] - -[package.dependencies] -Flask = ">=0.9" - -[[package]] -name = "flask-login" -version = "0.6.3" -description = "User authentication and session management for Flask." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333"}, - {file = "Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d"}, -] - -[package.dependencies] -Flask = ">=1.0.4" -Werkzeug = ">=1.0.1" - -[[package]] -name = "flatbuffers" -version = "23.5.26" -description = "The FlatBuffers serialization format for Python" -optional = false -python-versions = "*" -files = [ - {file = "flatbuffers-23.5.26-py2.py3-none-any.whl", hash = "sha256:c0ff356da363087b915fde4b8b45bdda73432fc17cddb3c8157472eab1422ad1"}, - {file = "flatbuffers-23.5.26.tar.gz", hash = "sha256:9ea1144cac05ce5d86e2859f431c6cd5e66cd9c78c558317c7955fb8d4c78d89"}, -] - -[[package]] -name = "fonttools" -version = "4.47.2" -description = "Tools to manipulate font files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b629108351d25512d4ea1a8393a2dba325b7b7d7308116b605ea3f8e1be88df"}, - {file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c19044256c44fe299d9a73456aabee4b4d06c6b930287be93b533b4737d70aa1"}, - {file = "fonttools-4.47.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8be28c036b9f186e8c7eaf8a11b42373e7e4949f9e9f370202b9da4c4c3f56c"}, - {file = "fonttools-4.47.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f83a4daef6d2a202acb9bf572958f91cfde5b10c8ee7fb1d09a4c81e5d851fd8"}, - {file = "fonttools-4.47.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5a5318ba5365d992666ac4fe35365f93004109d18858a3e18ae46f67907670"}, - {file = "fonttools-4.47.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8f57ecd742545362a0f7186774b2d1c53423ed9ece67689c93a1055b236f638c"}, - {file = "fonttools-4.47.2-cp310-cp310-win32.whl", hash = "sha256:a1c154bb85dc9a4cf145250c88d112d88eb414bad81d4cb524d06258dea1bdc0"}, - {file = "fonttools-4.47.2-cp310-cp310-win_amd64.whl", hash = "sha256:3e2b95dce2ead58fb12524d0ca7d63a63459dd489e7e5838c3cd53557f8933e1"}, - {file = "fonttools-4.47.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29495d6d109cdbabe73cfb6f419ce67080c3ef9ea1e08d5750240fd4b0c4763b"}, - {file = "fonttools-4.47.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0a1d313a415eaaba2b35d6cd33536560deeebd2ed758b9bfb89ab5d97dc5deac"}, - {file = "fonttools-4.47.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90f898cdd67f52f18049250a6474185ef6544c91f27a7bee70d87d77a8daf89c"}, - {file = "fonttools-4.47.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3480eeb52770ff75140fe7d9a2ec33fb67b07efea0ab5129c7e0c6a639c40c70"}, - {file = "fonttools-4.47.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0255dbc128fee75fb9be364806b940ed450dd6838672a150d501ee86523ac61e"}, - {file = "fonttools-4.47.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f791446ff297fd5f1e2247c188de53c1bfb9dd7f0549eba55b73a3c2087a2703"}, - {file = "fonttools-4.47.2-cp311-cp311-win32.whl", hash = "sha256:740947906590a878a4bde7dd748e85fefa4d470a268b964748403b3ab2aeed6c"}, - {file = "fonttools-4.47.2-cp311-cp311-win_amd64.whl", hash = "sha256:63fbed184979f09a65aa9c88b395ca539c94287ba3a364517698462e13e457c9"}, - {file = "fonttools-4.47.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4ec558c543609e71b2275c4894e93493f65d2f41c15fe1d089080c1d0bb4d635"}, - {file = "fonttools-4.47.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e040f905d542362e07e72e03612a6270c33d38281fd573160e1003e43718d68d"}, - {file = "fonttools-4.47.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dd58cc03016b281bd2c74c84cdaa6bd3ce54c5a7f47478b7657b930ac3ed8eb"}, - {file = "fonttools-4.47.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32ab2e9702dff0dd4510c7bb958f265a8d3dd5c0e2547e7b5f7a3df4979abb07"}, - {file = "fonttools-4.47.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a808f3c1d1df1f5bf39be869b6e0c263570cdafb5bdb2df66087733f566ea71"}, - {file = "fonttools-4.47.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac71e2e201df041a2891067dc36256755b1229ae167edbdc419b16da78732c2f"}, - {file = "fonttools-4.47.2-cp312-cp312-win32.whl", hash = "sha256:69731e8bea0578b3c28fdb43dbf95b9386e2d49a399e9a4ad736b8e479b08085"}, - {file = "fonttools-4.47.2-cp312-cp312-win_amd64.whl", hash = "sha256:b3e1304e5f19ca861d86a72218ecce68f391646d85c851742d265787f55457a4"}, - {file = "fonttools-4.47.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:254d9a6f7be00212bf0c3159e0a420eb19c63793b2c05e049eb337f3023c5ecc"}, - {file = "fonttools-4.47.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eabae77a07c41ae0b35184894202305c3ad211a93b2eb53837c2a1143c8bc952"}, - {file = "fonttools-4.47.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86a5ab2873ed2575d0fcdf1828143cfc6b977ac448e3dc616bb1e3d20efbafa"}, - {file = "fonttools-4.47.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13819db8445a0cec8c3ff5f243af6418ab19175072a9a92f6cc8ca7d1452754b"}, - {file = "fonttools-4.47.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4e743935139aa485fe3253fc33fe467eab6ea42583fa681223ea3f1a93dd01e6"}, - {file = "fonttools-4.47.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d49ce3ea7b7173faebc5664872243b40cf88814ca3eb135c4a3cdff66af71946"}, - {file = "fonttools-4.47.2-cp38-cp38-win32.whl", hash = "sha256:94208ea750e3f96e267f394d5588579bb64cc628e321dbb1d4243ffbc291b18b"}, - {file = "fonttools-4.47.2-cp38-cp38-win_amd64.whl", hash = "sha256:0f750037e02beb8b3569fbff701a572e62a685d2a0e840d75816592280e5feae"}, - {file = "fonttools-4.47.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d71606c9321f6701642bd4746f99b6089e53d7e9817fc6b964e90d9c5f0ecc6"}, - {file = "fonttools-4.47.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86e0427864c6c91cf77f16d1fb9bf1bbf7453e824589e8fb8461b6ee1144f506"}, - {file = "fonttools-4.47.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a00bd0e68e88987dcc047ea31c26d40a3c61185153b03457956a87e39d43c37"}, - {file = "fonttools-4.47.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5d77479fb885ef38a16a253a2f4096bc3d14e63a56d6246bfdb56365a12b20c"}, - {file = "fonttools-4.47.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5465df494f20a7d01712b072ae3ee9ad2887004701b95cb2cc6dcb9c2c97a899"}, - {file = "fonttools-4.47.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4c811d3c73b6abac275babb8aa439206288f56fdb2c6f8835e3d7b70de8937a7"}, - {file = "fonttools-4.47.2-cp39-cp39-win32.whl", hash = "sha256:5b60e3afa9635e3dfd3ace2757039593e3bd3cf128be0ddb7a1ff4ac45fa5a50"}, - {file = "fonttools-4.47.2-cp39-cp39-win_amd64.whl", hash = "sha256:7ee48bd9d6b7e8f66866c9090807e3a4a56cf43ffad48962725a190e0dd774c8"}, - {file = "fonttools-4.47.2-py3-none-any.whl", hash = "sha256:7eb7ad665258fba68fd22228a09f347469d95a97fb88198e133595947a20a184"}, - {file = "fonttools-4.47.2.tar.gz", hash = "sha256:7df26dd3650e98ca45f1e29883c96a0b9f5bb6af8d632a6a108bc744fa0bd9b3"}, -] - -[package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] -graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "pycairo", "scipy"] -lxml = ["lxml (>=4.0,<5)"] -pathops = ["skia-pathops (>=0.5.0)"] -plot = ["matplotlib"] -repacker = ["uharfbuzz (>=0.23.0)"] -symfont = ["sympy"] -type1 = ["xattr"] -ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.1.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] - -[[package]] -name = "fsspec" -version = "2023.12.2" -description = "File-system specification" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fsspec-2023.12.2-py3-none-any.whl", hash = "sha256:d800d87f72189a745fa3d6b033b9dc4a34ad069f60ca60b943a63599f5501960"}, - {file = "fsspec-2023.12.2.tar.gz", hash = "sha256:8548d39e8810b59c38014934f6b31e57f40c1b20f911f4cc2b85389c7e9bf0cb"}, -] - -[package.extras] -abfs = ["adlfs"] -adl = ["adlfs"] -arrow = ["pyarrow (>=1)"] -dask = ["dask", "distributed"] -devel = ["pytest", "pytest-cov"] -dropbox = ["dropbox", "dropboxdrivefs", "requests"] -full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] -fuse = ["fusepy"] -gcs = ["gcsfs"] -git = ["pygit2"] -github = ["requests"] -gs = ["gcsfs"] -gui = ["panel"] -hdfs = ["pyarrow (>=1)"] -http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "requests"] -libarchive = ["libarchive-c"] -oci = ["ocifs"] -s3 = ["s3fs"] -sftp = ["paramiko"] -smb = ["smbprotocol"] -ssh = ["paramiko"] -tqdm = ["tqdm"] - -[[package]] -name = "ftfy" -version = "6.3.1" -description = "Fixes mojibake and other problems with Unicode, after the fact" -optional = false -python-versions = ">=3.9" -files = [ - {file = "ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083"}, - {file = "ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec"}, -] - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "gevent" -version = "24.10.3" -description = "Coroutine-based network library" -optional = false -python-versions = ">=3.9" -files = [ - {file = "gevent-24.10.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d7a1ad0f2da582f5bd238bca067e1c6c482c30c15a6e4d14aaa3215cbb2232f3"}, - {file = "gevent-24.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4e526fdc279c655c1e809b0c34b45844182c2a6b219802da5e411bd2cf5a8ad"}, - {file = "gevent-24.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57a5c4e0bdac482c5f02f240d0354e61362df73501ef6ebafce8ef635cad7527"}, - {file = "gevent-24.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d67daed8383326dc8b5e58d88e148d29b6b52274a489e383530b0969ae7b9cb9"}, - {file = "gevent-24.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e24ffea72e27987979c009536fd0868e52239b44afe6cf7135ce8aafd0f108e"}, - {file = "gevent-24.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c1d80090485da1ea3d99205fe97908b31188c1f4857f08b333ffaf2de2e89d18"}, - {file = "gevent-24.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0c129f81d60cda614acb4b0c5731997ca05b031fb406fcb58ad53a7ade53b13"}, - {file = "gevent-24.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:26ca7a6b42d35129617025ac801135118333cad75856ffc3217b38e707383eba"}, - {file = "gevent-24.10.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:68c3a0d8402755eba7f69022e42e8021192a721ca8341908acc222ea597029b6"}, - {file = "gevent-24.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d850a453d66336272be4f1d3a8126777f3efdaea62d053b4829857f91e09755"}, - {file = "gevent-24.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e58ee3723f1fbe07d66892f1caa7481c306f653a6829b6fd16cb23d618a5915"}, - {file = "gevent-24.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b52382124eca13135a3abe4f65c6bd428656975980a48e51b17aeab68bdb14db"}, - {file = "gevent-24.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ca2266e08f43c0e22c028801dff7d92a0b102ef20e4caeb6a46abfb95f6a328"}, - {file = "gevent-24.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d758f0d4dbf32502ec87bb9b536ca8055090a16f8305f0ada3ce6f34e70f2fd7"}, - {file = "gevent-24.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0de6eb3d55c03138fda567d9bfed28487ce5d0928c5107549767a93efdf2be26"}, - {file = "gevent-24.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:385710355eadecdb70428a5ae3e7e5a45dcf888baa1426884588be9d25ac4290"}, - {file = "gevent-24.10.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3ad8fb70aa0ebc935729c9699ac31b210a49b689a7b27b7ac9f91676475f3f53"}, - {file = "gevent-24.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f18689f7a70d2ed0e75bad5036ec3c89690a493d4cfac8d7cdb258ac04b132bd"}, - {file = "gevent-24.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f4f171d4d2018170454d84c934842e1b5f6ce7468ba298f6e7f7cff15000a3"}, - {file = "gevent-24.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7021e26d70189b33c27173d4173f27bf4685d6b6f1c0ea50e5335f8491cb110c"}, - {file = "gevent-24.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34aea15f9c79f27a8faeaa361bc1e72c773a9b54a1996a2ec4eefc8bcd59a824"}, - {file = "gevent-24.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8af65a4d4feaec6042c666d22c322a310fba3b47e841ad52f724b9c3ce5da48e"}, - {file = "gevent-24.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:89c4115e3f5ada55f92b61701a46043fe42f702b5af863b029e4c1a76f6cc2d4"}, - {file = "gevent-24.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:1ce6dab94c0b0d24425ba55712de2f8c9cb21267150ca63f5bb3a0e1f165da99"}, - {file = "gevent-24.10.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:f147e38423fbe96e8731f60a63475b3d2cab2f3d10578d8ee9d10c507c58a2ff"}, - {file = "gevent-24.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18e6984ec96fc95fd67488555c38ece3015be1f38b1bcceb27b7d6c36b343008"}, - {file = "gevent-24.10.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:051b22e2758accfddb0457728bfc9abf8c3f2ce6bca43f1ff6e07b5ed9e49bf4"}, - {file = "gevent-24.10.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb5edb6433764119a664bbb148d2aea9990950aa89cc3498f475c2408d523ea3"}, - {file = "gevent-24.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce417bcaaab496bc9c77f75566531e9d93816262037b8b2dbb88b0fdcd66587c"}, - {file = "gevent-24.10.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:1c3a828b033fb02b7c31da4d75014a1f82e6c072fc0523456569a57f8b025861"}, - {file = "gevent-24.10.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f2ae3efbbd120cdf4a68b7abc27a37e61e6f443c5a06ec2c6ad94c37cd8471ec"}, - {file = "gevent-24.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:9e1210334a9bc9f76c3d008e0785ca62214f8a54e1325f6c2ecab3b6a572a015"}, - {file = "gevent-24.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70e9ed7ecb70e0df7dc97c3bc420de9a45a7c76bd5861c6cfec8c549700e681e"}, - {file = "gevent-24.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3ac83b74304487afa211a01909c7dd257e574db0cd429d866c298e21df7aeedf"}, - {file = "gevent-24.10.3-cp39-cp39-win32.whl", hash = "sha256:a9a89d6e396ef6f1e3968521bf56e8c4bee25b193bbf5d428b7782d582410822"}, - {file = "gevent-24.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:40ea3e40e8bb4fdb143c2a8edf2ccfdebd56016c7317c341ce8094c7bee08818"}, - {file = "gevent-24.10.3-pp310-pypy310_pp73-macosx_11_0_universal2.whl", hash = "sha256:e534e6a968d74463b11de6c9c67f4b4bf61775fb00f2e6e0f7fcdd412ceade18"}, - {file = "gevent-24.10.3.tar.gz", hash = "sha256:aa7ee1bd5cabb2b7ef35105f863b386c8d5e332f754b60cfc354148bd70d35d1"}, -] - -[package.dependencies] -cffi = {version = ">=1.17.1", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} -greenlet = {version = ">=3.1.1", markers = "platform_python_implementation == \"CPython\""} -"zope.event" = "*" -"zope.interface" = "*" - -[package.extras] -dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] -docs = ["furo", "repoze.sphinx.autointerface", "sphinx", "sphinxcontrib-programoutput", "zope.schema"] -monitor = ["psutil (>=5.7.0)"] -recommended = ["cffi (>=1.17.1)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)"] -test = ["cffi (>=1.17.1)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idna", "objgraph", "psutil (>=5.7.0)", "requests"] - -[[package]] -name = "geventhttpclient" -version = "2.3.1" -description = "HTTP client library for gevent" -optional = false -python-versions = ">=3.9" -files = [ - {file = "geventhttpclient-2.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da22ab7bf5af4ba3d07cffee6de448b42696e53e7ac1fe97ed289037733bf1c2"}, - {file = "geventhttpclient-2.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2399e3d4e2fae8bbd91756189da6e9d84adf8f3eaace5eef0667874a705a29f8"}, - {file = "geventhttpclient-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3e33e87d0d5b9f5782c4e6d3cb7e3592fea41af52713137d04776df7646d71b"}, - {file = "geventhttpclient-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c071db313866c3d0510feb6c0f40ec086ccf7e4a845701b6316c82c06e8b9b29"}, - {file = "geventhttpclient-2.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f36f0c6ef88a27e60af8369d9c2189fe372c6f2943182a7568e0f2ad33bb69f1"}, - {file = "geventhttpclient-2.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4624843c03a5337282a42247d987c2531193e57255ee307b36eeb4f243a0c21"}, - {file = "geventhttpclient-2.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d614573621ba827c417786057e1e20e9f96c4f6b3878c55b1b7b54e1026693bc"}, - {file = "geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5d51330a40ac9762879d0e296c279c1beae8cfa6484bb196ac829242c416b709"}, - {file = "geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc9f2162d4e8cb86bb5322d99bfd552088a3eacd540a841298f06bb8bc1f1f03"}, - {file = "geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:06e59d3397e63c65ecc7a7561a5289f0cf2e2c2252e29632741e792f57f5d124"}, - {file = "geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4436eef515b3e0c1d4a453ae32e047290e780a623c1eddb11026ae9d5fb03d42"}, - {file = "geventhttpclient-2.3.1-cp310-cp310-win32.whl", hash = "sha256:5d1cf7d8a4f8e15cc8fd7d88ac4cdb058d6274203a42587e594cc9f0850ac862"}, - {file = "geventhttpclient-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:4deaebc121036f7ea95430c2d0f80ab085b15280e6ab677a6360b70e57020e7f"}, - {file = "geventhttpclient-2.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0ae055b9ce1704f2ce72c0847df28f4e14dbb3eea79256cda6c909d82688ea3"}, - {file = "geventhttpclient-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f087af2ac439495b5388841d6f3c4de8d2573ca9870593d78f7b554aa5cfa7f5"}, - {file = "geventhttpclient-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76c367d175810facfe56281e516c9a5a4a191eff76641faaa30aa33882ed4b2f"}, - {file = "geventhttpclient-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a58376d0d461fe0322ff2ad362553b437daee1eeb92b4c0e3b1ffef9e77defbe"}, - {file = "geventhttpclient-2.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f440cc704f8a9869848a109b2c401805c17c070539b2014e7b884ecfc8591e33"}, - {file = "geventhttpclient-2.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f10c62994f9052f23948c19de930b2d1f063240462c8bd7077c2b3290e61f4fa"}, - {file = "geventhttpclient-2.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c45d9f3dd9627844c12e9ca347258c7be585bed54046336220e25ea6eac155"}, - {file = "geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:77c1a2c6e3854bf87cd5588b95174640c8a881716bd07fa0d131d082270a6795"}, - {file = "geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ce649d4e25c2d56023471df0bf1e8e2ab67dfe4ff12ce3e8fe7e6fae30cd672a"}, - {file = "geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:265d9f31b4ac8f688eebef0bd4c814ffb37a16f769ad0c8c8b8c24a84db8eab5"}, - {file = "geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2de436a9d61dae877e4e811fb3e2594e2a1df1b18f4280878f318aef48a562b9"}, - {file = "geventhttpclient-2.3.1-cp311-cp311-win32.whl", hash = "sha256:83e22178b9480b0a95edf0053d4f30b717d0b696b3c262beabe6964d9c5224b1"}, - {file = "geventhttpclient-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:97b072a282233384c1302a7dee88ad8bfedc916f06b1bc1da54f84980f1406a9"}, - {file = "geventhttpclient-2.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e1c90abcc2735cd8dd2d2572a13da32f6625392dc04862decb5c6476a3ddee22"}, - {file = "geventhttpclient-2.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5deb41c2f51247b4e568c14964f59d7b8e537eff51900564c88af3200004e678"}, - {file = "geventhttpclient-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c6f1a56a66a90c4beae2f009b5e9d42db9a58ced165aa35441ace04d69cb7b37"}, - {file = "geventhttpclient-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ee6e741849c29e3129b1ec3828ac3a5e5dcb043402f852ea92c52334fb8cabf"}, - {file = "geventhttpclient-2.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d0972096a63b1ddaa73fa3dab2c7a136e3ab8bf7999a2f85a5dee851fa77cdd"}, - {file = "geventhttpclient-2.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00675ba682fb7d19d659c14686fa8a52a65e3f301b56c2a4ee6333b380dd9467"}, - {file = "geventhttpclient-2.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea77b67c186df90473416f4403839728f70ef6cf1689cec97b4f6bbde392a8a8"}, - {file = "geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ddcc3f0fdffd9a3801e1005b73026202cffed8199863fdef9315bea9a860a032"}, - {file = "geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:c9f1ef4ec048563cc621a47ff01a4f10048ff8b676d7a4d75e5433ed8e703e56"}, - {file = "geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:a364b30bec7a0a00dbe256e2b6807e4dc866bead7ac84aaa51ca5e2c3d15c258"}, - {file = "geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:25d255383d3d6a6fbd643bb51ae1a7e4f6f7b0dbd5f3225b537d0bd0432eaf39"}, - {file = "geventhttpclient-2.3.1-cp312-cp312-win32.whl", hash = "sha256:ad0b507e354d2f398186dcb12fe526d0594e7c9387b514fb843f7a14fdf1729a"}, - {file = "geventhttpclient-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:7924e0883bc2b177cfe27aa65af6bb9dd57f3e26905c7675a2d1f3ef69df7cca"}, - {file = "geventhttpclient-2.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fe912c6456faab196b952adcd63e9353a0d5c8deb31c8d733d38f4f0ab22e359"}, - {file = "geventhttpclient-2.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b599359779c2278018786c35d70664d441a7cd0d6baef2b2cd0d1685cf478ed"}, - {file = "geventhttpclient-2.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34107b506e2c40ec7784efa282469bf86888cacddced463dceeb58c201834897"}, - {file = "geventhttpclient-2.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc34031905b2b31a80d88cd33d7e42b81812950e5304860ab6a65ee2803e2046"}, - {file = "geventhttpclient-2.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50b54f67ba2087f4d9d2172065c5c5de0f0c7f865ac350116e5452de4be31444"}, - {file = "geventhttpclient-2.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ddeb431836c2ef7fd33c505a06180dc907b474e0e8537a43ff12e12c9bf0307"}, - {file = "geventhttpclient-2.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4890713433ca19b081f70b5f7ad258a0979ec3354f9538b50b3ad7d0a86f88de"}, - {file = "geventhttpclient-2.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8ca7dcbe94cb563341087b00b6fbd0fdd70b2acc1b5d963f9ebbfbc1e5e2893"}, - {file = "geventhttpclient-2.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05a1bbdd43ae36bcc10b3dbfa0806aefc5033a91efecfddfe56159446a46ea71"}, - {file = "geventhttpclient-2.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f82c454595a88a5e510ae0985711ef398386998b6f37d90fc30e9ff1a2001280"}, - {file = "geventhttpclient-2.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b032a5cdb1721921f4cd36aad620af318263b462962cfb23d648cdb93aab232"}, - {file = "geventhttpclient-2.3.1-cp39-cp39-win32.whl", hash = "sha256:ce2c7d18bac7ffdacc4a86cd490bea6136a7d1e1170f8624f2e3bbe3b189d5b8"}, - {file = "geventhttpclient-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:6ca50dd9761971d3557b897108933b34fb4a11533d52f0f2753840c740a2861a"}, - {file = "geventhttpclient-2.3.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c31431e38df45b3c79bf3c9427c796adb8263d622bc6fa25e2f6ba916c2aad93"}, - {file = "geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:855ab1e145575769b180b57accb0573a77cd6a7392f40a6ef7bc9a4926ebd77b"}, - {file = "geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a374aad77c01539e786d0c7829bec2eba034ccd45733c1bf9811ad18d2a8ecd"}, - {file = "geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c1e97460608304f400485ac099736fff3566d3d8db2038533d466f8cf5de5a"}, - {file = "geventhttpclient-2.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4f843f81ee44ba4c553a1b3f73115e0ad8f00044023c24db29f5b1df3da08465"}, - {file = "geventhttpclient-2.3.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:321b73c73d73b85cfeff36b9b5ee04174ec8406fb3dadc129558a26ccb879360"}, - {file = "geventhttpclient-2.3.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:829d03c2a140edbe74ad1fb4f850384f585f3e06fc47cfe647d065412b93926f"}, - {file = "geventhttpclient-2.3.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:994c543f156db7bce3bae15491a0e041eeb3f1cf467e0d1db0c161a900a90bec"}, - {file = "geventhttpclient-2.3.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4beff505306aa9da5cdfe2f206b403ec7c8d06a22d6b7248365772858c4ee8c"}, - {file = "geventhttpclient-2.3.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fb0a9673074541ccda09a2423fa16f4528819ceb1ba19d252213f6aca7d4b44a"}, - {file = "geventhttpclient-2.3.1.tar.gz", hash = "sha256:b40ddac8517c456818942c7812f555f84702105c82783238c9fcb8dc12675185"}, -] - -[package.dependencies] -brotli = "*" -certifi = "*" -gevent = "*" -urllib3 = "*" - -[package.extras] -benchmarks = ["httplib2", "httpx", "requests", "urllib3"] -dev = ["dpkt", "pytest", "requests"] -examples = ["oauth2"] - -[[package]] -name = "greenlet" -version = "3.1.1" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -files = [ - {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, - {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, - {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, - {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, - {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, - {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, - {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, - {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, - {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, - {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, - {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, - {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, - {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, - {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, - {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, - {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, - {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, - {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, - {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, - {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, - {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, - {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, - {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, - {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, - {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, - {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, - {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, - {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, - {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, - {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, - {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, - {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, - {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "gunicorn" -version = "23.0.0" -description = "WSGI HTTP Server for UNIX" -optional = false -python-versions = ">=3.7" -files = [ - {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, - {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, -] - -[package.dependencies] -packaging = "*" - -[package.extras] -eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] -gevent = ["gevent (>=1.4.0)"] -setproctitle = ["setproctitle"] -testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] -tornado = ["tornado (>=0.2)"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "httpcore" -version = "1.0.2" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, - {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.23.0)"] - -[[package]] -name = "httptools" -version = "0.6.4" -description = "A collection of framework independent HTTP protocol utils." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"}, - {file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"}, - {file = "httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1"}, - {file = "httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50"}, - {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959"}, - {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4"}, - {file = "httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c"}, - {file = "httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069"}, - {file = "httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a"}, - {file = "httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975"}, - {file = "httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636"}, - {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721"}, - {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988"}, - {file = "httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17"}, - {file = "httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2"}, - {file = "httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44"}, - {file = "httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1"}, - {file = "httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2"}, - {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81"}, - {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f"}, - {file = "httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970"}, - {file = "httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660"}, - {file = "httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083"}, - {file = "httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3"}, - {file = "httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071"}, - {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5"}, - {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0"}, - {file = "httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8"}, - {file = "httptools-0.6.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba"}, - {file = "httptools-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc"}, - {file = "httptools-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff"}, - {file = "httptools-0.6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490"}, - {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43"}, - {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440"}, - {file = "httptools-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f"}, - {file = "httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003"}, - {file = "httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab"}, - {file = "httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547"}, - {file = "httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9"}, - {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076"}, - {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd"}, - {file = "httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6"}, - {file = "httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c"}, -] - -[package.extras] -test = ["Cython (>=0.29.24)"] - -[[package]] -name = "httpx" -version = "0.28.1" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, - {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "huggingface-hub" -version = "0.29.1" -description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "huggingface_hub-0.29.1-py3-none-any.whl", hash = "sha256:352f69caf16566c7b6de84b54a822f6238e17ddd8ae3da4f8f2272aea5b198d5"}, - {file = "huggingface_hub-0.29.1.tar.gz", hash = "sha256:9524eae42077b8ff4fc459ceb7a514eca1c1232b775276b009709fe2a084f250"}, -] - -[package.dependencies] -filelock = "*" -fsspec = ">=2023.5.0" -packaging = ">=20.9" -pyyaml = ">=5.1" -requests = "*" -tqdm = ">=4.42.1" -typing-extensions = ">=3.7.4.3" - -[package.extras] -all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] -cli = ["InquirerPy (==0.3.4)"] -dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] -fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] -hf-transfer = ["hf-transfer (>=0.1.4)"] -inference = ["aiohttp"] -quality = ["libcst (==1.4.0)", "mypy (==1.5.1)", "ruff (>=0.9.0)"] -tensorflow = ["graphviz", "pydot", "tensorflow"] -tensorflow-testing = ["keras (<3.0)", "tensorflow"] -testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] -torch = ["safetensors[torch]", "torch"] -typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] - -[[package]] -name = "humanfriendly" -version = "10.0" -description = "Human friendly output for text interfaces using Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, - {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, -] - -[package.dependencies] -pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} - -[[package]] -name = "idna" -version = "3.6" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, -] - -[[package]] -name = "imageio" -version = "2.33.1" -description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." -optional = false -python-versions = ">=3.8" -files = [ - {file = "imageio-2.33.1-py3-none-any.whl", hash = "sha256:c5094c48ccf6b2e6da8b4061cd95e1209380afafcbeae4a4e280938cce227e1d"}, - {file = "imageio-2.33.1.tar.gz", hash = "sha256:78722d40b137bd98f5ec7312119f8aea9ad2049f76f434748eb306b6937cc1ce"}, -] - -[package.dependencies] -numpy = "*" -pillow = ">=8.3.2" - -[package.extras] -all-plugins = ["astropy", "av", "imageio-ffmpeg", "pillow-heif", "psutil", "tifffile"] -all-plugins-pypy = ["av", "imageio-ffmpeg", "pillow-heif", "psutil", "tifffile"] -build = ["wheel"] -dev = ["black", "flake8", "fsspec[github]", "pytest", "pytest-cov"] -docs = ["numpydoc", "pydata-sphinx-theme", "sphinx (<6)"] -ffmpeg = ["imageio-ffmpeg", "psutil"] -fits = ["astropy"] -full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "itk", "numpydoc", "pillow-heif", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "sphinx (<6)", "tifffile", "wheel"] -gdal = ["gdal"] -itk = ["itk"] -linting = ["black", "flake8"] -pillow-heif = ["pillow-heif"] -pyav = ["av"] -test = ["fsspec[github]", "pytest", "pytest-cov"] -tifffile = ["tifffile"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "insightface" -version = "0.7.3" -description = "InsightFace Python Library" -optional = false -python-versions = "*" -files = [ - {file = "insightface-0.7.3.tar.gz", hash = "sha256:f191f719612ebb37018f41936814500544cd0f86e6fcd676c023f354c668ddf7"}, -] - -[package.dependencies] -albumentations = "*" -cython = "*" -easydict = "*" -matplotlib = "*" -numpy = "*" -onnx = "*" -Pillow = "*" -prettytable = "*" -requests = "*" -scikit-image = "*" -scikit-learn = "*" -scipy = "*" -tqdm = "*" - -[[package]] -name = "itsdangerous" -version = "2.1.2" -description = "Safely pass data to untrusted environments and back." -optional = false -python-versions = ">=3.7" -files = [ - {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, - {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, -] - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "joblib" -version = "1.3.2" -description = "Lightweight pipelining with Python functions" -optional = false -python-versions = ">=3.7" -files = [ - {file = "joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9"}, - {file = "joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1"}, -] - -[[package]] -name = "kiwisolver" -version = "1.4.5" -description = "A fast implementation of the Cassowary constraint solver" -optional = false -python-versions = ">=3.7" -files = [ - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, - {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, -] - -[[package]] -name = "lazy-loader" -version = "0.3" -description = "lazy_loader" -optional = false -python-versions = ">=3.7" -files = [ - {file = "lazy_loader-0.3-py3-none-any.whl", hash = "sha256:1e9e76ee8631e264c62ce10006718e80b2cfc74340d17d1031e0f84af7478554"}, - {file = "lazy_loader-0.3.tar.gz", hash = "sha256:3b68898e34f5b2a29daaaac172c6555512d0f32074f147e2254e4a6d9d838f37"}, -] - -[package.extras] -lint = ["pre-commit (>=3.3)"] -test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] - -[[package]] -name = "locust" -version = "2.33.0" -description = "Developer-friendly load testing framework" -optional = false -python-versions = ">=3.9" -files = [ - {file = "locust-2.33.0-py3-none-any.whl", hash = "sha256:77fcc5cc35cceee5e12d99f5bb23bc441d145bdef6967c2e93d6e4d93451553e"}, - {file = "locust-2.33.0.tar.gz", hash = "sha256:ba291b7ab2349cc2db540adb8888bc93feb89ea4e4e10d80b935e5065091e8e9"}, -] - -[package.dependencies] -configargparse = ">=1.5.5" -flask = ">=2.0.0" -flask-cors = ">=3.0.10" -flask-login = ">=0.6.3" -gevent = [ - {version = ">=22.10.2", markers = "python_version <= \"3.12\""}, - {version = ">=24.10.1", markers = "python_version > \"3.13\""}, -] -geventhttpclient = ">=2.3.1" -msgpack = ">=1.0.0" -psutil = ">=5.9.1" -pywin32 = {version = "*", markers = "sys_platform == \"win32\""} -pyzmq = ">=25.0.0" -requests = [ - {version = ">=2.26.0", markers = "python_version <= \"3.11\""}, - {version = ">=2.32.2", markers = "python_version > \"3.11\""}, -] -setuptools = ">=70.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.11\""} -werkzeug = ">=2.0.0" - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "2.1.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] - -[[package]] -name = "matplotlib" -version = "3.8.2" -description = "Python plotting package" -optional = false -python-versions = ">=3.9" -files = [ - {file = "matplotlib-3.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:09796f89fb71a0c0e1e2f4bdaf63fb2cefc84446bb963ecdeb40dfee7dfa98c7"}, - {file = "matplotlib-3.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9c6976748a25e8b9be51ea028df49b8e561eed7809146da7a47dbecebab367"}, - {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78e4f2cedf303869b782071b55fdde5987fda3038e9d09e58c91cc261b5ad18"}, - {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e208f46cf6576a7624195aa047cb344a7f802e113bb1a06cfd4bee431de5e31"}, - {file = "matplotlib-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a569130ff53798ea5f50afce7406e91fdc471ca1e0e26ba976a8c734c9427a"}, - {file = "matplotlib-3.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:830f00640c965c5b7f6bc32f0d4ce0c36dfe0379f7dd65b07a00c801713ec40a"}, - {file = "matplotlib-3.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d86593ccf546223eb75a39b44c32788e6f6440d13cfc4750c1c15d0fcb850b63"}, - {file = "matplotlib-3.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a5430836811b7652991939012f43d2808a2db9b64ee240387e8c43e2e5578c8"}, - {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9576723858a78751d5aacd2497b8aef29ffea6d1c95981505877f7ac28215c6"}, - {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba9cbd8ac6cf422f3102622b20f8552d601bf8837e49a3afed188d560152788"}, - {file = "matplotlib-3.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:03f9d160a29e0b65c0790bb07f4f45d6a181b1ac33eb1bb0dd225986450148f0"}, - {file = "matplotlib-3.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:3773002da767f0a9323ba1a9b9b5d00d6257dbd2a93107233167cfb581f64717"}, - {file = "matplotlib-3.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c318c1e95e2f5926fba326f68177dee364aa791d6df022ceb91b8221bd0a627"}, - {file = "matplotlib-3.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:091275d18d942cf1ee9609c830a1bc36610607d8223b1b981c37d5c9fc3e46a4"}, - {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b0f3b8ea0e99e233a4bcc44590f01604840d833c280ebb8fe5554fd3e6cfe8d"}, - {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b1704a530395aaf73912be741c04d181f82ca78084fbd80bc737be04848331"}, - {file = "matplotlib-3.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533b0e3b0c6768eef8cbe4b583731ce25a91ab54a22f830db2b031e83cca9213"}, - {file = "matplotlib-3.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:0f4fc5d72b75e2c18e55eb32292659cf731d9d5b312a6eb036506304f4675630"}, - {file = "matplotlib-3.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:deaed9ad4da0b1aea77fe0aa0cebb9ef611c70b3177be936a95e5d01fa05094f"}, - {file = "matplotlib-3.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:172f4d0fbac3383d39164c6caafd3255ce6fa58f08fc392513a0b1d3b89c4f89"}, - {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7d36c2209d9136cd8e02fab1c0ddc185ce79bc914c45054a9f514e44c787917"}, - {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5864bdd7da445e4e5e011b199bb67168cdad10b501750367c496420f2ad00843"}, - {file = "matplotlib-3.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ef8345b48e95cee45ff25192ed1f4857273117917a4dcd48e3905619bcd9c9b8"}, - {file = "matplotlib-3.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:7c48d9e221b637c017232e3760ed30b4e8d5dfd081daf327e829bf2a72c731b4"}, - {file = "matplotlib-3.8.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa11b3c6928a1e496c1a79917d51d4cd5d04f8a2e75f21df4949eeefdf697f4b"}, - {file = "matplotlib-3.8.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1095fecf99eeb7384dabad4bf44b965f929a5f6079654b681193edf7169ec20"}, - {file = "matplotlib-3.8.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:bddfb1db89bfaa855912261c805bd0e10218923cc262b9159a49c29a7a1c1afa"}, - {file = "matplotlib-3.8.2.tar.gz", hash = "sha256:01a978b871b881ee76017152f1f1a0cbf6bd5f7b8ff8c96df0df1bd57d8755a1"}, -] - -[package.dependencies] -contourpy = ">=1.0.1" -cycler = ">=0.10" -fonttools = ">=4.22.0" -kiwisolver = ">=1.3.1" -numpy = ">=1.21,<2" -packaging = ">=20.0" -pillow = ">=8" -pyparsing = ">=2.3.1" -python-dateutil = ">=2.7" - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "mpmath" -version = "1.3.0" -description = "Python library for arbitrary-precision floating-point arithmetic" -optional = false -python-versions = "*" -files = [ - {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, - {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, -] - -[package.extras] -develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] -docs = ["sphinx"] -gmpy = ["gmpy2 (>=2.1.0a4)"] -tests = ["pytest (>=4.6)"] - -[[package]] -name = "msgpack" -version = "1.0.7" -description = "MessagePack serializer" -optional = false -python-versions = ">=3.8" -files = [ - {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862"}, - {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329"}, - {file = "msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b"}, - {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6"}, - {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee"}, - {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d"}, - {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d"}, - {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1"}, - {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681"}, - {file = "msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9"}, - {file = "msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e"}, - {file = "msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1"}, - {file = "msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5"}, - {file = "msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9"}, - {file = "msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf"}, - {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95"}, - {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0"}, - {file = "msgpack-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7"}, - {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d"}, - {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524"}, - {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc"}, - {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc"}, - {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf"}, - {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c"}, - {file = "msgpack-1.0.7-cp38-cp38-win32.whl", hash = "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2"}, - {file = "msgpack-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c"}, - {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f"}, - {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81"}, - {file = "msgpack-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc"}, - {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d"}, - {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7"}, - {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61"}, - {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819"}, - {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd"}, - {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f"}, - {file = "msgpack-1.0.7-cp39-cp39-win32.whl", hash = "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad"}, - {file = "msgpack-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3"}, - {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, -] - -[[package]] -name = "mypy" -version = "1.15.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"}, - {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"}, - {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"}, - {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"}, - {file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"}, - {file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"}, - {file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"}, - {file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"}, - {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"}, - {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"}, - {file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"}, - {file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"}, - {file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"}, - {file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"}, - {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"}, - {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"}, - {file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"}, - {file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"}, - {file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"}, - {file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"}, - {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"}, - {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"}, - {file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"}, - {file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"}, - {file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"}, - {file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"}, - {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"}, - {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"}, - {file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"}, - {file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"}, - {file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"}, - {file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"}, -] - -[package.dependencies] -mypy_extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing_extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "networkx" -version = "3.2.1" -description = "Python package for creating and manipulating graphs and networks" -optional = false -python-versions = ">=3.9" -files = [ - {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, - {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, -] - -[package.extras] -default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] -developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] -doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] -test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] - -[[package]] -name = "numpy" -version = "1.26.4" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, -] - -[[package]] -name = "onnx" -version = "1.16.0" -description = "Open Neural Network Exchange" -optional = false -python-versions = ">=3.8" -files = [ - {file = "onnx-1.16.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:9eadbdce25b19d6216f426d6d99b8bc877a65ed92cbef9707751c6669190ba4f"}, - {file = "onnx-1.16.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:034ae21a2aaa2e9c14119a840d2926d213c27aad29e5e3edaa30145a745048e1"}, - {file = "onnx-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec22a43d74eb1f2303373e2fbe7fbcaa45fb225f4eb146edfed1356ada7a9aea"}, - {file = "onnx-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:298f28a2b5ac09145fa958513d3d1e6b349ccf86a877dbdcccad57713fe360b3"}, - {file = "onnx-1.16.0-cp310-cp310-win32.whl", hash = "sha256:66300197b52beca08bc6262d43c103289c5d45fde43fb51922ed1eb83658cf0c"}, - {file = "onnx-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:ae0029f5e47bf70a1a62e7f88c80bca4ef39b844a89910039184221775df5e43"}, - {file = "onnx-1.16.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:f51179d4af3372b4f3800c558d204b592c61e4b4a18b8f61e0eea7f46211221a"}, - {file = "onnx-1.16.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:5202559070afec5144332db216c20f2fff8323cf7f6512b0ca11b215eacc5bf3"}, - {file = "onnx-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77579e7c15b4df39d29465b216639a5f9b74026bdd9e4b6306cd19a32dcfe67c"}, - {file = "onnx-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e60ca76ac24b65c25860d0f2d2cdd96d6320d062a01dd8ce87c5743603789b8"}, - {file = "onnx-1.16.0-cp311-cp311-win32.whl", hash = "sha256:81b4ee01bc554e8a2b11ac6439882508a5377a1c6b452acd69a1eebb83571117"}, - {file = "onnx-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:7449241e70b847b9c3eb8dae622df8c1b456d11032a9d7e26e0ee8a698d5bf86"}, - {file = "onnx-1.16.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:03a627488b1a9975d95d6a55582af3e14c7f3bb87444725b999935ddd271d352"}, - {file = "onnx-1.16.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c392faeabd9283ee344ccb4b067d1fea9dfc614fa1f0de7c47589efd79e15e78"}, - {file = "onnx-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0efeb46985de08f0efe758cb54ad3457e821a05c2eaf5ba2ccb8cd1602c08084"}, - {file = "onnx-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddf14a3d32234f23e44abb73a755cb96a423fac7f004e8f046f36b10214151ee"}, - {file = "onnx-1.16.0-cp312-cp312-win32.whl", hash = "sha256:62a2e27ae8ba5fc9b4a2620301446a517b5ffaaf8566611de7a7c2160f5bcf4c"}, - {file = "onnx-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:3e0860fea94efde777e81a6f68f65761ed5e5f3adea2e050d7fbe373a9ae05b3"}, - {file = "onnx-1.16.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:70a90649318f3470985439ea078277c9fb2a2e6e2fd7c8f3f2b279402ad6c7e6"}, - {file = "onnx-1.16.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:71839546b7f93be4fa807995b182ab4b4414c9dbf049fee11eaaced16fcf8df2"}, - {file = "onnx-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7665217c45a61eb44718c8e9349d2ad004efa0cb9fbc4be5c6d5e18b9fe12b52"}, - {file = "onnx-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5752bbbd5717304a7643643dba383a2fb31e8eb0682f4e7b7d141206328a73b"}, - {file = "onnx-1.16.0-cp38-cp38-win32.whl", hash = "sha256:257858cbcb2055284f09fa2ae2b1cfd64f5850367da388d6e7e7b05920a40c90"}, - {file = "onnx-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:209fe84995a28038e29ae8369edd35f33e0ef1ebc3bddbf6584629823469deb1"}, - {file = "onnx-1.16.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:8cf3e518b1b1b960be542e7c62bed4e5219e04c85d540817b7027029537dec92"}, - {file = "onnx-1.16.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:30f02beaf081c7d9fa3a8c566a912fc4408e28fc33b1452d58f890851691d364"}, - {file = "onnx-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fb29a9a692b522deef1f6b8f2145da62c0c43ea1ed5b4c0f66f827fdc28847d"}, - {file = "onnx-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7755cbd5f4e47952e37276ea5978a46fc8346684392315902b5ed4a719d87d06"}, - {file = "onnx-1.16.0-cp39-cp39-win32.whl", hash = "sha256:7532343dc5b8b5e7c3e3efa441a3100552f7600155c4db9120acd7574f64ffbf"}, - {file = "onnx-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:d7886c05aa6d583ec42f6287678923c1e343afc4350e49d5b36a0023772ffa22"}, - {file = "onnx-1.16.0.tar.gz", hash = "sha256:237c6987c6c59d9f44b6136f5819af79574f8d96a760a1fa843bede11f3822f7"}, -] - -[package.dependencies] -numpy = ">=1.20" -protobuf = ">=3.20.2" - -[package.extras] -reference = ["Pillow", "google-re2"] - -[[package]] -name = "onnxruntime" -version = "1.20.1" -description = "ONNX Runtime is a runtime accelerator for Machine Learning models" -optional = false -python-versions = "*" -files = [ - {file = "onnxruntime-1.20.1-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:e50ba5ff7fed4f7d9253a6baf801ca2883cc08491f9d32d78a80da57256a5439"}, - {file = "onnxruntime-1.20.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b2908b50101a19e99c4d4e97ebb9905561daf61829403061c1adc1b588bc0de"}, - {file = "onnxruntime-1.20.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d82daaec24045a2e87598b8ac2b417b1cce623244e80e663882e9fe1aae86410"}, - {file = "onnxruntime-1.20.1-cp310-cp310-win32.whl", hash = "sha256:4c4b251a725a3b8cf2aab284f7d940c26094ecd9d442f07dd81ab5470e99b83f"}, - {file = "onnxruntime-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:d3b616bb53a77a9463707bb313637223380fc327f5064c9a782e8ec69c22e6a2"}, - {file = "onnxruntime-1.20.1-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:06bfbf02ca9ab5f28946e0f912a562a5f005301d0c419283dc57b3ed7969bb7b"}, - {file = "onnxruntime-1.20.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6243e34d74423bdd1edf0ae9596dd61023b260f546ee17d701723915f06a9f7"}, - {file = "onnxruntime-1.20.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5eec64c0269dcdb8d9a9a53dc4d64f87b9e0c19801d9321246a53b7eb5a7d1bc"}, - {file = "onnxruntime-1.20.1-cp311-cp311-win32.whl", hash = "sha256:a19bc6e8c70e2485a1725b3d517a2319603acc14c1f1a017dda0afe6d4665b41"}, - {file = "onnxruntime-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:8508887eb1c5f9537a4071768723ec7c30c28eb2518a00d0adcd32c89dea3221"}, - {file = "onnxruntime-1.20.1-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:22b0655e2bf4f2161d52706e31f517a0e54939dc393e92577df51808a7edc8c9"}, - {file = "onnxruntime-1.20.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f56e898815963d6dc4ee1c35fc6c36506466eff6d16f3cb9848cea4e8c8172"}, - {file = "onnxruntime-1.20.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb71a814f66517a65628c9e4a2bb530a6edd2cd5d87ffa0af0f6f773a027d99e"}, - {file = "onnxruntime-1.20.1-cp312-cp312-win32.whl", hash = "sha256:bd386cc9ee5f686ee8a75ba74037750aca55183085bf1941da8efcfe12d5b120"}, - {file = "onnxruntime-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:19c2d843eb074f385e8bbb753a40df780511061a63f9def1b216bf53860223fb"}, - {file = "onnxruntime-1.20.1-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:cc01437a32d0042b606f462245c8bbae269e5442797f6213e36ce61d5abdd8cc"}, - {file = "onnxruntime-1.20.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb44b08e017a648924dbe91b82d89b0c105b1adcfe31e90d1dc06b8677ad37be"}, - {file = "onnxruntime-1.20.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bda6aebdf7917c1d811f21d41633df00c58aff2bef2f598f69289c1f1dabc4b3"}, - {file = "onnxruntime-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:d30367df7e70f1d9fc5a6a68106f5961686d39b54d3221f760085524e8d38e16"}, - {file = "onnxruntime-1.20.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9158465745423b2b5d97ed25aa7740c7d38d2993ee2e5c3bfacb0c4145c49d8"}, - {file = "onnxruntime-1.20.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0df6f2df83d61f46e842dbcde610ede27218947c33e994545a22333491e72a3b"}, -] - -[package.dependencies] -coloredlogs = "*" -flatbuffers = "*" -numpy = ">=1.21.6" -packaging = "*" -protobuf = "*" -sympy = "*" - -[[package]] -name = "onnxruntime-gpu" -version = "1.19.2" -description = "ONNX Runtime is a runtime accelerator for Machine Learning models" -optional = false -python-versions = "*" -files = [ - {file = "onnxruntime_gpu-1.19.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a49740e079e7c5215830d30cde3df792e903df007aa0b0fd7aa797937061b27a"}, - {file = "onnxruntime_gpu-1.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:b895920bb5e4241299f68874e0becdc2635ea0142939c11e7ff5ae5b28993613"}, - {file = "onnxruntime_gpu-1.19.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:562fc7c755393eaad9751e56149339dd201ffbfdb3ef5f43ff21d0619ba9045f"}, - {file = "onnxruntime_gpu-1.19.2-cp311-cp311-win_amd64.whl", hash = "sha256:522f7495918176cb8c1a3c78bde7152d984f7096acc786c73a27643af8af87c9"}, - {file = "onnxruntime_gpu-1.19.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:554a02a3fac0119707eb87327908afd21c4e6f0fa5bf9a034398f098adc316c5"}, - {file = "onnxruntime_gpu-1.19.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c6165a405027e3c0f11d189ae7013b5d66919b3381f9bfb3405c0c0cf07968"}, - {file = "onnxruntime_gpu-1.19.2-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4a8562e1e6f1912870c60bfaf8233c82b86e5b93ae39f211b650ac0f2015430"}, - {file = "onnxruntime_gpu-1.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:55505c99e18688a7c68fdc811ed6e7a315aa36f543b33920c77d03a627d2c3f5"}, - {file = "onnxruntime_gpu-1.19.2-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9e369f01f55ea726ae5d28f18236426e52e97c433f0b7682054e61c478a06c9"}, - {file = "onnxruntime_gpu-1.19.2-cp39-cp39-win_amd64.whl", hash = "sha256:c8b8128174b0470537e9f4983aeecc002a435d13914970c2af2f41d244ef2781"}, -] - -[package.dependencies] -coloredlogs = "*" -flatbuffers = "*" -numpy = ">=1.21.6" -packaging = "*" -protobuf = "*" -sympy = "*" - -[package.source] -type = "legacy" -url = "https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple" -reference = "cuda12" - -[[package]] -name = "onnxruntime-openvino" -version = "1.18.0" -description = "ONNX Runtime is a runtime accelerator for Machine Learning models" -optional = false -python-versions = "*" -files = [ - {file = "onnxruntime_openvino-1.18.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:565b874d21bcd48126da7d62f57db019f5ec0e1f82ae9b0740afa2ad91f8d331"}, - {file = "onnxruntime_openvino-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:7f1931060f710a6c8e32121bb73044c4772ef5925802fc8776d3fe1e87ab3f75"}, - {file = "onnxruntime_openvino-1.18.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb1723d386f70a8e26398d983ebe35d2c25ba56e9cdb382670ebbf1f5139f8ba"}, - {file = "onnxruntime_openvino-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:874a1e263dd86674593e5a879257650b06a8609c4d5768c3d8ed8dc4ae874b9c"}, - {file = "onnxruntime_openvino-1.18.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:597eb18f3de7ead69b08a242d74c4573b28bbfba40ca2a1a40f75bf7a834808e"}, -] - -[package.dependencies] -coloredlogs = "*" -flatbuffers = "*" -numpy = ">=1.26.4" -packaging = "*" -protobuf = "*" -sympy = "*" - -[[package]] -name = "opencv-python-headless" -version = "4.11.0.86" -description = "Wrapper package for OpenCV python bindings." -optional = false -python-versions = ">=3.6" -files = [ - {file = "opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798"}, - {file = "opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:48128188ade4a7e517237c8e1e11a9cdf5c282761473383e77beb875bb1e61ca"}, - {file = "opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:a66c1b286a9de872c343ee7c3553b084244299714ebb50fbdcd76f07ebbe6c81"}, - {file = "opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6efabcaa9df731f29e5ea9051776715b1bdd1845d7c9530065c7951d2a2899eb"}, - {file = "opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e0a27c19dd1f40ddff94976cfe43066fbbe9dfbb2ec1907d66c19caef42a57b"}, - {file = "opencv_python_headless-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:f447d8acbb0b6f2808da71fddd29c1cdd448d2bc98f72d9bb78a7a898fc9621b"}, - {file = "opencv_python_headless-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""}, - {version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, -] - -[[package]] -name = "orjson" -version = "3.10.15" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.8" -files = [ - {file = "orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c2c79fa308e6edb0ffab0a31fd75a7841bf2a79a20ef08a3c6e3b26814c8ca8"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cb85490aa6bf98abd20607ab5c8324c0acb48d6da7863a51be48505646c814"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763dadac05e4e9d2bc14938a45a2d0560549561287d41c465d3c58aec818b164"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a330b9b4734f09a623f74a7490db713695e13b67c959713b78369f26b3dee6bf"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a61a4622b7ff861f019974f73d8165be1bd9a0855e1cad18ee167acacabeb061"}, - {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd271247691574416b3228db667b84775c497b245fa275c6ab90dc1ffbbd2b3"}, - {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4759b109c37f635aa5c5cc93a1b26927bfde24b254bcc0e1149a9fada253d2d"}, - {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e992fd5cfb8b9f00bfad2fd7a05a4299db2bbe92e6440d9dd2fab27655b3182"}, - {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f95fb363d79366af56c3f26b71df40b9a583b07bbaaf5b317407c4d58497852e"}, - {file = "orjson-3.10.15-cp310-cp310-win32.whl", hash = "sha256:f9875f5fea7492da8ec2444839dcc439b0ef298978f311103d0b7dfd775898ab"}, - {file = "orjson-3.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:17085a6aa91e1cd70ca8533989a18b5433e15d29c574582f76f821737c8d5806"}, - {file = "orjson-3.10.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c4cc83960ab79a4031f3119cc4b1a1c627a3dc09df125b27c4201dff2af7eaa6"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddbeef2481d895ab8be5185f2432c334d6dec1f5d1933a9c83014d188e102cef"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e590a0477b23ecd5b0ac865b1b907b01b3c5535f5e8a8f6ab0e503efb896334"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6be38bd103d2fd9bdfa31c2720b23b5d47c6796bcb1d1b598e3924441b4298d"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff4f6edb1578960ed628a3b998fa54d78d9bb3e2eb2cfc5c2a09732431c678d0"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0482b21d0462eddd67e7fce10b89e0b6ac56570424662b685a0d6fccf581e13"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bb5cc3527036ae3d98b65e37b7986a918955f85332c1ee07f9d3f82f3a6899b5"}, - {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d569c1c462912acdd119ccbf719cf7102ea2c67dd03b99edcb1a3048651ac96b"}, - {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1e6d33efab6b71d67f22bf2962895d3dc6f82a6273a965fab762e64fa90dc399"}, - {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c33be3795e299f565681d69852ac8c1bc5c84863c0b0030b2b3468843be90388"}, - {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eea80037b9fae5339b214f59308ef0589fc06dc870578b7cce6d71eb2096764c"}, - {file = "orjson-3.10.15-cp311-cp311-win32.whl", hash = "sha256:d5ac11b659fd798228a7adba3e37c010e0152b78b1982897020a8e019a94882e"}, - {file = "orjson-3.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:cf45e0214c593660339ef63e875f32ddd5aa3b4adc15e662cdb80dc49e194f8e"}, - {file = "orjson-3.10.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d11c0714fc85bfcf36ada1179400862da3288fc785c30e8297844c867d7505a"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba5a1e85d554e3897fa9fe6fbcff2ed32d55008973ec9a2b992bd9a65d2352d"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7723ad949a0ea502df656948ddd8b392780a5beaa4c3b5f97e525191b102fff0"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fd9bc64421e9fe9bd88039e7ce8e58d4fead67ca88e3a4014b143cec7684fd4"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dadba0e7b6594216c214ef7894c4bd5f08d7c0135f4dd0145600be4fbcc16767"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48f59114fe318f33bbaee8ebeda696d8ccc94c9e90bc27dbe72153094e26f41"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:035fb83585e0f15e076759b6fedaf0abb460d1765b6a36f48018a52858443514"}, - {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d13b7fe322d75bf84464b075eafd8e7dd9eae05649aa2a5354cfa32f43c59f17"}, - {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7066b74f9f259849629e0d04db6609db4cf5b973248f455ba5d3bd58a4daaa5b"}, - {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88dc3f65a026bd3175eb157fea994fca6ac7c4c8579fc5a86fc2114ad05705b7"}, - {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b342567e5465bd99faa559507fe45e33fc76b9fb868a63f1642c6bc0735ad02a"}, - {file = "orjson-3.10.15-cp312-cp312-win32.whl", hash = "sha256:0a4f27ea5617828e6b58922fdbec67b0aa4bb844e2d363b9244c47fa2180e665"}, - {file = "orjson-3.10.15-cp312-cp312-win_amd64.whl", hash = "sha256:ef5b87e7aa9545ddadd2309efe6824bd3dd64ac101c15dae0f2f597911d46eaa"}, - {file = "orjson-3.10.15-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bae0e6ec2b7ba6895198cd981b7cca95d1487d0147c8ed751e5632ad16f031a6"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93ce145b2db1252dd86af37d4165b6faa83072b46e3995ecc95d4b2301b725a"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c203f6f969210128af3acae0ef9ea6aab9782939f45f6fe02d05958fe761ef9"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8918719572d662e18b8af66aef699d8c21072e54b6c82a3f8f6404c1f5ccd5e0"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f71eae9651465dff70aa80db92586ad5b92df46a9373ee55252109bb6b703307"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e117eb299a35f2634e25ed120c37c641398826c2f5a3d3cc39f5993b96171b9e"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13242f12d295e83c2955756a574ddd6741c81e5b99f2bef8ed8d53e47a01e4b7"}, - {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7946922ada8f3e0b7b958cc3eb22cfcf6c0df83d1fe5521b4a100103e3fa84c8"}, - {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b7155eb1623347f0f22c38c9abdd738b287e39b9982e1da227503387b81b34ca"}, - {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:208beedfa807c922da4e81061dafa9c8489c6328934ca2a562efa707e049e561"}, - {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eca81f83b1b8c07449e1d6ff7074e82e3fd6777e588f1a6632127f286a968825"}, - {file = "orjson-3.10.15-cp313-cp313-win32.whl", hash = "sha256:c03cd6eea1bd3b949d0d007c8d57049aa2b39bd49f58b4b2af571a5d3833d890"}, - {file = "orjson-3.10.15-cp313-cp313-win_amd64.whl", hash = "sha256:fd56a26a04f6ba5fb2045b0acc487a63162a958ed837648c5781e1fe3316cfbf"}, - {file = "orjson-3.10.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e8afd6200e12771467a1a44e5ad780614b86abb4b11862ec54861a82d677746"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9a18c500f19273e9e104cca8c1f0b40a6470bcccfc33afcc088045d0bf5ea6"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb00b7bfbdf5d34a13180e4805d76b4567025da19a197645ca746fc2fb536586"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33aedc3d903378e257047fee506f11e0833146ca3e57a1a1fb0ddb789876c1e1"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0099ae6aed5eb1fc84c9eb72b95505a3df4267e6962eb93cdd5af03be71c98"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c864a80a2d467d7786274fce0e4f93ef2a7ca4ff31f7fc5634225aaa4e9e98c"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c25774c9e88a3e0013d7d1a6c8056926b607a61edd423b50eb5c88fd7f2823ae"}, - {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e78c211d0074e783d824ce7bb85bf459f93a233eb67a5b5003498232ddfb0e8a"}, - {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:43e17289ffdbbac8f39243916c893d2ae41a2ea1a9cbb060a56a4d75286351ae"}, - {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:781d54657063f361e89714293c095f506c533582ee40a426cb6489c48a637b81"}, - {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6875210307d36c94873f553786a808af2788e362bd0cf4c8e66d976791e7b528"}, - {file = "orjson-3.10.15-cp38-cp38-win32.whl", hash = "sha256:305b38b2b8f8083cc3d618927d7f424349afce5975b316d33075ef0f73576b60"}, - {file = "orjson-3.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:5dd9ef1639878cc3efffed349543cbf9372bdbd79f478615a1c633fe4e4180d1"}, - {file = "orjson-3.10.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ffe19f3e8d68111e8644d4f4e267a069ca427926855582ff01fc012496d19969"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d433bf32a363823863a96561a555227c18a522a8217a6f9400f00ddc70139ae2"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da03392674f59a95d03fa5fb9fe3a160b0511ad84b7a3914699ea5a1b3a38da2"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a63bb41559b05360ded9132032239e47983a39b151af1201f07ec9370715c82"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3766ac4702f8f795ff3fa067968e806b4344af257011858cc3d6d8721588b53f"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1c73dcc8fadbd7c55802d9aa093b36878d34a3b3222c41052ce6b0fc65f8e8"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b299383825eafe642cbab34be762ccff9fd3408d72726a6b2a4506d410a71ab3"}, - {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:abc7abecdbf67a173ef1316036ebbf54ce400ef2300b4e26a7b843bd446c2480"}, - {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:3614ea508d522a621384c1d6639016a5a2e4f027f3e4a1c93a51867615d28829"}, - {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:295c70f9dc154307777ba30fe29ff15c1bcc9dfc5c48632f37d20a607e9ba85a"}, - {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:63309e3ff924c62404923c80b9e2048c1f74ba4b615e7584584389ada50ed428"}, - {file = "orjson-3.10.15-cp39-cp39-win32.whl", hash = "sha256:a2f708c62d026fb5340788ba94a55c23df4e1869fec74be455e0b2f5363b8507"}, - {file = "orjson-3.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:efcf6c735c3d22ef60c4aa27a5238f1a477df85e9b15f2142f9d669beb2d13fd"}, - {file = "orjson-3.10.15.tar.gz", hash = "sha256:05ca7fe452a2e9d8d9d706a2984c95b9c2ebc5db417ce0b7a49b91d50642a23e"}, -] - -[[package]] -name = "packaging" -version = "23.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "pillow" -version = "10.4.0" -description = "Python Imaging Library (Fork)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, - {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, - {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, - {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, - {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, - {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, - {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, - {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, - {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, - {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, - {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, - {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, - {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, - {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, - {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, - {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, - {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, - {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, - {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, - {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] -xmp = ["defusedxml"] - -[[package]] -name = "platformdirs" -version = "4.1.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, -] - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "prettytable" -version = "3.9.0" -description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" -optional = false -python-versions = ">=3.8" -files = [ - {file = "prettytable-3.9.0-py3-none-any.whl", hash = "sha256:a71292ab7769a5de274b146b276ce938786f56c31cf7cea88b6f3775d82fe8c8"}, - {file = "prettytable-3.9.0.tar.gz", hash = "sha256:f4ed94803c23073a90620b201965e5dc0bccf1760b7a7eaf3158cab8aaffdf34"}, -] - -[package.dependencies] -wcwidth = "*" - -[package.extras] -tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"] - -[[package]] -name = "protobuf" -version = "4.25.2" -description = "" -optional = false -python-versions = ">=3.8" -files = [ - {file = "protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6"}, - {file = "protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9"}, - {file = "protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d"}, - {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62"}, - {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020"}, - {file = "protobuf-4.25.2-cp38-cp38-win32.whl", hash = "sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61"}, - {file = "protobuf-4.25.2-cp38-cp38-win_amd64.whl", hash = "sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62"}, - {file = "protobuf-4.25.2-cp39-cp39-win32.whl", hash = "sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3"}, - {file = "protobuf-4.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0"}, - {file = "protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830"}, - {file = "protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e"}, -] - -[[package]] -name = "psutil" -version = "5.9.7" -description = "Cross-platform lib for process and system monitoring in Python." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -files = [ - {file = "psutil-5.9.7-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0bd41bf2d1463dfa535942b2a8f0e958acf6607ac0be52265ab31f7923bcd5e6"}, - {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:5794944462509e49d4d458f4dbfb92c47539e7d8d15c796f141f474010084056"}, - {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:fe361f743cb3389b8efda21980d93eb55c1f1e3898269bc9a2a1d0bb7b1f6508"}, - {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:e469990e28f1ad738f65a42dcfc17adaed9d0f325d55047593cb9033a0ab63df"}, - {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:3c4747a3e2ead1589e647e64aad601981f01b68f9398ddf94d01e3dc0d1e57c7"}, - {file = "psutil-5.9.7-cp27-none-win32.whl", hash = "sha256:1d4bc4a0148fdd7fd8f38e0498639ae128e64538faa507df25a20f8f7fb2341c"}, - {file = "psutil-5.9.7-cp27-none-win_amd64.whl", hash = "sha256:4c03362e280d06bbbfcd52f29acd79c733e0af33d707c54255d21029b8b32ba6"}, - {file = "psutil-5.9.7-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ea36cc62e69a13ec52b2f625c27527f6e4479bca2b340b7a452af55b34fcbe2e"}, - {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1132704b876e58d277168cd729d64750633d5ff0183acf5b3c986b8466cd0284"}, - {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8b7f07948f1304497ce4f4684881250cd859b16d06a1dc4d7941eeb6233bfe"}, - {file = "psutil-5.9.7-cp36-cp36m-win32.whl", hash = "sha256:b27f8fdb190c8c03914f908a4555159327d7481dac2f01008d483137ef3311a9"}, - {file = "psutil-5.9.7-cp36-cp36m-win_amd64.whl", hash = "sha256:44969859757f4d8f2a9bd5b76eba8c3099a2c8cf3992ff62144061e39ba8568e"}, - {file = "psutil-5.9.7-cp37-abi3-win32.whl", hash = "sha256:c727ca5a9b2dd5193b8644b9f0c883d54f1248310023b5ad3e92036c5e2ada68"}, - {file = "psutil-5.9.7-cp37-abi3-win_amd64.whl", hash = "sha256:f37f87e4d73b79e6c5e749440c3113b81d1ee7d26f21c19c47371ddea834f414"}, - {file = "psutil-5.9.7-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:032f4f2c909818c86cea4fe2cc407f1c0f0cde8e6c6d702b28b8ce0c0d143340"}, - {file = "psutil-5.9.7.tar.gz", hash = "sha256:3f02134e82cfb5d089fddf20bb2e03fd5cd52395321d1c8458a9e58500ff417c"}, -] - -[package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] - -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] - -[[package]] -name = "pydantic" -version = "2.10.6" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, - {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.27.2" -typing-extensions = ">=4.12.2" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] - -[[package]] -name = "pydantic-core" -version = "2.27.2" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, - {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pydantic-settings" -version = "2.8.1" -description = "Settings management using Pydantic" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c"}, - {file = "pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585"}, -] - -[package.dependencies] -pydantic = ">=2.7.0" -python-dotenv = ">=0.21.0" - -[package.extras] -azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] -toml = ["tomli (>=2.0.1)"] -yaml = ["pyyaml (>=6.0.1)"] - -[[package]] -name = "pygments" -version = "2.17.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, -] - -[package.extras] -plugins = ["importlib-metadata"] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyparsing" -version = "3.1.1" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.6.8" -files = [ - {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, - {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pyreadline3" -version = "3.4.1" -description = "A python implementation of GNU readline." -optional = false -python-versions = "*" -files = [ - {file = "pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb"}, - {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"}, -] - -[[package]] -name = "pytest" -version = "8.3.4" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, - {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "0.25.3" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"}, - {file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"}, -] - -[package.dependencies] -pytest = ">=8.2,<9" - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - -[[package]] -name = "pytest-cov" -version = "6.0.0" -description = "Pytest plugin for measuring coverage." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, - {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, -] - -[package.dependencies] -coverage = {version = ">=7.5", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] - -[[package]] -name = "pytest-mock" -version = "3.14.0" -description = "Thin-wrapper around the mock package for easier use with pytest" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, - {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, -] - -[package.dependencies] -pytest = ">=6.2.5" - -[package.extras] -dev = ["pre-commit", "pytest-asyncio", "tox"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-dotenv" -version = "1.0.0" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, - {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "python-multipart" -version = "0.0.20" -description = "A streaming multipart parser for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, - {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, -] - -[[package]] -name = "pywin32" -version = "306" -description = "Python for Window Extensions" -optional = false -python-versions = "*" -files = [ - {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, - {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, - {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, - {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, - {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, - {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, - {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, - {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, - {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, - {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, - {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, - {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, - {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, - {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - -[[package]] -name = "pyzmq" -version = "25.1.2" -description = "Python bindings for 0MQ" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:e624c789359f1a16f83f35e2c705d07663ff2b4d4479bad35621178d8f0f6ea4"}, - {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49151b0efece79f6a79d41a461d78535356136ee70084a1c22532fc6383f4ad0"}, - {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9a5f194cf730f2b24d6af1f833c14c10f41023da46a7f736f48b6d35061e76e"}, - {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:faf79a302f834d9e8304fafdc11d0d042266667ac45209afa57e5efc998e3872"}, - {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f51a7b4ead28d3fca8dda53216314a553b0f7a91ee8fc46a72b402a78c3e43d"}, - {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0ddd6d71d4ef17ba5a87becf7ddf01b371eaba553c603477679ae817a8d84d75"}, - {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:246747b88917e4867e2367b005fc8eefbb4a54b7db363d6c92f89d69abfff4b6"}, - {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:00c48ae2fd81e2a50c3485de1b9d5c7c57cd85dc8ec55683eac16846e57ac979"}, - {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a68d491fc20762b630e5db2191dd07ff89834086740f70e978bb2ef2668be08"}, - {file = "pyzmq-25.1.2-cp310-cp310-win32.whl", hash = "sha256:09dfe949e83087da88c4a76767df04b22304a682d6154de2c572625c62ad6886"}, - {file = "pyzmq-25.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:fa99973d2ed20417744fca0073390ad65ce225b546febb0580358e36aa90dba6"}, - {file = "pyzmq-25.1.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:82544e0e2d0c1811482d37eef297020a040c32e0687c1f6fc23a75b75db8062c"}, - {file = "pyzmq-25.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01171fc48542348cd1a360a4b6c3e7d8f46cdcf53a8d40f84db6707a6768acc1"}, - {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc69c96735ab501419c432110016329bf0dea8898ce16fab97c6d9106dc0b348"}, - {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e124e6b1dd3dfbeb695435dff0e383256655bb18082e094a8dd1f6293114642"}, - {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7598d2ba821caa37a0f9d54c25164a4fa351ce019d64d0b44b45540950458840"}, - {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d1299d7e964c13607efd148ca1f07dcbf27c3ab9e125d1d0ae1d580a1682399d"}, - {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4e6f689880d5ad87918430957297c975203a082d9a036cc426648fcbedae769b"}, - {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cc69949484171cc961e6ecd4a8911b9ce7a0d1f738fcae717177c231bf77437b"}, - {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9880078f683466b7f567b8624bfc16cad65077be046b6e8abb53bed4eeb82dd3"}, - {file = "pyzmq-25.1.2-cp311-cp311-win32.whl", hash = "sha256:4e5837af3e5aaa99a091302df5ee001149baff06ad22b722d34e30df5f0d9097"}, - {file = "pyzmq-25.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:25c2dbb97d38b5ac9fd15586e048ec5eb1e38f3d47fe7d92167b0c77bb3584e9"}, - {file = "pyzmq-25.1.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:11e70516688190e9c2db14fcf93c04192b02d457b582a1f6190b154691b4c93a"}, - {file = "pyzmq-25.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:313c3794d650d1fccaaab2df942af9f2c01d6217c846177cfcbc693c7410839e"}, - {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b3cbba2f47062b85fe0ef9de5b987612140a9ba3a9c6d2543c6dec9f7c2ab27"}, - {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc31baa0c32a2ca660784d5af3b9487e13b61b3032cb01a115fce6588e1bed30"}, - {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c9087b109070c5ab0b383079fa1b5f797f8d43e9a66c07a4b8b8bdecfd88ee"}, - {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f8429b17cbb746c3e043cb986328da023657e79d5ed258b711c06a70c2ea7537"}, - {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5074adeacede5f810b7ef39607ee59d94e948b4fd954495bdb072f8c54558181"}, - {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7ae8f354b895cbd85212da245f1a5ad8159e7840e37d78b476bb4f4c3f32a9fe"}, - {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b264bf2cc96b5bc43ce0e852be995e400376bd87ceb363822e2cb1964fcdc737"}, - {file = "pyzmq-25.1.2-cp312-cp312-win32.whl", hash = "sha256:02bbc1a87b76e04fd780b45e7f695471ae6de747769e540da909173d50ff8e2d"}, - {file = "pyzmq-25.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:ced111c2e81506abd1dc142e6cd7b68dd53747b3b7ae5edbea4578c5eeff96b7"}, - {file = "pyzmq-25.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7b6d09a8962a91151f0976008eb7b29b433a560fde056ec7a3db9ec8f1075438"}, - {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967668420f36878a3c9ecb5ab33c9d0ff8d054f9c0233d995a6d25b0e95e1b6b"}, - {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5edac3f57c7ddaacdb4d40f6ef2f9e299471fc38d112f4bc6d60ab9365445fb0"}, - {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0dabfb10ef897f3b7e101cacba1437bd3a5032ee667b7ead32bbcdd1a8422fe7"}, - {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2c6441e0398c2baacfe5ba30c937d274cfc2dc5b55e82e3749e333aabffde561"}, - {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:16b726c1f6c2e7625706549f9dbe9b06004dfbec30dbed4bf50cbdfc73e5b32a"}, - {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:a86c2dd76ef71a773e70551a07318b8e52379f58dafa7ae1e0a4be78efd1ff16"}, - {file = "pyzmq-25.1.2-cp36-cp36m-win32.whl", hash = "sha256:359f7f74b5d3c65dae137f33eb2bcfa7ad9ebefd1cab85c935f063f1dbb245cc"}, - {file = "pyzmq-25.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:55875492f820d0eb3417b51d96fea549cde77893ae3790fd25491c5754ea2f68"}, - {file = "pyzmq-25.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b8c8a419dfb02e91b453615c69568442e897aaf77561ee0064d789705ff37a92"}, - {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8807c87fa893527ae8a524c15fc505d9950d5e856f03dae5921b5e9aa3b8783b"}, - {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5e319ed7d6b8f5fad9b76daa0a68497bc6f129858ad956331a5835785761e003"}, - {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3c53687dde4d9d473c587ae80cc328e5b102b517447456184b485587ebd18b62"}, - {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9add2e5b33d2cd765ad96d5eb734a5e795a0755f7fc49aa04f76d7ddda73fd70"}, - {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e690145a8c0c273c28d3b89d6fb32c45e0d9605b2293c10e650265bf5c11cfec"}, - {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:00a06faa7165634f0cac1abb27e54d7a0b3b44eb9994530b8ec73cf52e15353b"}, - {file = "pyzmq-25.1.2-cp37-cp37m-win32.whl", hash = "sha256:0f97bc2f1f13cb16905a5f3e1fbdf100e712d841482b2237484360f8bc4cb3d7"}, - {file = "pyzmq-25.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6cc0020b74b2e410287e5942e1e10886ff81ac77789eb20bec13f7ae681f0fdd"}, - {file = "pyzmq-25.1.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:bef02cfcbded83473bdd86dd8d3729cd82b2e569b75844fb4ea08fee3c26ae41"}, - {file = "pyzmq-25.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e10a4b5a4b1192d74853cc71a5e9fd022594573926c2a3a4802020360aa719d8"}, - {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8c5f80e578427d4695adac6fdf4370c14a2feafdc8cb35549c219b90652536ae"}, - {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5dde6751e857910c1339890f3524de74007958557593b9e7e8c5f01cd919f8a7"}, - {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea1608dd169da230a0ad602d5b1ebd39807ac96cae1845c3ceed39af08a5c6df"}, - {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0f513130c4c361201da9bc69df25a086487250e16b5571ead521b31ff6b02220"}, - {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:019744b99da30330798bb37df33549d59d380c78e516e3bab9c9b84f87a9592f"}, - {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2e2713ef44be5d52dd8b8e2023d706bf66cb22072e97fc71b168e01d25192755"}, - {file = "pyzmq-25.1.2-cp38-cp38-win32.whl", hash = "sha256:07cd61a20a535524906595e09344505a9bd46f1da7a07e504b315d41cd42eb07"}, - {file = "pyzmq-25.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb7e49a17fb8c77d3119d41a4523e432eb0c6932187c37deb6fbb00cc3028088"}, - {file = "pyzmq-25.1.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:94504ff66f278ab4b7e03e4cba7e7e400cb73bfa9d3d71f58d8972a8dc67e7a6"}, - {file = "pyzmq-25.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6dd0d50bbf9dca1d0bdea219ae6b40f713a3fb477c06ca3714f208fd69e16fd8"}, - {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:004ff469d21e86f0ef0369717351073e0e577428e514c47c8480770d5e24a565"}, - {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c0b5ca88a8928147b7b1e2dfa09f3b6c256bc1135a1338536cbc9ea13d3b7add"}, - {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9a79f1d2495b167119d02be7448bfba57fad2a4207c4f68abc0bab4b92925b"}, - {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:518efd91c3d8ac9f9b4f7dd0e2b7b8bf1a4fe82a308009016b07eaa48681af82"}, - {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1ec23bd7b3a893ae676d0e54ad47d18064e6c5ae1fadc2f195143fb27373f7f6"}, - {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db36c27baed588a5a8346b971477b718fdc66cf5b80cbfbd914b4d6d355e44e2"}, - {file = "pyzmq-25.1.2-cp39-cp39-win32.whl", hash = "sha256:39b1067f13aba39d794a24761e385e2eddc26295826530a8c7b6c6c341584289"}, - {file = "pyzmq-25.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:8e9f3fabc445d0ce320ea2c59a75fe3ea591fdbdeebec5db6de530dd4b09412e"}, - {file = "pyzmq-25.1.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a8c1d566344aee826b74e472e16edae0a02e2a044f14f7c24e123002dcff1c05"}, - {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759cfd391a0996345ba94b6a5110fca9c557ad4166d86a6e81ea526c376a01e8"}, - {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c61e346ac34b74028ede1c6b4bcecf649d69b707b3ff9dc0fab453821b04d1e"}, - {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb8fc1f8d69b411b8ec0b5f1ffbcaf14c1db95b6bccea21d83610987435f1a4"}, - {file = "pyzmq-25.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3c00c9b7d1ca8165c610437ca0c92e7b5607b2f9076f4eb4b095c85d6e680a1d"}, - {file = "pyzmq-25.1.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:df0c7a16ebb94452d2909b9a7b3337940e9a87a824c4fc1c7c36bb4404cb0cde"}, - {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:45999e7f7ed5c390f2e87ece7f6c56bf979fb213550229e711e45ecc7d42ccb8"}, - {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ac170e9e048b40c605358667aca3d94e98f604a18c44bdb4c102e67070f3ac9b"}, - {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b604734bec94f05f81b360a272fc824334267426ae9905ff32dc2be433ab96"}, - {file = "pyzmq-25.1.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a793ac733e3d895d96f865f1806f160696422554e46d30105807fdc9841b9f7d"}, - {file = "pyzmq-25.1.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0806175f2ae5ad4b835ecd87f5f85583316b69f17e97786f7443baaf54b9bb98"}, - {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ef12e259e7bc317c7597d4f6ef59b97b913e162d83b421dd0db3d6410f17a244"}, - {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea253b368eb41116011add00f8d5726762320b1bda892f744c91997b65754d73"}, - {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b9b1f2ad6498445a941d9a4fee096d387fee436e45cc660e72e768d3d8ee611"}, - {file = "pyzmq-25.1.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8b14c75979ce932c53b79976a395cb2a8cd3aaf14aef75e8c2cb55a330b9b49d"}, - {file = "pyzmq-25.1.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:889370d5174a741a62566c003ee8ddba4b04c3f09a97b8000092b7ca83ec9c49"}, - {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a18fff090441a40ffda8a7f4f18f03dc56ae73f148f1832e109f9bffa85df15"}, - {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99a6b36f95c98839ad98f8c553d8507644c880cf1e0a57fe5e3a3f3969040882"}, - {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4345c9a27f4310afbb9c01750e9461ff33d6fb74cd2456b107525bbeebcb5be3"}, - {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3516e0b6224cf6e43e341d56da15fd33bdc37fa0c06af4f029f7d7dfceceabbc"}, - {file = "pyzmq-25.1.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:146b9b1f29ead41255387fb07be56dc29639262c0f7344f570eecdcd8d683314"}, - {file = "pyzmq-25.1.2.tar.gz", hash = "sha256:93f1aa311e8bb912e34f004cf186407a4e90eec4f0ecc0efd26056bf7eda0226"}, -] - -[package.dependencies] -cffi = {version = "*", markers = "implementation_name == \"pypy\""} - -[[package]] -name = "qudida" -version = "0.0.4" -description = "QUick and DIrty Domain Adaptation" -optional = false -python-versions = ">=3.5.0" -files = [ - {file = "qudida-0.0.4-py3-none-any.whl", hash = "sha256:4519714c40cd0f2e6c51e1735edae8f8b19f4efe1f33be13e9d644ca5f736dd6"}, - {file = "qudida-0.0.4.tar.gz", hash = "sha256:db198e2887ab0c9aa0023e565afbff41dfb76b361f85fd5e13f780d75ba18cc8"}, -] - -[package.dependencies] -numpy = ">=0.18.0" -opencv-python-headless = ">=4.0.1" -scikit-learn = ">=0.19.1" -typing-extensions = "*" - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rich" -version = "13.9.4" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, - {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "ruff" -version = "0.9.9" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -files = [ - {file = "ruff-0.9.9-py3-none-linux_armv6l.whl", hash = "sha256:628abb5ea10345e53dff55b167595a159d3e174d6720bf19761f5e467e68d367"}, - {file = "ruff-0.9.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6cd1428e834b35d7493354723543b28cc11dc14d1ce19b685f6e68e07c05ec7"}, - {file = "ruff-0.9.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5ee162652869120ad260670706f3cd36cd3f32b0c651f02b6da142652c54941d"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aa0f6b75082c9be1ec5a1db78c6d4b02e2375c3068438241dc19c7c306cc61a"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:584cc66e89fb5f80f84b05133dd677a17cdd86901d6479712c96597a3f28e7fe"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf3369325761a35aba75cd5c55ba1b5eb17d772f12ab168fbfac54be85cf18c"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3403a53a32a90ce929aa2f758542aca9234befa133e29f4933dcef28a24317be"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:18454e7fa4e4d72cffe28a37cf6a73cb2594f81ec9f4eca31a0aaa9ccdfb1590"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fadfe2c88724c9617339f62319ed40dcdadadf2888d5afb88bf3adee7b35bfb"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6df104d08c442a1aabcfd254279b8cc1e2cbf41a605aa3e26610ba1ec4acf0b0"}, - {file = "ruff-0.9.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d7c62939daf5b2a15af48abbd23bea1efdd38c312d6e7c4cedf5a24e03207e17"}, - {file = "ruff-0.9.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9494ba82a37a4b81b6a798076e4a3251c13243fc37967e998efe4cce58c8a8d1"}, - {file = "ruff-0.9.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4efd7a96ed6d36ef011ae798bf794c5501a514be369296c672dab7921087fa57"}, - {file = "ruff-0.9.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ab90a7944c5a1296f3ecb08d1cbf8c2da34c7e68114b1271a431a3ad30cb660e"}, - {file = "ruff-0.9.9-py3-none-win32.whl", hash = "sha256:6b4c376d929c25ecd6d87e182a230fa4377b8e5125a4ff52d506ee8c087153c1"}, - {file = "ruff-0.9.9-py3-none-win_amd64.whl", hash = "sha256:837982ea24091d4c1700ddb2f63b7070e5baec508e43b01de013dc7eff974ff1"}, - {file = "ruff-0.9.9-py3-none-win_arm64.whl", hash = "sha256:3ac78f127517209fe6d96ab00f3ba97cafe38718b23b1db3e96d8b2d39e37ddf"}, - {file = "ruff-0.9.9.tar.gz", hash = "sha256:0062ed13f22173e85f8f7056f9a24016e692efeea8704d1a5e8011b8aa850933"}, -] - -[[package]] -name = "scikit-image" -version = "0.22.0" -description = "Image processing in Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "scikit_image-0.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74ec5c1d4693506842cc7c9487c89d8fc32aed064e9363def7af08b8f8cbb31d"}, - {file = "scikit_image-0.22.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:a05ae4fe03d802587ed8974e900b943275548cde6a6807b785039d63e9a7a5ff"}, - {file = "scikit_image-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a92dca3d95b1301442af055e196a54b5a5128c6768b79fc0a4098f1d662dee6"}, - {file = "scikit_image-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3663d063d8bf2fb9bdfb0ca967b9ee3b6593139c860c7abc2d2351a8a8863938"}, - {file = "scikit_image-0.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:ebdbdc901bae14dab637f8d5c99f6d5cc7aaf4a3b6f4003194e003e9f688a6fc"}, - {file = "scikit_image-0.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95d6da2d8a44a36ae04437c76d32deb4e3c993ffc846b394b9949fd8ded73cb2"}, - {file = "scikit_image-0.22.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:2c6ef454a85f569659b813ac2a93948022b0298516b757c9c6c904132be327e2"}, - {file = "scikit_image-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e87872f067444ee90a00dd49ca897208308645382e8a24bd3e76f301af2352cd"}, - {file = "scikit_image-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5c378db54e61b491b9edeefff87e49fcf7fdf729bb93c777d7a5f15d36f743e"}, - {file = "scikit_image-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:2bcb74adb0634258a67f66c2bb29978c9a3e222463e003b67ba12056c003971b"}, - {file = "scikit_image-0.22.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:003ca2274ac0fac252280e7179ff986ff783407001459ddea443fe7916e38cff"}, - {file = "scikit_image-0.22.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:cf3c0c15b60ae3e557a0c7575fbd352f0c3ce0afca562febfe3ab80efbeec0e9"}, - {file = "scikit_image-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b23908dd4d120e6aecb1ed0277563e8cbc8d6c0565bdc4c4c6475d53608452"}, - {file = "scikit_image-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be79d7493f320a964f8fcf603121595ba82f84720de999db0fcca002266a549a"}, - {file = "scikit_image-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:722b970aa5da725dca55252c373b18bbea7858c1cdb406e19f9b01a4a73b30b2"}, - {file = "scikit_image-0.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:22318b35044cfeeb63ee60c56fc62450e5fe516228138f1d06c7a26378248a86"}, - {file = "scikit_image-0.22.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:9e801c44a814afdadeabf4dffdffc23733e393767958b82319706f5fa3e1eaa9"}, - {file = "scikit_image-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c472a1fb3665ec5c00423684590631d95f9afcbc97f01407d348b821880b2cb3"}, - {file = "scikit_image-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b7a6c89e8d6252332121b58f50e1625c35f7d6a85489c0b6b7ee4f5155d547a"}, - {file = "scikit_image-0.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:5071b8f6341bfb0737ab05c8ab4ac0261f9e25dbcc7b5d31e5ed230fd24a7929"}, - {file = "scikit_image-0.22.0.tar.gz", hash = "sha256:018d734df1d2da2719087d15f679d19285fce97cd37695103deadfaef2873236"}, -] - -[package.dependencies] -imageio = ">=2.27" -lazy_loader = ">=0.3" -networkx = ">=2.8" -numpy = ">=1.22" -packaging = ">=21" -pillow = ">=9.0.1" -scipy = ">=1.8" -tifffile = ">=2022.8.12" - -[package.extras] -build = ["Cython (>=0.29.32)", "build", "meson-python (>=0.14)", "ninja", "numpy (>=1.22)", "packaging (>=21)", "pythran", "setuptools (>=67)", "spin (==0.6)", "wheel"] -data = ["pooch (>=1.6.0)"] -developer = ["pre-commit", "tomli"] -docs = ["PyWavelets (>=1.1.1)", "dask[array] (>=2022.9.2)", "ipykernel", "ipywidgets", "kaleido", "matplotlib (>=3.5)", "myst-parser", "numpydoc (>=1.6)", "pandas (>=1.5)", "plotly (>=5.10)", "pooch (>=1.6)", "pydata-sphinx-theme (>=0.14.1)", "pytest-runner", "scikit-learn (>=1.1)", "seaborn (>=0.11)", "sphinx (>=7.2)", "sphinx-copybutton", "sphinx-gallery (>=0.14)", "sphinx_design (>=0.5)", "tifffile (>=2022.8.12)"] -optional = ["PyWavelets (>=1.1.1)", "SimpleITK", "astropy (>=5.0)", "cloudpickle (>=0.2.1)", "dask[array] (>=2021.1.0)", "matplotlib (>=3.5)", "pooch (>=1.6.0)", "pyamg", "scikit-learn (>=1.1)"] -test = ["asv", "matplotlib (>=3.5)", "numpydoc (>=1.5)", "pooch (>=1.6.0)", "pytest (>=7.0)", "pytest-cov (>=2.11.0)", "pytest-faulthandler", "pytest-localserver"] - -[[package]] -name = "scikit-learn" -version = "1.3.2" -description = "A set of python modules for machine learning and data mining" -optional = false -python-versions = ">=3.8" -files = [ - {file = "scikit-learn-1.3.2.tar.gz", hash = "sha256:a2f54c76accc15a34bfb9066e6c7a56c1e7235dda5762b990792330b52ccfb05"}, - {file = "scikit_learn-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e326c0eb5cf4d6ba40f93776a20e9a7a69524c4db0757e7ce24ba222471ee8a1"}, - {file = "scikit_learn-1.3.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:535805c2a01ccb40ca4ab7d081d771aea67e535153e35a1fd99418fcedd1648a"}, - {file = "scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1215e5e58e9880b554b01187b8c9390bf4dc4692eedeaf542d3273f4785e342c"}, - {file = "scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ee107923a623b9f517754ea2f69ea3b62fc898a3641766cb7deb2f2ce450161"}, - {file = "scikit_learn-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:35a22e8015048c628ad099da9df5ab3004cdbf81edc75b396fd0cff8699ac58c"}, - {file = "scikit_learn-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6fb6bc98f234fda43163ddbe36df8bcde1d13ee176c6dc9b92bb7d3fc842eb66"}, - {file = "scikit_learn-1.3.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:18424efee518a1cde7b0b53a422cde2f6625197de6af36da0b57ec502f126157"}, - {file = "scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3271552a5eb16f208a6f7f617b8cc6d1f137b52c8a1ef8edf547db0259b2c9fb"}, - {file = "scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4144a5004a676d5022b798d9e573b05139e77f271253a4703eed295bde0433"}, - {file = "scikit_learn-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:67f37d708f042a9b8d59551cf94d30431e01374e00dc2645fa186059c6c5d78b"}, - {file = "scikit_learn-1.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8db94cd8a2e038b37a80a04df8783e09caac77cbe052146432e67800e430c028"}, - {file = "scikit_learn-1.3.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:61a6efd384258789aa89415a410dcdb39a50e19d3d8410bd29be365bcdd512d5"}, - {file = "scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb06f8dce3f5ddc5dee1715a9b9f19f20d295bed8e3cd4fa51e1d050347de525"}, - {file = "scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b2de18d86f630d68fe1f87af690d451388bb186480afc719e5f770590c2ef6c"}, - {file = "scikit_learn-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:0402638c9a7c219ee52c94cbebc8fcb5eb9fe9c773717965c1f4185588ad3107"}, - {file = "scikit_learn-1.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a19f90f95ba93c1a7f7924906d0576a84da7f3b2282ac3bfb7a08a32801add93"}, - {file = "scikit_learn-1.3.2-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:b8692e395a03a60cd927125eef3a8e3424d86dde9b2370d544f0ea35f78a8073"}, - {file = "scikit_learn-1.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15e1e94cc23d04d39da797ee34236ce2375ddea158b10bee3c343647d615581d"}, - {file = "scikit_learn-1.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:785a2213086b7b1abf037aeadbbd6d67159feb3e30263434139c98425e3dcfcf"}, - {file = "scikit_learn-1.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:64381066f8aa63c2710e6b56edc9f0894cc7bf59bd71b8ce5613a4559b6145e0"}, - {file = "scikit_learn-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6c43290337f7a4b969d207e620658372ba3c1ffb611f8bc2b6f031dc5c6d1d03"}, - {file = "scikit_learn-1.3.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:dc9002fc200bed597d5d34e90c752b74df516d592db162f756cc52836b38fe0e"}, - {file = "scikit_learn-1.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d08ada33e955c54355d909b9c06a4789a729977f165b8bae6f225ff0a60ec4a"}, - {file = "scikit_learn-1.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763f0ae4b79b0ff9cca0bf3716bcc9915bdacff3cebea15ec79652d1cc4fa5c9"}, - {file = "scikit_learn-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:ed932ea780517b00dae7431e031faae6b49b20eb6950918eb83bd043237950e0"}, -] - -[package.dependencies] -joblib = ">=1.1.1" -numpy = ">=1.17.3,<2.0" -scipy = ">=1.5.0" -threadpoolctl = ">=2.0.0" - -[package.extras] -benchmark = ["matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "pandas (>=1.0.5)"] -docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)", "sphinx (>=6.0.0)", "sphinx-copybutton (>=0.5.2)", "sphinx-gallery (>=0.10.1)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] -examples = ["matplotlib (>=3.1.3)", "pandas (>=1.0.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)"] -tests = ["black (>=23.3.0)", "matplotlib (>=3.1.3)", "mypy (>=1.3)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.0.272)", "scikit-image (>=0.16.2)"] - -[[package]] -name = "scipy" -version = "1.11.4" -description = "Fundamental algorithms for scientific computing in Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "scipy-1.11.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc9a714581f561af0848e6b69947fda0614915f072dfd14142ed1bfe1b806710"}, - {file = "scipy-1.11.4-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cf00bd2b1b0211888d4dc75656c0412213a8b25e80d73898083f402b50f47e41"}, - {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9999c008ccf00e8fbcce1236f85ade5c569d13144f77a1946bef8863e8f6eb4"}, - {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933baf588daa8dc9a92c20a0be32f56d43faf3d1a60ab11b3f08c356430f6e56"}, - {file = "scipy-1.11.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8fce70f39076a5aa62e92e69a7f62349f9574d8405c0a5de6ed3ef72de07f446"}, - {file = "scipy-1.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:6550466fbeec7453d7465e74d4f4b19f905642c89a7525571ee91dd7adabb5a3"}, - {file = "scipy-1.11.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f313b39a7e94f296025e3cffc2c567618174c0b1dde173960cf23808f9fae4be"}, - {file = "scipy-1.11.4-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1b7c3dca977f30a739e0409fb001056484661cb2541a01aba0bb0029f7b68db8"}, - {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00150c5eae7b610c32589dda259eacc7c4f1665aedf25d921907f4d08a951b1c"}, - {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:530f9ad26440e85766509dbf78edcfe13ffd0ab7fec2560ee5c36ff74d6269ff"}, - {file = "scipy-1.11.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e347b14fe01003d3b78e196e84bd3f48ffe4c8a7b8a1afbcb8f5505cb710993"}, - {file = "scipy-1.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:acf8ed278cc03f5aff035e69cb511741e0418681d25fbbb86ca65429c4f4d9cd"}, - {file = "scipy-1.11.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:028eccd22e654b3ea01ee63705681ee79933652b2d8f873e7949898dda6d11b6"}, - {file = "scipy-1.11.4-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c6ff6ef9cc27f9b3db93a6f8b38f97387e6e0591600369a297a50a8e96e835d"}, - {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b030c6674b9230d37c5c60ab456e2cf12f6784596d15ce8da9365e70896effc4"}, - {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad669df80528aeca5f557712102538f4f37e503f0c5b9541655016dd0932ca79"}, - {file = "scipy-1.11.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce7fff2e23ab2cc81ff452a9444c215c28e6305f396b2ba88343a567feec9660"}, - {file = "scipy-1.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:36750b7733d960d7994888f0d148d31ea3017ac15eef664194b4ef68d36a4a97"}, - {file = "scipy-1.11.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e619aba2df228a9b34718efb023966da781e89dd3d21637b27f2e54db0410d7"}, - {file = "scipy-1.11.4-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:f3cd9e7b3c2c1ec26364856f9fbe78695fe631150f94cd1c22228456404cf1ec"}, - {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d10e45a6c50211fe256da61a11c34927c68f277e03138777bdebedd933712fea"}, - {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91af76a68eeae0064887a48e25c4e616fa519fa0d38602eda7e0f97d65d57937"}, - {file = "scipy-1.11.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6df1468153a31cf55ed5ed39647279beb9cfb5d3f84369453b49e4b8502394fd"}, - {file = "scipy-1.11.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee410e6de8f88fd5cf6eadd73c135020bfbbbdfcd0f6162c36a7638a1ea8cc65"}, - {file = "scipy-1.11.4.tar.gz", hash = "sha256:90a2b78e7f5733b9de748f589f09225013685f9b218275257f8a8168ededaeaa"}, -] - -[package.dependencies] -numpy = ">=1.21.6,<1.28.0" - -[package.extras] -dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] -doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] -test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - -[[package]] -name = "setuptools" -version = "70.3.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc"}, - {file = "setuptools-70.3.0.tar.gz", hash = "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5"}, -] - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sniffio" -version = "1.3.0" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, -] - -[[package]] -name = "starlette" -version = "0.41.2" -description = "The little ASGI library that shines." -optional = false -python-versions = ">=3.8" -files = [ - {file = "starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d"}, - {file = "starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62"}, -] - -[package.dependencies] -anyio = ">=3.4.0,<5" - -[package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] - -[[package]] -name = "sympy" -version = "1.12" -description = "Computer algebra system (CAS) in Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5"}, - {file = "sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8"}, -] - -[package.dependencies] -mpmath = ">=0.19" - -[[package]] -name = "threadpoolctl" -version = "3.2.0" -description = "threadpoolctl" -optional = false -python-versions = ">=3.8" -files = [ - {file = "threadpoolctl-3.2.0-py3-none-any.whl", hash = "sha256:2b7818516e423bdaebb97c723f86a7c6b0a83d3f3b0970328d66f4d9104dc032"}, - {file = "threadpoolctl-3.2.0.tar.gz", hash = "sha256:c96a0ba3bdddeaca37dc4cc7344aafad41cdb8c313f74fdfe387a867bba93355"}, -] - -[[package]] -name = "tifffile" -version = "2023.12.9" -description = "Read and write TIFF files" -optional = false -python-versions = ">=3.9" -files = [ - {file = "tifffile-2023.12.9-py3-none-any.whl", hash = "sha256:9b066e4b1a900891ea42ffd33dab8ba34c537935618b9893ddef42d7d422692f"}, - {file = "tifffile-2023.12.9.tar.gz", hash = "sha256:9dd1da91180a6453018a241ff219e1905f169384355cd89c9ef4034c1b46cdb8"}, -] - -[package.dependencies] -numpy = "*" - -[package.extras] -all = ["defusedxml", "fsspec", "imagecodecs (>=2023.8.12)", "lxml", "matplotlib", "zarr"] - -[[package]] -name = "tokenizers" -version = "0.21.0" -description = "" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tokenizers-0.21.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3c4c93eae637e7d2aaae3d376f06085164e1660f89304c0ab2b1d08a406636b2"}, - {file = "tokenizers-0.21.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:f53ea537c925422a2e0e92a24cce96f6bc5046bbef24a1652a5edc8ba975f62e"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b177fb54c4702ef611de0c069d9169f0004233890e0c4c5bd5508ae05abf193"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b43779a269f4629bebb114e19c3fca0223296ae9fea8bb9a7a6c6fb0657ff8e"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aeb255802be90acfd363626753fda0064a8df06031012fe7d52fd9a905eb00e"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b09dbeb7a8d73ee204a70f94fc06ea0f17dcf0844f16102b9f414f0b7463ba"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:400832c0904f77ce87c40f1a8a27493071282f785724ae62144324f171377273"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84ca973b3a96894d1707e189c14a774b701596d579ffc7e69debfc036a61a04"}, - {file = "tokenizers-0.21.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eb7202d231b273c34ec67767378cd04c767e967fda12d4a9e36208a34e2f137e"}, - {file = "tokenizers-0.21.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:089d56db6782a73a27fd8abf3ba21779f5b85d4a9f35e3b493c7bbcbbf0d539b"}, - {file = "tokenizers-0.21.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:c87ca3dc48b9b1222d984b6b7490355a6fdb411a2d810f6f05977258400ddb74"}, - {file = "tokenizers-0.21.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4145505a973116f91bc3ac45988a92e618a6f83eb458f49ea0790df94ee243ff"}, - {file = "tokenizers-0.21.0-cp39-abi3-win32.whl", hash = "sha256:eb1702c2f27d25d9dd5b389cc1f2f51813e99f8ca30d9e25348db6585a97e24a"}, - {file = "tokenizers-0.21.0-cp39-abi3-win_amd64.whl", hash = "sha256:87841da5a25a3a5f70c102de371db120f41873b854ba65e52bccd57df5a3780c"}, - {file = "tokenizers-0.21.0.tar.gz", hash = "sha256:ee0894bf311b75b0c03079f33859ae4b2334d675d4e93f5a4132e1eae2834fe4"}, -] - -[package.dependencies] -huggingface-hub = ">=0.16.4,<1.0" - -[package.extras] -dev = ["tokenizers[testing]"] -docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"] -testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests", "ruff"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "tqdm" -version = "4.66.3" -description = "Fast, Extensible Progress Meter" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tqdm-4.66.3-py3-none-any.whl", hash = "sha256:4f41d54107ff9a223dca80b53efe4fb654c67efaba7f47bada3ee9d50e05bd53"}, - {file = "tqdm-4.66.3.tar.gz", hash = "sha256:23097a41eba115ba99ecae40d06444c15d1c0c698d527a01c6c8bd1c5d0647e5"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "urllib3" -version = "2.1.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "uvicorn" -version = "0.34.0" -description = "The lightning-fast ASGI server." -optional = false -python-versions = ">=3.9" -files = [ - {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"}, - {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"}, -] - -[package.dependencies] -click = ">=7.0" -colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} -h11 = ">=0.8" -httptools = {version = ">=0.6.3", optional = true, markers = "extra == \"standard\""} -python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} -typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} -uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} -watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} - -[package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] - -[[package]] -name = "uvloop" -version = "0.19.0" -description = "Fast implementation of asyncio event loop on top of libuv" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, - {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, -] - -[package.extras] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] - -[[package]] -name = "watchfiles" -version = "0.21.0" -description = "Simple, modern and high performance file watching and code reload in python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa"}, - {file = "watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d"}, - {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c"}, - {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9"}, - {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9"}, - {file = "watchfiles-0.21.0-cp310-none-win32.whl", hash = "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293"}, - {file = "watchfiles-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235"}, - {file = "watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7"}, - {file = "watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d"}, - {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7"}, - {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0"}, - {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365"}, - {file = "watchfiles-0.21.0-cp311-none-win32.whl", hash = "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400"}, - {file = "watchfiles-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe"}, - {file = "watchfiles-0.21.0-cp311-none-win_arm64.whl", hash = "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078"}, - {file = "watchfiles-0.21.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a"}, - {file = "watchfiles-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7"}, - {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c"}, - {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235"}, - {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7"}, - {file = "watchfiles-0.21.0-cp312-none-win32.whl", hash = "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3"}, - {file = "watchfiles-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094"}, - {file = "watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6"}, - {file = "watchfiles-0.21.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:4ea10a29aa5de67de02256a28d1bf53d21322295cb00bd2d57fcd19b850ebd99"}, - {file = "watchfiles-0.21.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:40bca549fdc929b470dd1dbfcb47b3295cb46a6d2c90e50588b0a1b3bd98f429"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9b37a7ba223b2f26122c148bb8d09a9ff312afca998c48c725ff5a0a632145f7"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ad3fe0a3567c2f0f629d800409cd528cb6251da12e81a1f765e5c5345fd0137"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d353c4cfda586db2a176ce42c88f2fc31ec25e50212650c89fdd0f560ee507b"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83a696da8922314ff2aec02987eefb03784f473281d740bf9170181829133765"}, - {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a03651352fc20975ee2a707cd2d74a386cd303cc688f407296064ad1e6d1562"}, - {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ad692bc7792be8c32918c699638b660c0de078a6cbe464c46e1340dadb94c19"}, - {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06247538e8253975bdb328e7683f8515ff5ff041f43be6c40bff62d989b7d0b0"}, - {file = "watchfiles-0.21.0-cp38-none-win32.whl", hash = "sha256:9a0aa47f94ea9a0b39dd30850b0adf2e1cd32a8b4f9c7aa443d852aacf9ca214"}, - {file = "watchfiles-0.21.0-cp38-none-win_amd64.whl", hash = "sha256:8d5f400326840934e3507701f9f7269247f7c026d1b6cfd49477d2be0933cfca"}, - {file = "watchfiles-0.21.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7f762a1a85a12cc3484f77eee7be87b10f8c50b0b787bb02f4e357403cad0c0e"}, - {file = "watchfiles-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6e9be3ef84e2bb9710f3f777accce25556f4a71e15d2b73223788d528fcc2052"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4c48a10d17571d1275701e14a601e36959ffada3add8cdbc9e5061a6e3579a5d"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c889025f59884423428c261f212e04d438de865beda0b1e1babab85ef4c0f01"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:66fac0c238ab9a2e72d026b5fb91cb902c146202bbd29a9a1a44e8db7b710b6f"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a21f71885aa2744719459951819e7bf5a906a6448a6b2bbce8e9cc9f2c8128"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c9198c989f47898b2c22201756f73249de3748e0fc9de44adaf54a8b259cc0c"}, - {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f57c4461cd24fda22493109c45b3980863c58a25b8bec885ca8bea6b8d4b28"}, - {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:853853cbf7bf9408b404754b92512ebe3e3a83587503d766d23e6bf83d092ee6"}, - {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d5b1dc0e708fad9f92c296ab2f948af403bf201db8fb2eb4c8179db143732e49"}, - {file = "watchfiles-0.21.0-cp39-none-win32.whl", hash = "sha256:59137c0c6826bd56c710d1d2bda81553b5e6b7c84d5a676747d80caf0409ad94"}, - {file = "watchfiles-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:6cb8fdc044909e2078c248986f2fc76f911f72b51ea4a4fbbf472e01d14faa58"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c"}, - {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:08dca260e85ffae975448e344834d765983237ad6dc308231aa16e7933db763e"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ccceb50c611c433145502735e0370877cced72a6c70fd2410238bcbc7fe51d8"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57d430f5fb63fea141ab71ca9c064e80de3a20b427ca2febcbfcef70ff0ce895"}, - {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd5fad9b9c0dd89904bbdea978ce89a2b692a7ee8a0ce19b940e538c88a809c"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:be6dd5d52b73018b21adc1c5d28ac0c68184a64769052dfeb0c5d9998e7f56a2"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b3cab0e06143768499384a8a5efb9c4dc53e19382952859e4802f294214f36ec"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6ed10c2497e5fedadf61e465b3ca12a19f96004c15dcffe4bd442ebadc2d85"}, - {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43babacef21c519bc6631c5fce2a61eccdfc011b4bcb9047255e9620732c8097"}, - {file = "watchfiles-0.21.0.tar.gz", hash = "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3"}, -] - -[package.dependencies] -anyio = ">=3.0.0" - -[[package]] -name = "wcwidth" -version = "0.2.13" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = "*" -files = [ - {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, - {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, -] - -[[package]] -name = "websockets" -version = "12.0" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, - {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, - {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, - {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, - {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, - {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, - {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, - {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, - {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, - {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, - {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, - {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, - {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, - {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, - {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, - {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, - {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, - {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, - {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, - {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, - {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, - {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, - {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, - {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, - {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, -] - -[[package]] -name = "werkzeug" -version = "3.0.3" -description = "The comprehensive WSGI web application library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, - {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, -] - -[package.dependencies] -MarkupSafe = ">=2.1.1" - -[package.extras] -watchdog = ["watchdog (>=2.3)"] - -[[package]] -name = "zope-event" -version = "5.0" -description = "Very basic event publishing system" -optional = false -python-versions = ">=3.7" -files = [ - {file = "zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26"}, - {file = "zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"}, -] - -[package.dependencies] -setuptools = "*" - -[package.extras] -docs = ["Sphinx"] -test = ["zope.testrunner"] - -[[package]] -name = "zope-interface" -version = "6.1" -description = "Interfaces for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "zope.interface-6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb"}, - {file = "zope.interface-6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92"}, - {file = "zope.interface-6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3"}, - {file = "zope.interface-6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd"}, - {file = "zope.interface-6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41"}, - {file = "zope.interface-6.1-cp310-cp310-win_amd64.whl", hash = "sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f"}, - {file = "zope.interface-6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1"}, - {file = "zope.interface-6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736"}, - {file = "zope.interface-6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605"}, - {file = "zope.interface-6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8"}, - {file = "zope.interface-6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de"}, - {file = "zope.interface-6.1-cp311-cp311-win_amd64.whl", hash = "sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1"}, - {file = "zope.interface-6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a"}, - {file = "zope.interface-6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7"}, - {file = "zope.interface-6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d"}, - {file = "zope.interface-6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff"}, - {file = "zope.interface-6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0"}, - {file = "zope.interface-6.1-cp312-cp312-win_amd64.whl", hash = "sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b"}, - {file = "zope.interface-6.1-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:2f8d89721834524a813f37fa174bac074ec3d179858e4ad1b7efd4401f8ac45d"}, - {file = "zope.interface-6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13b7d0f2a67eb83c385880489dbb80145e9d344427b4262c49fbf2581677c11c"}, - {file = "zope.interface-6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef43ee91c193f827e49599e824385ec7c7f3cd152d74cb1dfe02cb135f264d83"}, - {file = "zope.interface-6.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e441e8b7d587af0414d25e8d05e27040d78581388eed4c54c30c0c91aad3a379"}, - {file = "zope.interface-6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89b28772fc2562ed9ad871c865f5320ef761a7fcc188a935e21fe8b31a38ca9"}, - {file = "zope.interface-6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70d2cef1bf529bff41559be2de9d44d47b002f65e17f43c73ddefc92f32bf00f"}, - {file = "zope.interface-6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ad54ed57bdfa3254d23ae04a4b1ce405954969c1b0550cc2d1d2990e8b439de1"}, - {file = "zope.interface-6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef467d86d3cfde8b39ea1b35090208b0447caaabd38405420830f7fd85fbdd56"}, - {file = "zope.interface-6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6af47f10cfc54c2ba2d825220f180cc1e2d4914d783d6fc0cd93d43d7bc1c78b"}, - {file = "zope.interface-6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9559138690e1bd4ea6cd0954d22d1e9251e8025ce9ede5d0af0ceae4a401e43"}, - {file = "zope.interface-6.1-cp38-cp38-win_amd64.whl", hash = "sha256:964a7af27379ff4357dad1256d9f215047e70e93009e532d36dcb8909036033d"}, - {file = "zope.interface-6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:387545206c56b0315fbadb0431d5129c797f92dc59e276b3ce82db07ac1c6179"}, - {file = "zope.interface-6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:57d0a8ce40ce440f96a2c77824ee94bf0d0925e6089df7366c2272ccefcb7941"}, - {file = "zope.interface-6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ebc4d34e7620c4f0da7bf162c81978fce0ea820e4fa1e8fc40ee763839805f3"}, - {file = "zope.interface-6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a804abc126b33824a44a7aa94f06cd211a18bbf31898ba04bd0924fbe9d282d"}, - {file = "zope.interface-6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f294a15f7723fc0d3b40701ca9b446133ec713eafc1cc6afa7b3d98666ee1ac"}, - {file = "zope.interface-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a41f87bb93b8048fe866fa9e3d0c51e27fe55149035dcf5f43da4b56732c0a40"}, - {file = "zope.interface-6.1.tar.gz", hash = "sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309"}, -] - -[package.dependencies] -setuptools = "*" - -[package.extras] -docs = ["Sphinx", "repoze.sphinx.autointerface", "sphinx-rtd-theme"] -test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] -testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] - -[metadata] -lock-version = "2.0" -python-versions = ">=3.10,<4.0" -content-hash = "b690d5fbd141da3947f4f1dc029aba1b95e7faafd723166f2c4bdc47a66c095e" diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index 71174e2158..b962b76dc8 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -1,72 +1,77 @@ -[tool.poetry] +[project] name = "machine-learning" -version = "1.128.0" +version = "1.129.0" description = "" -authors = ["Hau Tran "] +authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }] +requires-python = ">=3.10,<4.0" readme = "README.md" -packages = [{include = "app"}] +dependencies = [ + "aiocache>=0.12.1,<1.0", + "fastapi>=0.95.2,<1.0", + "ftfy>=6.1.1", + "gunicorn>=21.1.0", + "huggingface-hub>=0.20.1,<1.0", + "insightface>=0.7.3,<1.0", + "opencv-python-headless>=4.7.0.72,<5.0", + "orjson>=3.9.5", + "pillow>=9.5.0,<11.0", + "pydantic>=2.0.0,<3", + "pydantic-settings>=2.5.2,<3", + "python-multipart>=0.0.6,<1.0", + "rich>=13.4.2", + "tokenizers>=0.15.0,<1.0", + "uvicorn[standard]>=0.22.0,<1.0", +] -[tool.poetry.dependencies] -python = ">=3.10,<4.0" -insightface = ">=0.7.3,<1.0" -opencv-python-headless = ">=4.7.0.72,<5.0" -pillow = ">=9.5.0,<11.0" -fastapi = ">=0.95.2,<1.0" -uvicorn = {extras = ["standard"], version = ">=0.22.0,<1.0"} -pydantic = "^2.0.0" -pydantic-settings = "^2.5.2" -aiocache = ">=0.12.1,<1.0" -rich = ">=13.4.2" -ftfy = ">=6.1.1" -python-multipart = ">=0.0.6,<1.0" -orjson = ">=3.9.5" -gunicorn = ">=21.1.0" -huggingface-hub = ">=0.20.1,<1.0" -tokenizers = ">=0.15.0,<1.0" +[dependency-groups] +test = [ + "httpx>=0.24.1", + "pytest>=7.3.1", + "pytest-asyncio>=0.21.0", + "pytest-cov>=4.1.0", + "pytest-mock>=3.11.1", +] +types = [ + "types-pyyaml>=6.0.12.20241230", + "types-requests>=2.32.0.20250306", + "types-setuptools>=75.8.2.20250305", + "types-simplejson>=3.20.0.20250218", + "types-ujson>=5.10.0.20240515", +] +lint = [ + "black>=23.3.0", + "mypy>=1.3.0", + "ruff>=0.0.272", + { include-group = "types" }, +] +dev = ["locust>=2.15.1", { include-group = "test" }, { include-group = "lint" }] -[tool.poetry.group.dev.dependencies] -mypy = ">=1.3.0" -black = ">=23.3.0" -pytest = ">=7.3.1" -locust = ">=2.15.1" -httpx = ">=0.24.1" -pytest-asyncio = ">=0.21.0" -pytest-cov = ">=4.1.0" -ruff = ">=0.0.272" -pytest-mock = ">=3.11.1" +[project.optional-dependencies] +cpu = ["onnxruntime>=1.15.0,<2"] +cuda = ["onnxruntime-gpu>=1.17.0,<2"] +openvino = ["onnxruntime-openvino>=1.17.1,<1.19.0"] +armnn = ["onnxruntime>=1.15.0,<2"] -[tool.poetry.group.cpu] -optional = true +[tool.uv] +compile-bytecode = true -[tool.poetry.group.cpu.dependencies] -onnxruntime = "^1.15.0" - -[tool.poetry.group.cuda] -optional = true - -[tool.poetry.group.cuda.dependencies] -onnxruntime-gpu = {version = "^1.17.0", source = "cuda12"} - -[tool.poetry.group.openvino] -optional = true - -[tool.poetry.group.openvino.dependencies] -onnxruntime-openvino = ">=1.17.1,<1.19.0" - -[tool.poetry.group.armnn] -optional = true - -[tool.poetry.group.armnn.dependencies] -onnxruntime = "^1.15.0" - -[[tool.poetry.source]] +[[tool.uv.index]] name = "cuda12" url = "https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/" -priority = "explicit" +explicit = true + +[tool.uv.sources] +onnxruntime-gpu = { index = "cuda12" } + +[tool.hatch.build.targets.sdist] +include = ["app"] + +[tool.hatch.build.targets.wheel] +include = ["app"] [build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" [tool.mypy] python_version = "3.11" diff --git a/machine-learning/uv.lock b/machine-learning/uv.lock new file mode 100644 index 0000000000..55cf8ed555 --- /dev/null +++ b/machine-learning/uv.lock @@ -0,0 +1,2648 @@ +version = 1 +revision = 1 +requires-python = ">=3.10, <4.0" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.13.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'linux')", + "(python_full_version == '3.13.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'linux')", + "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", +] + +[[package]] +name = "aiocache" +version = "0.12.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/64/b945b8025a9d1e6e2138845f4022165d3b337f55f50984fbc6a4c0a1e355/aiocache-0.12.3.tar.gz", hash = "sha256:f528b27bf4d436b497a1d0d1a8f59a542c153ab1e37c3621713cb376d44c4713", size = 132196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/d7/15d67e05b235d1ed8c3ce61688fe4d84130e72af1657acadfaac3479f4cf/aiocache-0.12.3-py2.py3-none-any.whl", hash = "sha256:889086fc24710f431937b87ad3720a289f7fc31c4fd8b68e9f918b9bacd8270d", size = 28199 }, +] + +[[package]] +name = "albumentations" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "opencv-python-headless" }, + { name = "pyyaml" }, + { name = "qudida" }, + { name = "scikit-image" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/d6/8dd5b690d28a332a0b2c3179a345808b5d4c7ad5ddc079b7e116098dff35/albumentations-1.3.1.tar.gz", hash = "sha256:a6a38388fe546c568071e8c82f414498e86c9ed03c08b58e7a88b31cf7a244c6", size = 176371 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/f6/c486cedb4f75147232f32ec4c97026714cfef7c7e247a1f0427bc5489f66/albumentations-1.3.1-py3-none-any.whl", hash = "sha256:6b641d13733181d9ecdc29550e6ad580d1bfa9d25e2213a66940062f25e291bd", size = 125706 }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anyio" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f", size = 158770 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/cd/d6d9bb1dadf73e7af02d18225cbd2c93f8552e13130484f1c8dcfece292b/anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee", size = 85481 }, +] + +[[package]] +name = "black" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419 }, + { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080 }, + { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886 }, + { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404 }, + { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372 }, + { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865 }, + { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699 }, + { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028 }, + { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988 }, + { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985 }, + { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816 }, + { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860 }, + { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 }, + { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 }, + { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 }, + { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 }, + { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 }, +] + +[[package]] +name = "blinker" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/13/6df5fc090ff4e5d246baf1f45fe9e5623aa8565757dfa5bd243f6a545f9e/blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182", size = 28134 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9", size = 13068 }, +] + +[[package]] +name = "brotli" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/3a/dbf4fb970c1019a57b5e492e1e0eae745d32e59ba4d6161ab5422b08eefe/Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752", size = 873045 }, + { url = "https://files.pythonhosted.org/packages/dd/11/afc14026ea7f44bd6eb9316d800d439d092c8d508752055ce8d03086079a/Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9", size = 446218 }, + { url = "https://files.pythonhosted.org/packages/36/83/7545a6e7729db43cb36c4287ae388d6885c85a86dd251768a47015dfde32/Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3", size = 2903872 }, + { url = "https://files.pythonhosted.org/packages/32/23/35331c4d9391fcc0f29fd9bec2c76e4b4eeab769afbc4b11dd2e1098fb13/Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d", size = 2941254 }, + { url = "https://files.pythonhosted.org/packages/3b/24/1671acb450c902edb64bd765d73603797c6c7280a9ada85a195f6b78c6e5/Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e", size = 2857293 }, + { url = "https://files.pythonhosted.org/packages/d5/00/40f760cc27007912b327fe15bf6bfd8eaecbe451687f72a8abc587d503b3/Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da", size = 3002385 }, + { url = "https://files.pythonhosted.org/packages/b8/cb/8aaa83f7a4caa131757668c0fb0c4b6384b09ffa77f2fba9570d87ab587d/Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80", size = 2911104 }, + { url = "https://files.pythonhosted.org/packages/bc/c4/65456561d89d3c49f46b7fbeb8fe6e449f13bdc8ea7791832c5d476b2faf/Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d", size = 2809981 }, + { url = "https://files.pythonhosted.org/packages/05/1b/cf49528437bae28abce5f6e059f0d0be6fecdcc1d3e33e7c54b3ca498425/Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0", size = 2935297 }, + { url = "https://files.pythonhosted.org/packages/81/ff/190d4af610680bf0c5a09eb5d1eac6e99c7c8e216440f9c7cfd42b7adab5/Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e", size = 2930735 }, + { url = "https://files.pythonhosted.org/packages/80/7d/f1abbc0c98f6e09abd3cad63ec34af17abc4c44f308a7a539010f79aae7a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c", size = 2933107 }, + { url = "https://files.pythonhosted.org/packages/34/ce/5a5020ba48f2b5a4ad1c0522d095ad5847a0be508e7d7569c8630ce25062/Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1", size = 2845400 }, + { url = "https://files.pythonhosted.org/packages/44/89/fa2c4355ab1eecf3994e5a0a7f5492c6ff81dfcb5f9ba7859bd534bb5c1a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2", size = 3031985 }, + { url = "https://files.pythonhosted.org/packages/af/a4/79196b4a1674143d19dca400866b1a4d1a089040df7b93b88ebae81f3447/Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec", size = 2927099 }, + { url = "https://files.pythonhosted.org/packages/e9/54/1c0278556a097f9651e657b873ab08f01b9a9ae4cac128ceb66427d7cd20/Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2", size = 333172 }, + { url = "https://files.pythonhosted.org/packages/f7/65/b785722e941193fd8b571afd9edbec2a9b838ddec4375d8af33a50b8dab9/Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128", size = 357255 }, + { url = "https://files.pythonhosted.org/packages/96/12/ad41e7fadd5db55459c4c401842b47f7fee51068f86dd2894dd0dcfc2d2a/Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc", size = 873068 }, + { url = "https://files.pythonhosted.org/packages/95/4e/5afab7b2b4b61a84e9c75b17814198ce515343a44e2ed4488fac314cd0a9/Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6", size = 446244 }, + { url = "https://files.pythonhosted.org/packages/9d/e6/f305eb61fb9a8580c525478a4a34c5ae1a9bcb12c3aee619114940bc513d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd", size = 2906500 }, + { url = "https://files.pythonhosted.org/packages/3e/4f/af6846cfbc1550a3024e5d3775ede1e00474c40882c7bf5b37a43ca35e91/Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf", size = 2943950 }, + { url = "https://files.pythonhosted.org/packages/b3/e7/ca2993c7682d8629b62630ebf0d1f3bb3d579e667ce8e7ca03a0a0576a2d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61", size = 2918527 }, + { url = "https://files.pythonhosted.org/packages/b3/96/da98e7bedc4c51104d29cc61e5f449a502dd3dbc211944546a4cc65500d3/Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327", size = 2845489 }, + { url = "https://files.pythonhosted.org/packages/e8/ef/ccbc16947d6ce943a7f57e1a40596c75859eeb6d279c6994eddd69615265/Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd", size = 2914080 }, + { url = "https://files.pythonhosted.org/packages/80/d6/0bd38d758d1afa62a5524172f0b18626bb2392d717ff94806f741fcd5ee9/Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9", size = 2813051 }, + { url = "https://files.pythonhosted.org/packages/14/56/48859dd5d129d7519e001f06dcfbb6e2cf6db92b2702c0c2ce7d97e086c1/Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265", size = 2938172 }, + { url = "https://files.pythonhosted.org/packages/3d/77/a236d5f8cd9e9f4348da5acc75ab032ab1ab2c03cc8f430d24eea2672888/Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8", size = 2933023 }, + { url = "https://files.pythonhosted.org/packages/f1/87/3b283efc0f5cb35f7f84c0c240b1e1a1003a5e47141a4881bf87c86d0ce2/Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f", size = 2935871 }, + { url = "https://files.pythonhosted.org/packages/f3/eb/2be4cc3e2141dc1a43ad4ca1875a72088229de38c68e842746b342667b2a/Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757", size = 2847784 }, + { url = "https://files.pythonhosted.org/packages/66/13/b58ddebfd35edde572ccefe6890cf7c493f0c319aad2a5badee134b4d8ec/Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0", size = 3034905 }, + { url = "https://files.pythonhosted.org/packages/84/9c/bc96b6c7db824998a49ed3b38e441a2cae9234da6fa11f6ed17e8cf4f147/Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b", size = 2929467 }, + { url = "https://files.pythonhosted.org/packages/e7/71/8f161dee223c7ff7fea9d44893fba953ce97cf2c3c33f78ba260a91bcff5/Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50", size = 333169 }, + { url = "https://files.pythonhosted.org/packages/02/8a/fece0ee1057643cb2a5bbf59682de13f1725f8482b2c057d4e799d7ade75/Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1", size = 357253 }, + { url = "https://files.pythonhosted.org/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693 }, + { url = "https://files.pythonhosted.org/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489 }, + { url = "https://files.pythonhosted.org/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081 }, + { url = "https://files.pythonhosted.org/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244 }, + { url = "https://files.pythonhosted.org/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505 }, + { url = "https://files.pythonhosted.org/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152 }, + { url = "https://files.pythonhosted.org/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252 }, + { url = "https://files.pythonhosted.org/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955 }, + { url = "https://files.pythonhosted.org/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304 }, + { url = "https://files.pythonhosted.org/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452 }, + { url = "https://files.pythonhosted.org/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751 }, + { url = "https://files.pythonhosted.org/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757 }, + { url = "https://files.pythonhosted.org/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146 }, + { url = "https://files.pythonhosted.org/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055 }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102 }, + { url = "https://files.pythonhosted.org/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029 }, + { url = "https://files.pythonhosted.org/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276 }, + { url = "https://files.pythonhosted.org/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255 }, + { url = "https://files.pythonhosted.org/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681 }, + { url = "https://files.pythonhosted.org/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475 }, + { url = "https://files.pythonhosted.org/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173 }, + { url = "https://files.pythonhosted.org/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803 }, + { url = "https://files.pythonhosted.org/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946 }, + { url = "https://files.pythonhosted.org/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707 }, + { url = "https://files.pythonhosted.org/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231 }, + { url = "https://files.pythonhosted.org/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157 }, + { url = "https://files.pythonhosted.org/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122 }, + { url = "https://files.pythonhosted.org/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206 }, + { url = "https://files.pythonhosted.org/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804 }, + { url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517 }, +] + +[[package]] +name = "certifi" +version = "2023.11.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/91/c89518dd4fe1f3a4e3f6ab7ff23cb00ef2e8c9adf99dacc618ad5e068e28/certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", size = 163637 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/62/428ef076be88fa93716b576e4a01f919d25968913e817077a386fcbe4f42/certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474", size = 162530 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/61/095a0aa1a84d1481998b534177c8566fdc50bb1233ea9a0478cd3cc075bd/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", size = 194219 }, + { url = "https://files.pythonhosted.org/packages/cc/94/f7cf5e5134175de79ad2059edf2adce18e0685ebdb9227ff0139975d0e93/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", size = 122521 }, + { url = "https://files.pythonhosted.org/packages/46/6a/d5c26c41c49b546860cc1acabdddf48b0b3fb2685f4f5617ac59261b44ae/charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", size = 120383 }, + { url = "https://files.pythonhosted.org/packages/b8/60/e2f67915a51be59d4539ed189eb0a2b0d292bf79270410746becb32bc2c3/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", size = 138223 }, + { url = "https://files.pythonhosted.org/packages/05/8c/eb854996d5fef5e4f33ad56927ad053d04dc820e4a3d39023f35cad72617/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", size = 148101 }, + { url = "https://files.pythonhosted.org/packages/f6/93/bb6cbeec3bf9da9b2eba458c15966658d1daa8b982c642f81c93ad9b40e1/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", size = 140699 }, + { url = "https://files.pythonhosted.org/packages/da/f1/3702ba2a7470666a62fd81c58a4c40be00670e5006a67f4d626e57f013ae/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", size = 142065 }, + { url = "https://files.pythonhosted.org/packages/3f/ba/3f5e7be00b215fa10e13d64b1f6237eb6ebea66676a41b2bcdd09fe74323/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", size = 144505 }, + { url = "https://files.pythonhosted.org/packages/33/c3/3b96a435c5109dd5b6adc8a59ba1d678b302a97938f032e3770cc84cd354/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", size = 139425 }, + { url = "https://files.pythonhosted.org/packages/43/05/3bf613e719efe68fb3a77f9c536a389f35b95d75424b96b426a47a45ef1d/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", size = 145287 }, + { url = "https://files.pythonhosted.org/packages/58/78/a0bc646900994df12e07b4ae5c713f2b3e5998f58b9d3720cce2aa45652f/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", size = 149929 }, + { url = "https://files.pythonhosted.org/packages/eb/5c/97d97248af4920bc68687d9c3b3c0f47c910e21a8ff80af4565a576bd2f0/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", size = 141605 }, + { url = "https://files.pythonhosted.org/packages/a8/31/47d018ef89f95b8aded95c589a77c072c55e94b50a41aa99c0a2008a45a4/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", size = 142646 }, + { url = "https://files.pythonhosted.org/packages/ae/d5/4fecf1d58bedb1340a50f165ba1c7ddc0400252d6832ff619c4568b36cc0/charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", size = 92846 }, + { url = "https://files.pythonhosted.org/packages/a2/a0/4af29e22cb5942488cf45630cbdd7cefd908768e69bdd90280842e4e8529/charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", size = 100343 }, + { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, + { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, + { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, + { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, + { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, + { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, + { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, + { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, + { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, + { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, + { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, + { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, + { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, + { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, + { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, + { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, + { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, + { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, + { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, + { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, + { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, + { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, + { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, + { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, + { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, + { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, + { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, + { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, + { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, + { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coloredlogs" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "humanfriendly" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018 }, +] + +[[package]] +name = "configargparse" +version = "1.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/8a/73f1008adfad01cb923255b924b1528727b8270e67cb4ef41eabdc7d783e/ConfigArgParse-1.7.tar.gz", hash = "sha256:e7067471884de5478c58a511e529f0f9bd1c66bfef1dea90935438d6c23306d1", size = 43817 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/b3/b4ac838711fd74a2b4e6f746703cf9dd2cf5462d17dac07e349234e21b97/ConfigArgParse-1.7-py3-none-any.whl", hash = "sha256:d249da6591465c6c26df64a9f73d2536e743be2f244eb3ebe61114af2f94f86b", size = 25489 }, +] + +[[package]] +name = "contourpy" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/a3/48ddc7ae832b000952cf4be64452381d150a41a2299c2eb19237168528d1/contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a", size = 13455881 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/ea/f6e90933d82cc5aacf52f886a1c01f47f96eba99108ca2929c7b3ef45f82/contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8", size = 256873 }, + { url = "https://files.pythonhosted.org/packages/fe/26/43821d61b7ee62c1809ec852bc572aaf4c27f101ebcebbbcce29a5ee0445/contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4", size = 242211 }, + { url = "https://files.pythonhosted.org/packages/9b/99/c8fb63072a7573fe7682e1786a021f29f9c5f660a3aafcdce80b9ee8348d/contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f", size = 293195 }, + { url = "https://files.pythonhosted.org/packages/c7/a7/ae0b4bb8e0c865270d02ee619981413996dc10ddf1fd2689c938173ff62f/contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e", size = 332279 }, + { url = "https://files.pythonhosted.org/packages/94/7c/682228b9085ff323fb7e946fe139072e5f21b71360cf91f36ea079d4ea95/contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9", size = 305326 }, + { url = "https://files.pythonhosted.org/packages/58/56/e2c43dcfa1f9c7db4d5e3d6f5134b24ed953f4e2133a4b12f0062148db58/contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa", size = 310732 }, + { url = "https://files.pythonhosted.org/packages/94/0b/8495c4582057abc8377f945f6e11a86f1c07ad7b32fd4fdc968478cd0324/contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9", size = 803420 }, + { url = "https://files.pythonhosted.org/packages/d5/1f/40399c7da649297147d404aedaa675cc60018f48ad284630c0d1406133e3/contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab", size = 829204 }, + { url = "https://files.pythonhosted.org/packages/8b/01/4be433b60dce7cbce8315cbcdfc016e7d25430a8b94e272355dff79cc3a8/contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488", size = 165434 }, + { url = "https://files.pythonhosted.org/packages/fd/7c/168f8343f33d861305e18c56901ef1bb675d3c7f977f435ec72751a71a54/contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41", size = 186652 }, + { url = "https://files.pythonhosted.org/packages/9b/54/1dafec3c84df1d29119037330f7289db84a679cb2d5283af4ef24d89f532/contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727", size = 258243 }, + { url = "https://files.pythonhosted.org/packages/5b/ac/26fa1057f62beaa2af4c55c6ac733b114a403b746cfe0ce3dc6e4aec921a/contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd", size = 243408 }, + { url = "https://files.pythonhosted.org/packages/b7/33/cd0ecc80123f499d76d2fe2807cb4d5638ef8730735c580c8a8a03e1928e/contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a", size = 294142 }, + { url = "https://files.pythonhosted.org/packages/6d/75/1b7bf20bf6394e01df2c4b4b3d44d3dc280c16ddaff72724639100bd4314/contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063", size = 333129 }, + { url = "https://files.pythonhosted.org/packages/22/5b/fedd961dff1877e5d3b83c5201295cfdcdc2438884c2851aa7ecf6cec045/contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e", size = 307461 }, + { url = "https://files.pythonhosted.org/packages/e2/83/29a63bbc72839cc6b24b5a0e3d004d4ed4e8439f26460ad9a34e39251904/contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686", size = 313352 }, + { url = "https://files.pythonhosted.org/packages/4b/c7/4bac0fc4f1e802ab47e75076d83d2e1448e0668ba6cc9000cf4e9d5bd94a/contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286", size = 804127 }, + { url = "https://files.pythonhosted.org/packages/e3/47/b3fd5bdc2f6ec13502d57a5bc390ffe62648605ed1689c93b0015150a784/contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95", size = 829561 }, + { url = "https://files.pythonhosted.org/packages/5c/04/be16038e754169caea4d02d82f8e5cd97dece593e5ac9e05735da0afd0c5/contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6", size = 166197 }, + { url = "https://files.pythonhosted.org/packages/ca/2a/d197a412ec474391ee878b1218cf2fe9c6e963903755887fc5654c06636a/contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de", size = 187556 }, + { url = "https://files.pythonhosted.org/packages/4f/03/839da46999173226bead08794cbd7b4d37c9e6b02686ca74c93556b43258/contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0", size = 259253 }, + { url = "https://files.pythonhosted.org/packages/f3/9e/8fb3f53144269d3fecdd8786d3a4686eeff55b9b35a3c0772a3f62f71e36/contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4", size = 242555 }, + { url = "https://files.pythonhosted.org/packages/a6/85/9815ccb5a18ee8c9a46bd5ef20d02b292cd4a99c62553f38c87015f16d59/contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779", size = 288108 }, + { url = "https://files.pythonhosted.org/packages/5a/d9/4df5c26bd0f496c8cd7940fd53db95d07deeb98518f02f805ce570590da8/contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316", size = 330810 }, + { url = "https://files.pythonhosted.org/packages/67/d4/8aae9793a0cfde72959312521ebd3aa635c260c3d580448e8db6bdcdd1aa/contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399", size = 305290 }, + { url = "https://files.pythonhosted.org/packages/20/84/ffddcdcc579cbf7213fd92a3578ca08a931a3bf879a22deb5a83ffc5002c/contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0", size = 303937 }, + { url = "https://files.pythonhosted.org/packages/d8/ad/6e570cf525f909da94559ed716189f92f529bc7b5f78645733c44619a0e2/contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0", size = 801977 }, + { url = "https://files.pythonhosted.org/packages/36/b4/55f23482c596eca36d16fc668b147865c56fcf90353f4c57f073d8d5e532/contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431", size = 827442 }, + { url = "https://files.pythonhosted.org/packages/e9/47/9c081b1f11d6053cb0aa4c46b7de2ea2849a4a8d40de81c7bc3f99773b02/contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f", size = 165363 }, + { url = "https://files.pythonhosted.org/packages/8e/ae/a6353db548bff1a592b85ae6bb80275f0a51dc25a0410d059e5b33183e36/contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9", size = 187731 }, +] + +[[package]] +name = "coverage" +version = "7.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/12/3669b6382792783e92046730ad3327f53b2726f0603f4c311c4da4824222/coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", size = 798716 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/93/4ad92f71e28ece5c0326e5f4a6630aa4928a8846654a65cfff69b49b95b9/coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07", size = 206713 }, + { url = "https://files.pythonhosted.org/packages/01/ae/747a580b1eda3f2e431d87de48f0604bd7bc92e52a1a95185a4aa585bc47/coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0", size = 207149 }, + { url = "https://files.pythonhosted.org/packages/07/1a/1f573f8a6145f6d4c9130bbc120e0024daf1b24cf2a78d7393fa6eb6aba7/coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72", size = 235584 }, + { url = "https://files.pythonhosted.org/packages/40/42/c8523f2e4db34aa9389caee0d3688b6ada7a84fcc782e943a868a7f302bd/coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/8d/95/565c310fffa16ede1a042e9ea1ca3962af0d8eb5543bc72df6b91dc0c3d5/coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491", size = 234649 }, + { url = "https://files.pythonhosted.org/packages/d5/81/3b550674d98968ec29c92e3e8650682be6c8b1fa7581a059e7e12e74c431/coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b", size = 233744 }, + { url = "https://files.pythonhosted.org/packages/0d/70/d66c7f51b3e33aabc5ea9f9624c1c9d9655472962270eb5e7b0d32707224/coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea", size = 232204 }, + { url = "https://files.pythonhosted.org/packages/23/2d/2b3a2dbed7a5f40693404c8a09e779d7c1a5fbed089d3e7224c002129ec8/coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a", size = 233335 }, + { url = "https://files.pythonhosted.org/packages/5a/4f/92d1d2ad720d698a4e71c176eacf531bfb8e0721d5ad560556f2c484a513/coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa", size = 209435 }, + { url = "https://files.pythonhosted.org/packages/c7/b9/cdf158e7991e2287bcf9082670928badb73d310047facac203ff8dcd5ff3/coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172", size = 210243 }, + { url = "https://files.pythonhosted.org/packages/87/31/9c0cf84f0dfcbe4215b7eb95c31777cdc0483c13390e69584c8150c85175/coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b", size = 206819 }, + { url = "https://files.pythonhosted.org/packages/53/ed/a38401079ad320ad6e054a01ec2b61d270511aeb3c201c80e99c841229d5/coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25", size = 207263 }, + { url = "https://files.pythonhosted.org/packages/20/e7/c3ad33b179ab4213f0d70da25a9c214d52464efa11caeab438592eb1d837/coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546", size = 239205 }, + { url = "https://files.pythonhosted.org/packages/36/91/fc02e8d8e694f557752120487fd982f654ba1421bbaa5560debf96ddceda/coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b", size = 236612 }, + { url = "https://files.pythonhosted.org/packages/cc/57/cb08f0eda0389a9a8aaa4fc1f9fec7ac361c3e2d68efd5890d7042c18aa3/coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e", size = 238479 }, + { url = "https://files.pythonhosted.org/packages/d5/c9/2c7681a9b3ca6e6f43d489c2e6653a53278ed857fd6e7010490c307b0a47/coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718", size = 237405 }, + { url = "https://files.pythonhosted.org/packages/b5/4e/ebfc6944b96317df8b537ae875d2e57c27b84eb98820bc0a1055f358f056/coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db", size = 236038 }, + { url = "https://files.pythonhosted.org/packages/13/f2/3a0bf1841a97c0654905e2ef531170f02c89fad2555879db8fe41a097871/coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522", size = 236812 }, + { url = "https://files.pythonhosted.org/packages/b9/9c/66bf59226b52ce6ed9541b02d33e80a6e816a832558fbdc1111a7bd3abd4/coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf", size = 209400 }, + { url = "https://files.pythonhosted.org/packages/2a/a0/b0790934c04dfc8d658d4a62acb8f7ca0efdf3818456fcad757b11c6479d/coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19", size = 210243 }, + { url = "https://files.pythonhosted.org/packages/7d/e7/9291de916d084f41adddfd4b82246e68d61d6a75747f075f7e64628998d2/coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", size = 207013 }, + { url = "https://files.pythonhosted.org/packages/27/03/932c2c5717a7fa80cd43c6a07d3177076d97b79f12f40f882f9916db0063/coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", size = 207251 }, + { url = "https://files.pythonhosted.org/packages/d5/3f/0af47dcb9327f65a45455fbca846fe96eb57c153af46c4754a3ba678938a/coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", size = 240268 }, + { url = "https://files.pythonhosted.org/packages/8a/3c/37a9d81bbd4b23bc7d46ca820e16174c613579c66342faa390a271d2e18b/coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", size = 237298 }, + { url = "https://files.pythonhosted.org/packages/c0/70/6b0627e5bd68204ee580126ed3513140b2298995c1233bd67404b4e44d0e/coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", size = 239367 }, + { url = "https://files.pythonhosted.org/packages/3c/eb/634d7dfab24ac3b790bebaf9da0f4a5352cbc125ce6a9d5c6cf4c6cae3c7/coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", size = 238853 }, + { url = "https://files.pythonhosted.org/packages/d9/0d/8e3ed00f1266ef7472a4e33458f42e39492e01a64281084fb3043553d3f1/coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", size = 237160 }, + { url = "https://files.pythonhosted.org/packages/ce/9c/4337f468ef0ab7a2e0887a9c9da0e58e2eada6fc6cbee637a4acd5dfd8a9/coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", size = 238824 }, + { url = "https://files.pythonhosted.org/packages/5e/09/3e94912b8dd37251377bb02727a33a67ee96b84bbbe092f132b401ca5dd9/coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", size = 209639 }, + { url = "https://files.pythonhosted.org/packages/01/69/d4f3a4101171f32bc5b3caec8ff94c2c60f700107a6aaef7244b2c166793/coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", size = 210428 }, + { url = "https://files.pythonhosted.org/packages/c2/4d/2dede4f7cb5a70fb0bb40a57627fddf1dbdc6b9c1db81f7c4dcdcb19e2f4/coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", size = 207039 }, + { url = "https://files.pythonhosted.org/packages/3f/f9/d86368ae8c79e28f1fb458ebc76ae9ff3e8bd8069adc24e8f2fed03c58b7/coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", size = 207298 }, + { url = "https://files.pythonhosted.org/packages/64/c5/b4cc3c3f64622c58fbfd4d8b9a7a8ce9d355f172f91fcabbba1f026852f6/coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", size = 239813 }, + { url = "https://files.pythonhosted.org/packages/8a/86/14c42e60b70a79b26099e4d289ccdfefbc68624d096f4481163085aa614c/coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", size = 236959 }, + { url = "https://files.pythonhosted.org/packages/7f/f8/4436a643631a2fbab4b44d54f515028f6099bfb1cd95b13cfbf701e7f2f2/coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", size = 238950 }, + { url = "https://files.pythonhosted.org/packages/49/50/1571810ddd01f99a0a8be464a4ac8b147f322cd1e8e296a1528984fc560b/coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", size = 238610 }, + { url = "https://files.pythonhosted.org/packages/f3/8c/6312d241fe7cbd1f0cade34a62fea6f333d1a261255d76b9a87074d8703c/coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", size = 236697 }, + { url = "https://files.pythonhosted.org/packages/ce/5f/fef33dfd05d87ee9030f614c857deb6df6556b8f6a1c51bbbb41e24ee5ac/coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", size = 238541 }, + { url = "https://files.pythonhosted.org/packages/a9/64/6a984b6e92e1ea1353b7ffa08e27f707a5e29b044622445859200f541e8c/coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", size = 209707 }, + { url = "https://files.pythonhosted.org/packages/5c/60/ce5a9e942e9543783b3db5d942e0578b391c25cdd5e7f342d854ea83d6b7/coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", size = 210439 }, + { url = "https://files.pythonhosted.org/packages/78/53/6719677e92c308207e7f10561a1b16ab8b5c00e9328efc9af7cfd6fb703e/coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", size = 207784 }, + { url = "https://files.pythonhosted.org/packages/fa/dd/7054928930671fcb39ae6a83bb71d9ab5f0afb733172543ced4b09a115ca/coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", size = 208058 }, + { url = "https://files.pythonhosted.org/packages/b5/7d/fd656ddc2b38301927b9eb3aae3fe827e7aa82e691923ed43721fd9423c9/coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", size = 250772 }, + { url = "https://files.pythonhosted.org/packages/90/d0/eb9a3cc2100b83064bb086f18aedde3afffd7de6ead28f69736c00b7f302/coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", size = 246490 }, + { url = "https://files.pythonhosted.org/packages/45/44/3f64f38f6faab8a0cfd2c6bc6eb4c6daead246b97cf5f8fc23bf3788f841/coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", size = 248848 }, + { url = "https://files.pythonhosted.org/packages/5d/11/4c465a5f98656821e499f4b4619929bd5a34639c466021740ecdca42aa30/coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", size = 248340 }, + { url = "https://files.pythonhosted.org/packages/f1/96/ebecda2d016cce9da812f404f720ca5df83c6b29f65dc80d2000d0078741/coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", size = 246229 }, + { url = "https://files.pythonhosted.org/packages/16/d9/3d820c00066ae55d69e6d0eae11d6149a5ca7546de469ba9d597f01bf2d7/coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", size = 247510 }, + { url = "https://files.pythonhosted.org/packages/8f/c3/4fa1eb412bb288ff6bfcc163c11700ff06e02c5fad8513817186e460ed43/coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", size = 210353 }, + { url = "https://files.pythonhosted.org/packages/7e/77/03fc2979d1538884d921c2013075917fc927f41cd8526909852fe4494112/coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", size = 211502 }, + { url = "https://files.pythonhosted.org/packages/cc/56/e1d75e8981a2a92c2a777e67c26efa96c66da59d645423146eb9ff3a851b/coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e", size = 198954 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, +] + +[[package]] +name = "cython" +version = "3.0.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/09/ffb61f29b8e3d207c444032b21328327d753e274ea081bc74e009827cc81/Cython-3.0.8.tar.gz", hash = "sha256:8333423d8fd5765e7cceea3a9985dd1e0a5dfeb2734629e1a2ed2d6233d39de6", size = 2744096 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/f4/d2542e186fe33ec1cc542770fb17466421ed54f4ffe04d00fe9549d0a467/Cython-3.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a846e0a38e2b24e9a5c5dc74b0e54c6e29420d88d1dafabc99e0fc0f3e338636", size = 3100459 }, + { url = "https://files.pythonhosted.org/packages/fc/27/2652f395aa708fb3081148e0df3ab700bd7288636c65332ef7febad6a380/Cython-3.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45523fdc2b78d79b32834cc1cc12dc2ca8967af87e22a3ee1bff20e77c7f5520", size = 3456626 }, + { url = "https://files.pythonhosted.org/packages/f9/bd/e8a1d26d04c08a67bcc383f2ea5493a4e77f37a8770ead00a238b08ad729/Cython-3.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa0b7f3f841fe087410cab66778e2d3fb20ae2d2078a2be3dffe66c6574be39", size = 3621379 }, + { url = "https://files.pythonhosted.org/packages/03/ae/ead7ec03d0062d439879d41b7830e4f2480213f7beabf2f7052a191cc6f7/Cython-3.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e87294e33e40c289c77a135f491cd721bd089f193f956f7b8ed5aa2d0b8c558f", size = 3671873 }, + { url = "https://files.pythonhosted.org/packages/63/b0/81dad725604d7b529c492f873a7fa1b5800704a9f26e100ed25e9fd8d057/Cython-3.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a1df7a129344b1215c20096d33c00193437df1a8fcca25b71f17c23b1a44f782", size = 3463832 }, + { url = "https://files.pythonhosted.org/packages/13/cd/72b8e0af597ac1b376421847acf6d6fa252e60059a2a00dcf05ceb16d28f/Cython-3.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:13c2a5e57a0358da467d97667297bf820b62a1a87ae47c5f87938b9bb593acbd", size = 3618325 }, + { url = "https://files.pythonhosted.org/packages/ef/73/11a4355d8b8966504c751e5bcb25916c4140de27bb2ba1b54ff21994d7fe/Cython-3.0.8-cp310-cp310-win32.whl", hash = "sha256:96b028f044f5880e3cb18ecdcfc6c8d3ce9d0af28418d5ab464509f26d8adf12", size = 2571305 }, + { url = "https://files.pythonhosted.org/packages/18/15/fdc0c3552d20f9337b134a36d786da24e47998fc39f62cb61c1534f26123/Cython-3.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:8140597a8b5cc4f119a1190f5a2228a84f5ca6d8d9ec386cfce24663f48b2539", size = 2776113 }, + { url = "https://files.pythonhosted.org/packages/db/a7/f4a0bc9a80e23b380daa2ebb4879bf434aaa0b3b91f7ad8a7f9762b4bd1b/Cython-3.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aae26f9663e50caf9657148403d9874eea41770ecdd6caf381d177c2b1bb82ba", size = 3113615 }, + { url = "https://files.pythonhosted.org/packages/e9/e9/e9295df74246c165b91253a473bfa179debf739c9bee961cbb3ae56c2b79/Cython-3.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:547eb3cdb2f8c6f48e6865d5a741d9dd051c25b3ce076fbca571727977b28ac3", size = 3436320 }, + { url = "https://files.pythonhosted.org/packages/26/2c/6a887c957aa53e44f928119dea628a5dfacc8e875424034f5fecac9daba4/Cython-3.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a567d4b9ba70b26db89d75b243529de9e649a2f56384287533cf91512705bee", size = 3591755 }, + { url = "https://files.pythonhosted.org/packages/ba/b8/f9c97bae6281da50b3ecb1f7fef0f7f7851eae084609b364717a2b366bf1/Cython-3.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d1426263b0e82fb22bda8ea60dc77a428581cc19e97741011b938445d383f1", size = 3636099 }, + { url = "https://files.pythonhosted.org/packages/17/ae/cd055c2c081c67a6fcad1d8d17d82bd6395b14c6741e3a938f40318c8bc5/Cython-3.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c26daaeccda072459b48d211415fd1e5507c06bcd976fa0d5b8b9f1063467d7b", size = 3458119 }, + { url = "https://files.pythonhosted.org/packages/72/ab/ac6f5548d6194f4bb2fc8c6c996aa7369f0fa1403e4d4de787d9e9309b27/Cython-3.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:289ce7838208211cd166e975865fd73b0649bf118170b6cebaedfbdaf4a37795", size = 3614418 }, + { url = "https://files.pythonhosted.org/packages/70/e2/3e3e448b7a94887bec3235bcb71957b6681dc42b4536459f8f54d46fa936/Cython-3.0.8-cp311-cp311-win32.whl", hash = "sha256:c8aa05f5e17f8042a3be052c24f2edc013fb8af874b0bf76907d16c51b4e7871", size = 2572819 }, + { url = "https://files.pythonhosted.org/packages/85/7d/58635941dfbb5b4e197adb88080b9cbfb230dc3b75683698a530a1989bdb/Cython-3.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:000dc9e135d0eec6ecb2b40a5b02d0868a2f8d2e027a41b0fe16a908a9e6de02", size = 2784167 }, + { url = "https://files.pythonhosted.org/packages/3d/8e/28f8c6109990eef7317ab7e43644092b49a88a39f9373dcd19318946df09/Cython-3.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90d3fe31db55685d8cb97d43b0ec39ef614fcf660f83c77ed06aa670cb0e164f", size = 3135638 }, + { url = "https://files.pythonhosted.org/packages/83/1f/4720cb682b8ed1ab9749dea35351a66dd29b6a022628cce038415660c384/Cython-3.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e24791ddae2324e88e3c902a765595c738f19ae34ee66bfb1a6dac54b1833419", size = 3340052 }, + { url = "https://files.pythonhosted.org/packages/8a/47/ec3fceb9e8f7d6fa130216b8740038e1df7c8e5f215bba363fcf1272a6c1/Cython-3.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f020fa1c0552052e0660790b8153b79e3fc9a15dbd8f1d0b841fe5d204a6ae6", size = 3510079 }, + { url = "https://files.pythonhosted.org/packages/71/31/b458127851e248effb909e2791b55870914863cde7c60b94db5ee65d7867/Cython-3.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18bfa387d7a7f77d7b2526af69a65dbd0b731b8d941aaff5becff8e21f6d7717", size = 3573972 }, + { url = "https://files.pythonhosted.org/packages/6b/d5/ca6513844d0634abd05ba12304053a454bb70441a9520afa9897d4300156/Cython-3.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fe81b339cffd87c0069c6049b4d33e28bdd1874625ee515785bf42c9fdff3658", size = 3356158 }, + { url = "https://files.pythonhosted.org/packages/33/59/98a87b6264f4ad45c820db13c4ec657567476efde020c49443cc842a86af/Cython-3.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:80fd94c076e1e1b1ee40a309be03080b75f413e8997cddcf401a118879863388", size = 3522312 }, + { url = "https://files.pythonhosted.org/packages/2b/cb/132115d07a0b9d4f075e0741db70a5416b424dcd875b2bb0dd805e818222/Cython-3.0.8-cp312-cp312-win32.whl", hash = "sha256:85077915a93e359a9b920280d214dc0cf8a62773e1f3d7d30fab8ea4daed670c", size = 2602579 }, + { url = "https://files.pythonhosted.org/packages/b4/69/cb4620287cd9ef461103e122c0a2ae7f7ecf183e02510676fb5a15c95b05/Cython-3.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:0cb2dcc565c7851f75d496f724a384a790fab12d1b82461b663e66605bec429a", size = 2791268 }, + { url = "https://files.pythonhosted.org/packages/e3/7f/f584f5d15323feb897d42ef0e9d910649e2150d7a30cf7e7a8cc1d236e6f/Cython-3.0.8-py2.py3-none-any.whl", hash = "sha256:171b27051253d3f9108e9759e504ba59ff06e7f7ba944457f94deaf9c21bf0b6", size = 1168213 }, +] + +[[package]] +name = "easydict" +version = "1.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d2/deb3296d08097fedd622d423c0ec8b68b78c1704b3f1545326f6ce05c75c/easydict-1.11.tar.gz", hash = "sha256:dcb1d2ed28eb300c8e46cd371340373abc62f7c14d6dea74fdfc6f1069061c78", size = 6644 } + +[[package]] +name = "exceptiongroup" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/1c/beef724eaf5b01bb44b6338c8c3494eff7cab376fab4904cfbbc3585dc79/exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68", size = 26264 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/9a/5028fd52db10e600f1c4674441b968cf2ea4959085bfb5b99fb1250e5f68/exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14", size = 16210 }, +] + +[[package]] +name = "fastapi" +version = "0.115.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/28/c5d26e5860df807241909a961a37d45e10533acef95fc368066c7dd186cd/fastapi-0.115.11.tar.gz", hash = "sha256:cc81f03f688678b92600a65a5e618b93592c65005db37157147204d8924bf94f", size = 294441 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/5d/4d8bbb94f0dbc22732350c06965e40740f4a92ca560e90bb566f4f73af41/fastapi-0.115.11-py3-none-any.whl", hash = "sha256:32e1541b7b74602e4ef4a0260ecaf3aadf9d4f19590bba3e1bf2ac4666aa2c64", size = 94926 }, +] + +[[package]] +name = "filelock" +version = "3.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/70/41905c80dcfe71b22fb06827b8eae65781783d4a14194bce79d16a013263/filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e", size = 14553 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/54/84d42a0bee35edba99dee7b59a8d4970eccdd44b99fe728ed912106fc781/filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c", size = 11740 }, +] + +[[package]] +name = "flask" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/09/c1a7354d3925a3c6c8cfdebf4245bae67d633ffda1ba415add06ffc839c5/flask-3.0.0.tar.gz", hash = "sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58", size = 674171 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/42/015c23096649b908c809c69388a805a571a3bea44362fe87e33fc3afa01f/flask-3.0.0-py3-none-any.whl", hash = "sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638", size = 99724 }, +] + +[[package]] +name = "flask-cors" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/6a/a8d56d60bcfa1ec3e4fdad81b45aafd508c3bd5c244a16526fa29139d7d4/flask_cors-4.0.1.tar.gz", hash = "sha256:eeb69b342142fdbf4766ad99357a7f3876a2ceb77689dc10ff912aac06c389e4", size = 30306 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/52/2aa6285f104616f73ee1ad7905a16b2b35af0143034ad0cf7b64bcba715c/Flask_Cors-4.0.1-py2.py3-none-any.whl", hash = "sha256:f2a704e4458665580c074b714c4627dd5a306b333deb9074d0b1794dfa2fb677", size = 14290 }, +] + +[[package]] +name = "flask-login" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/6e/2f4e13e373bb49e68c02c51ceadd22d172715a06716f9299d9df01b6ddb2/Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333", size = 48834 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/f5/67e9cc5c2036f58115f9fe0f00d203cf6780c3ff8ae0e705e7a9d9e8ff9e/Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d", size = 17303 }, +] + +[[package]] +name = "flatbuffers" +version = "23.5.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/6e/3e52cd294d8e7a61e010973cce076a0cb2c6c0dfd4d0b7a13648c1b98329/flatbuffers-23.5.26.tar.gz", hash = "sha256:9ea1144cac05ce5d86e2859f431c6cd5e66cd9c78c558317c7955fb8d4c78d89", size = 22114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/12/d5c79ee252793ffe845d58a913197bfa02ae9a0b5c9bc3dc4b58d477b9e7/flatbuffers-23.5.26-py2.py3-none-any.whl", hash = "sha256:c0ff356da363087b915fde4b8b45bdda73432fc17cddb3c8157472eab1422ad1", size = 26744 }, +] + +[[package]] +name = "fonttools" +version = "4.47.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/cd/75d24afa673edf92fd04657fad7d3b5e20c4abc3cad5bc14e5e30051c1f0/fonttools-4.47.2.tar.gz", hash = "sha256:7df26dd3650e98ca45f1e29883c96a0b9f5bb6af8d632a6a108bc744fa0bd9b3", size = 3410067 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/30/02de0b7f3d72f2c4fce3e512b166c1bdbe5a687408474b61eb0114be921c/fonttools-4.47.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b629108351d25512d4ea1a8393a2dba325b7b7d7308116b605ea3f8e1be88df", size = 2779949 }, + { url = "https://files.pythonhosted.org/packages/9a/52/1a5e1373afb78a040ea0c371ab8a79da121060a8e518968bb8f41457ca90/fonttools-4.47.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c19044256c44fe299d9a73456aabee4b4d06c6b930287be93b533b4737d70aa1", size = 2281336 }, + { url = "https://files.pythonhosted.org/packages/c5/ce/9d3b5bf51aafee024566ebb374f5b040381d92660cb04647af3c5860c611/fonttools-4.47.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8be28c036b9f186e8c7eaf8a11b42373e7e4949f9e9f370202b9da4c4c3f56c", size = 4541692 }, + { url = "https://files.pythonhosted.org/packages/e8/68/af41b7cfd35c7418e17b6a43bb106be4b0f0e5feb405a88dee29b186f2a7/fonttools-4.47.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f83a4daef6d2a202acb9bf572958f91cfde5b10c8ee7fb1d09a4c81e5d851fd8", size = 4600529 }, + { url = "https://files.pythonhosted.org/packages/ab/7e/428dbb4cfc342b7a05cbc9d349e134e7fad6588f4ce2a7128e8e3e58ad3b/fonttools-4.47.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5a5318ba5365d992666ac4fe35365f93004109d18858a3e18ae46f67907670", size = 4524215 }, + { url = "https://files.pythonhosted.org/packages/a6/61/762fad1cc1debc4626f2eb373fa999591c63c231fce53d5073574a639531/fonttools-4.47.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8f57ecd742545362a0f7186774b2d1c53423ed9ece67689c93a1055b236f638c", size = 4584778 }, + { url = "https://files.pythonhosted.org/packages/04/30/170ca22284c1d825470e8b5871d6b25d3a70e2f5b185ffb1647d5e11ee4d/fonttools-4.47.2-cp310-cp310-win32.whl", hash = "sha256:a1c154bb85dc9a4cf145250c88d112d88eb414bad81d4cb524d06258dea1bdc0", size = 2131876 }, + { url = "https://files.pythonhosted.org/packages/df/07/4a30437bed355b838b8ce31d14c5983334c31adc97e70c6ecff90c60d6d2/fonttools-4.47.2-cp310-cp310-win_amd64.whl", hash = "sha256:3e2b95dce2ead58fb12524d0ca7d63a63459dd489e7e5838c3cd53557f8933e1", size = 2177937 }, + { url = "https://files.pythonhosted.org/packages/dd/1d/670372323642eada0f7743cfcdd156de6a28d37769c916421fec2f32c814/fonttools-4.47.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29495d6d109cdbabe73cfb6f419ce67080c3ef9ea1e08d5750240fd4b0c4763b", size = 2782908 }, + { url = "https://files.pythonhosted.org/packages/c1/36/5f0bb863a6575db4c4b67fa9be7f98e4c551dd87638ef327bc180b988998/fonttools-4.47.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0a1d313a415eaaba2b35d6cd33536560deeebd2ed758b9bfb89ab5d97dc5deac", size = 2283501 }, + { url = "https://files.pythonhosted.org/packages/bd/1e/95de682a86567426bcc40a56c9b118ffa97de6cbfcc293addf20994e329d/fonttools-4.47.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90f898cdd67f52f18049250a6474185ef6544c91f27a7bee70d87d77a8daf89c", size = 4848039 }, + { url = "https://files.pythonhosted.org/packages/ef/95/92a0b5fc844c1db734752f8a51431de519cd6b02e7e561efa9e9fd415544/fonttools-4.47.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3480eeb52770ff75140fe7d9a2ec33fb67b07efea0ab5129c7e0c6a639c40c70", size = 4893166 }, + { url = "https://files.pythonhosted.org/packages/ff/e6/ed9dd7ee1afd6cd70eb7237688118fe489dbde962e3765c91c86c095f84b/fonttools-4.47.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0255dbc128fee75fb9be364806b940ed450dd6838672a150d501ee86523ac61e", size = 4815529 }, + { url = "https://files.pythonhosted.org/packages/6b/67/cdffa0b3cd8f863b45125c335bbd3d9dc16ec42f5a8d5b64dd1244c5ce6b/fonttools-4.47.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f791446ff297fd5f1e2247c188de53c1bfb9dd7f0549eba55b73a3c2087a2703", size = 4875414 }, + { url = "https://files.pythonhosted.org/packages/b8/fb/41638e748c8f20f5483987afcf9be746d3ccb9e9600ca62128a27c791a82/fonttools-4.47.2-cp311-cp311-win32.whl", hash = "sha256:740947906590a878a4bde7dd748e85fefa4d470a268b964748403b3ab2aeed6c", size = 2130073 }, + { url = "https://files.pythonhosted.org/packages/a0/ef/93321cf55180a778b4d97919b28739874c0afab90e7b9f5b232db70f47c2/fonttools-4.47.2-cp311-cp311-win_amd64.whl", hash = "sha256:63fbed184979f09a65aa9c88b395ca539c94287ba3a364517698462e13e457c9", size = 2178744 }, + { url = "https://files.pythonhosted.org/packages/c0/bd/4dd1e8a9e632f325d9203ce543402f912f26efd213c8d9efec0180fbac64/fonttools-4.47.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4ec558c543609e71b2275c4894e93493f65d2f41c15fe1d089080c1d0bb4d635", size = 2754076 }, + { url = "https://files.pythonhosted.org/packages/e6/4d/c2ebaac81dadbc3fc3c3c2fa5fe7b16429dc713b1b8ace49e11e92904d78/fonttools-4.47.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e040f905d542362e07e72e03612a6270c33d38281fd573160e1003e43718d68d", size = 2263784 }, + { url = "https://files.pythonhosted.org/packages/d3/f6/9d484cd275845c7e503a8669a5952a7fa089c7a881babb4dce5ebe6fc5d1/fonttools-4.47.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dd58cc03016b281bd2c74c84cdaa6bd3ce54c5a7f47478b7657b930ac3ed8eb", size = 4769142 }, + { url = "https://files.pythonhosted.org/packages/7a/bf/c6ae0768a531b38245aac0bb8d30bc05d53d499e09fccdc5d72e7c8d28b6/fonttools-4.47.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32ab2e9702dff0dd4510c7bb958f265a8d3dd5c0e2547e7b5f7a3df4979abb07", size = 4853241 }, + { url = "https://files.pythonhosted.org/packages/2b/f0/c06709666cb7722447efb70ea456c302bd6eb3b997d30076401fb32bca4b/fonttools-4.47.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a808f3c1d1df1f5bf39be869b6e0c263570cdafb5bdb2df66087733f566ea71", size = 4730447 }, + { url = "https://files.pythonhosted.org/packages/3e/71/4c758ae5f4f8047904fc1c6bbbb828248c94cc7aa6406af3a62ede766f25/fonttools-4.47.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac71e2e201df041a2891067dc36256755b1229ae167edbdc419b16da78732c2f", size = 4809265 }, + { url = "https://files.pythonhosted.org/packages/81/f6/a6912c11280607d48947341e2167502605a3917925c835afcd7dfcabc289/fonttools-4.47.2-cp312-cp312-win32.whl", hash = "sha256:69731e8bea0578b3c28fdb43dbf95b9386e2d49a399e9a4ad736b8e479b08085", size = 2118363 }, + { url = "https://files.pythonhosted.org/packages/81/4b/42d0488765ea5aa308b4e8197cb75366b2124240a73e86f98b6107ccf282/fonttools-4.47.2-cp312-cp312-win_amd64.whl", hash = "sha256:b3e1304e5f19ca861d86a72218ecce68f391646d85c851742d265787f55457a4", size = 2165866 }, + { url = "https://files.pythonhosted.org/packages/af/2f/c34b0f99d46766cf49566d1ee2ee3606e4c9880b5a7d734257dc61c804e9/fonttools-4.47.2-py3-none-any.whl", hash = "sha256:7eb7ad665258fba68fd22228a09f347469d95a97fb88198e133595947a20a184", size = 1063011 }, +] + +[[package]] +name = "fsspec" +version = "2023.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/08/cac914ff6ff46c4500fc4323a939dbe7a0f528cca04e7fd3e859611dea41/fsspec-2023.12.2.tar.gz", hash = "sha256:8548d39e8810b59c38014934f6b31e57f40c1b20f911f4cc2b85389c7e9bf0cb", size = 167507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/25/fab23259a52ece5670dcb8452e1af34b89e6135ecc17cd4b54b4b479eac6/fsspec-2023.12.2-py3-none-any.whl", hash = "sha256:d800d87f72189a745fa3d6b033b9dc4a34ad069f60ca60b943a63599f5501960", size = 168979 }, +] + +[[package]] +name = "ftfy" +version = "6.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/d3/8650919bc3c7c6e90ee3fa7fd618bf373cbbe55dff043bd67353dbb20cd8/ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec", size = 308927 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821 }, +] + +[[package]] +name = "gevent" +version = "24.10.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'CPython' and sys_platform == 'win32'" }, + { name = "greenlet", marker = "platform_python_implementation == 'CPython'" }, + { name = "zope-event" }, + { name = "zope-interface" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/f0/be10ed5d7721ed2317d7feb59e167603217156c2a6d57f128523e24e673d/gevent-24.10.3.tar.gz", hash = "sha256:aa7ee1bd5cabb2b7ef35105f863b386c8d5e332f754b60cfc354148bd70d35d1", size = 6108837 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/6f/a2100e7883c7bdfc2b45cb60b310ca748762a21596258b9dd01c5c093dbc/gevent-24.10.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d7a1ad0f2da582f5bd238bca067e1c6c482c30c15a6e4d14aaa3215cbb2232f3", size = 3014382 }, + { url = "https://files.pythonhosted.org/packages/7a/b1/460e4884ed6185d9eb9c4c2e9639d2b254197e46513301c0f63dec22dc90/gevent-24.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4e526fdc279c655c1e809b0c34b45844182c2a6b219802da5e411bd2cf5a8ad", size = 4853460 }, + { url = "https://files.pythonhosted.org/packages/ca/f6/7ded98760d381229183ecce8db2edcce96f13e23807d31a90c66dae85304/gevent-24.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57a5c4e0bdac482c5f02f240d0354e61362df73501ef6ebafce8ef635cad7527", size = 4977636 }, + { url = "https://files.pythonhosted.org/packages/7d/21/7b928e6029eedb93ef94fc0aee701f497af2e601f0ec00aac0e72e3f450e/gevent-24.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d67daed8383326dc8b5e58d88e148d29b6b52274a489e383530b0969ae7b9cb9", size = 5058031 }, + { url = "https://files.pythonhosted.org/packages/00/98/12c03fd004fbeeca01276ffc589f5a368fd741d02582ab7006d1bdef57e7/gevent-24.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e24ffea72e27987979c009536fd0868e52239b44afe6cf7135ce8aafd0f108e", size = 6683694 }, + { url = "https://files.pythonhosted.org/packages/64/4c/ea14d971452d3da09e49267e052d8312f112c7835120aed78d22ef14efee/gevent-24.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c1d80090485da1ea3d99205fe97908b31188c1f4857f08b333ffaf2de2e89d18", size = 5286063 }, + { url = "https://files.pythonhosted.org/packages/39/3f/397efff27e637d7306caa00d1560512c44028c25c70be1e72c46b79b1b66/gevent-24.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0c129f81d60cda614acb4b0c5731997ca05b031fb406fcb58ad53a7ade53b13", size = 6817462 }, + { url = "https://files.pythonhosted.org/packages/aa/5d/19939eaa7c5b7c0f37e0a0665a911ddfe1e35c25c512446fc356a065c16e/gevent-24.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:26ca7a6b42d35129617025ac801135118333cad75856ffc3217b38e707383eba", size = 1566631 }, + { url = "https://files.pythonhosted.org/packages/6e/01/1be5cf013826d8baae235976d6a94f3628014fd2db7c071aeec13f82b4d1/gevent-24.10.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:68c3a0d8402755eba7f69022e42e8021192a721ca8341908acc222ea597029b6", size = 2966909 }, + { url = "https://files.pythonhosted.org/packages/fe/3e/7fa9ab023f24d8689e2c77951981f8ea1f25089e0349a0bf8b35ee9b9277/gevent-24.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d850a453d66336272be4f1d3a8126777f3efdaea62d053b4829857f91e09755", size = 4913247 }, + { url = "https://files.pythonhosted.org/packages/db/63/6e40eaaa3c2abd1561faff11dc3e6781f8c25e975354b8835762834415af/gevent-24.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e58ee3723f1fbe07d66892f1caa7481c306f653a6829b6fd16cb23d618a5915", size = 5049036 }, + { url = "https://files.pythonhosted.org/packages/94/89/158bc32cdc898dda0481040ac18650022e73133d93460c5af56ca622fe9a/gevent-24.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b52382124eca13135a3abe4f65c6bd428656975980a48e51b17aeab68bdb14db", size = 5107299 }, + { url = "https://files.pythonhosted.org/packages/64/91/1abe62ee350fdfac186d33f615d0d3a0b3b140e7ccf23c73547aa0deec44/gevent-24.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ca2266e08f43c0e22c028801dff7d92a0b102ef20e4caeb6a46abfb95f6a328", size = 6819625 }, + { url = "https://files.pythonhosted.org/packages/92/8b/0b2fe0d36b7c4d463e46cc68eaf6c14488bd7d86cc37e995c64a0ff7d02f/gevent-24.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d758f0d4dbf32502ec87bb9b536ca8055090a16f8305f0ada3ce6f34e70f2fd7", size = 5474079 }, + { url = "https://files.pythonhosted.org/packages/12/7b/9f5abbf0021a50321314f850697e0f46d2e5081168223af2d8544af9d19f/gevent-24.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0de6eb3d55c03138fda567d9bfed28487ce5d0928c5107549767a93efdf2be26", size = 6901323 }, + { url = "https://files.pythonhosted.org/packages/8a/63/607715c621ae78ed581b7ba36d076df63feeb352993d521327f865056771/gevent-24.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:385710355eadecdb70428a5ae3e7e5a45dcf888baa1426884588be9d25ac4290", size = 1549468 }, + { url = "https://files.pythonhosted.org/packages/d9/e4/4edbe17001bb3e6fade4ad2d85ca8f9e4eabcbde4aa29aa6889281616e3e/gevent-24.10.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3ad8fb70aa0ebc935729c9699ac31b210a49b689a7b27b7ac9f91676475f3f53", size = 2970952 }, + { url = "https://files.pythonhosted.org/packages/3c/a6/ce0824fe9398ba6b00028a74840f12be1165d5feaacdc028ea953db3d6c3/gevent-24.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f18689f7a70d2ed0e75bad5036ec3c89690a493d4cfac8d7cdb258ac04b132bd", size = 5172230 }, + { url = "https://files.pythonhosted.org/packages/25/d4/9002cfb585bfa52c860ed4b1349d1a6400bdf2df9f1bd21df5ff33eea33c/gevent-24.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f4f171d4d2018170454d84c934842e1b5f6ce7468ba298f6e7f7cff15000a3", size = 5338394 }, + { url = "https://files.pythonhosted.org/packages/0c/98/222f1a14f22ad2d1cbcc37edb74095264c1f9c7ab49e6423693383462b8a/gevent-24.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7021e26d70189b33c27173d4173f27bf4685d6b6f1c0ea50e5335f8491cb110c", size = 5437989 }, + { url = "https://files.pythonhosted.org/packages/bf/e8/cbb46afea3c7ecdc7289e15cb4a6f89903f4f9754a27ca320d3e465abc78/gevent-24.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34aea15f9c79f27a8faeaa361bc1e72c773a9b54a1996a2ec4eefc8bcd59a824", size = 6838539 }, + { url = "https://files.pythonhosted.org/packages/69/c3/e43e348f23da404a6d4368a14453ed097cdfca97d5212eaceb987d04a0e1/gevent-24.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8af65a4d4feaec6042c666d22c322a310fba3b47e841ad52f724b9c3ce5da48e", size = 5513842 }, + { url = "https://files.pythonhosted.org/packages/c2/76/84b7c19c072a80900118717a85236859127d630cdf8b079fe42f19649f12/gevent-24.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:89c4115e3f5ada55f92b61701a46043fe42f702b5af863b029e4c1a76f6cc2d4", size = 6927374 }, + { url = "https://files.pythonhosted.org/packages/5e/69/0ab1b04c363547058fb5035275c144957b80b36cb6aee715fe6181b0cee9/gevent-24.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:1ce6dab94c0b0d24425ba55712de2f8c9cb21267150ca63f5bb3a0e1f165da99", size = 1546701 }, + { url = "https://files.pythonhosted.org/packages/f7/2d/c783583d7999cd2f2e7aa2d6a1c333d663003ca61255a89ff6a891be95f4/gevent-24.10.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:f147e38423fbe96e8731f60a63475b3d2cab2f3d10578d8ee9d10c507c58a2ff", size = 2962857 }, + { url = "https://files.pythonhosted.org/packages/f3/77/d3ce96fd49406f61976e9a3b6c742b97bb274d3b30c68ff190c5b5f81afd/gevent-24.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18e6984ec96fc95fd67488555c38ece3015be1f38b1bcceb27b7d6c36b343008", size = 5141676 }, + { url = "https://files.pythonhosted.org/packages/49/f4/f99f893770c316b9d2f03bd684947126cbed0321b89fe5423838974c2025/gevent-24.10.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:051b22e2758accfddb0457728bfc9abf8c3f2ce6bca43f1ff6e07b5ed9e49bf4", size = 5310248 }, + { url = "https://files.pythonhosted.org/packages/e3/0c/67257ba906f76ed82e8f0bd8c00c2a0687b360a1050b70db7e58dff749ab/gevent-24.10.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb5edb6433764119a664bbb148d2aea9990950aa89cc3498f475c2408d523ea3", size = 5407304 }, + { url = "https://files.pythonhosted.org/packages/35/6c/3a72da7c224b0111728130c0f1abc3ee07feff91b37e0ea83db98f4a3eaf/gevent-24.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce417bcaaab496bc9c77f75566531e9d93816262037b8b2dbb88b0fdcd66587c", size = 6818624 }, + { url = "https://files.pythonhosted.org/packages/a3/96/cc5f6ecba032a45fc312fe0db2908a893057fd81361eea93845d6c325556/gevent-24.10.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:1c3a828b033fb02b7c31da4d75014a1f82e6c072fc0523456569a57f8b025861", size = 5484356 }, + { url = "https://files.pythonhosted.org/packages/7c/97/e680b2b2f0c291ae4db9813ffbf02c22c2a0f14c8f1a613971385e29ef67/gevent-24.10.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f2ae3efbbd120cdf4a68b7abc27a37e61e6f443c5a06ec2c6ad94c37cd8471ec", size = 6903191 }, + { url = "https://files.pythonhosted.org/packages/1b/1c/b4181957da062d1c060974ec6cb798cc24aeeb28e8cd2ece84eb4b4991f7/gevent-24.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:9e1210334a9bc9f76c3d008e0785ca62214f8a54e1325f6c2ecab3b6a572a015", size = 1545117 }, + { url = "https://files.pythonhosted.org/packages/89/2b/bf4af9950b8f9abd5b4025858f6311930de550e3498bbfeb47c914701a1d/gevent-24.10.3-pp310-pypy310_pp73-macosx_11_0_universal2.whl", hash = "sha256:e534e6a968d74463b11de6c9c67f4b4bf61775fb00f2e6e0f7fcdd412ceade18", size = 1271541 }, +] + +[[package]] +name = "geventhttpclient" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "brotli" }, + { name = "certifi" }, + { name = "gevent" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/14/d4eddae757de44985718a9e38d9e6f2a923d764ed97d0f1cbc1a8aa2b0ef/geventhttpclient-2.3.1.tar.gz", hash = "sha256:b40ddac8517c456818942c7812f555f84702105c82783238c9fcb8dc12675185", size = 69345 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/a5/5e49d6a581b3f1399425e22961c6e341e90c12fa2193ed0adee9afbd864c/geventhttpclient-2.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da22ab7bf5af4ba3d07cffee6de448b42696e53e7ac1fe97ed289037733bf1c2", size = 71729 }, + { url = "https://files.pythonhosted.org/packages/eb/23/4ff584e5f344dae64b5bc588b65c4ea81083f9d662b9f64cf5f28e5ae9cc/geventhttpclient-2.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2399e3d4e2fae8bbd91756189da6e9d84adf8f3eaace5eef0667874a705a29f8", size = 52062 }, + { url = "https://files.pythonhosted.org/packages/bb/60/6bd8badb97b31a49f4c2b79466abce208a97dad95d447893c7546063fc8a/geventhttpclient-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3e33e87d0d5b9f5782c4e6d3cb7e3592fea41af52713137d04776df7646d71b", size = 51645 }, + { url = "https://files.pythonhosted.org/packages/e1/62/47d431bf05f74aa683d63163a11432bda8f576c86dec8c3bc9d6a156ee03/geventhttpclient-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c071db313866c3d0510feb6c0f40ec086ccf7e4a845701b6316c82c06e8b9b29", size = 117838 }, + { url = "https://files.pythonhosted.org/packages/6c/8b/e7c9ae813bb41883a96ad9afcf86465219c3bb682daa8b09448481edef8a/geventhttpclient-2.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f36f0c6ef88a27e60af8369d9c2189fe372c6f2943182a7568e0f2ad33bb69f1", size = 123272 }, + { url = "https://files.pythonhosted.org/packages/4d/26/71e9b2526009faadda9f588dac04f8bf837a5b97628ab44145efc3fa796e/geventhttpclient-2.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4624843c03a5337282a42247d987c2531193e57255ee307b36eeb4f243a0c21", size = 114319 }, + { url = "https://files.pythonhosted.org/packages/34/8c/1da2960293c42b7a6b01dbe3204b569e4cdb55b8289cb1c7154826500f19/geventhttpclient-2.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d614573621ba827c417786057e1e20e9f96c4f6b3878c55b1b7b54e1026693bc", size = 112705 }, + { url = "https://files.pythonhosted.org/packages/a7/a1/4d08ecf0f213fdc63f78a217f87c07c1cb9891e68cdf74c8cbca76298bdb/geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5d51330a40ac9762879d0e296c279c1beae8cfa6484bb196ac829242c416b709", size = 121236 }, + { url = "https://files.pythonhosted.org/packages/4f/f7/42ece3e1f54602c518d74364a214da3b35b6be267b335564b7e9f0d37705/geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc9f2162d4e8cb86bb5322d99bfd552088a3eacd540a841298f06bb8bc1f1f03", size = 117859 }, + { url = "https://files.pythonhosted.org/packages/1f/8e/de026b3697bffe5fa1a4938a3882107e378eea826905acf8e46c69b71ffd/geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:06e59d3397e63c65ecc7a7561a5289f0cf2e2c2252e29632741e792f57f5d124", size = 127268 }, + { url = "https://files.pythonhosted.org/packages/54/bf/1ee99a322467e6825a24612d306a46ca94b51088170d1b5de0df1c82ab2a/geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4436eef515b3e0c1d4a453ae32e047290e780a623c1eddb11026ae9d5fb03d42", size = 116426 }, + { url = "https://files.pythonhosted.org/packages/72/54/10c8ec745b3dcbfd52af62977fec85829749c0325e1a5429d050a4b45e75/geventhttpclient-2.3.1-cp310-cp310-win32.whl", hash = "sha256:5d1cf7d8a4f8e15cc8fd7d88ac4cdb058d6274203a42587e594cc9f0850ac862", size = 47599 }, + { url = "https://files.pythonhosted.org/packages/da/0d/36a47cdeaa83c3b4efdbd18d77720fa27dc40600998f4dedd7c4a1259862/geventhttpclient-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:4deaebc121036f7ea95430c2d0f80ab085b15280e6ab677a6360b70e57020e7f", size = 48302 }, + { url = "https://files.pythonhosted.org/packages/56/ad/1fcbbea0465f04d4425960e3737d4d8ae6407043cfc88688fb17b9064160/geventhttpclient-2.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0ae055b9ce1704f2ce72c0847df28f4e14dbb3eea79256cda6c909d82688ea3", size = 71733 }, + { url = "https://files.pythonhosted.org/packages/06/1a/10e547adb675beea407ff7117ecb4e5063534569ac14bb4360279d2888dd/geventhttpclient-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f087af2ac439495b5388841d6f3c4de8d2573ca9870593d78f7b554aa5cfa7f5", size = 52060 }, + { url = "https://files.pythonhosted.org/packages/e0/c0/9960ac6e8818a00702743cd2a9637d6f26909ac7ac59ca231f446e367b20/geventhttpclient-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76c367d175810facfe56281e516c9a5a4a191eff76641faaa30aa33882ed4b2f", size = 51649 }, + { url = "https://files.pythonhosted.org/packages/58/3a/b032cd8f885dafdfa002a8a0e4e21b633713798ec08e19010b815fbfead6/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a58376d0d461fe0322ff2ad362553b437daee1eeb92b4c0e3b1ffef9e77defbe", size = 117987 }, + { url = "https://files.pythonhosted.org/packages/94/36/6493a5cbc20c269a51186946947f3ca2eae687e05831289891027bd038c3/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f440cc704f8a9869848a109b2c401805c17c070539b2014e7b884ecfc8591e33", size = 123356 }, + { url = "https://files.pythonhosted.org/packages/2f/07/b66d9a13b97a7e59d84b4faf704113aa963aaf3a0f71c9138c8740d57d5c/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f10c62994f9052f23948c19de930b2d1f063240462c8bd7077c2b3290e61f4fa", size = 114460 }, + { url = "https://files.pythonhosted.org/packages/4e/72/1467b9e1ef63aecfe3b42333fb7607f66129dffaeca231f97e4be6f71803/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c45d9f3dd9627844c12e9ca347258c7be585bed54046336220e25ea6eac155", size = 112808 }, + { url = "https://files.pythonhosted.org/packages/ce/ef/64894efd67cb3459074c734736ecacff398cd841a5538dc70e3e77d35500/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:77c1a2c6e3854bf87cd5588b95174640c8a881716bd07fa0d131d082270a6795", size = 122049 }, + { url = "https://files.pythonhosted.org/packages/c5/c8/1b13b4ea4bb88d7c2db56d070a52daf4757b3139afd83885e81455cb422f/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ce649d4e25c2d56023471df0bf1e8e2ab67dfe4ff12ce3e8fe7e6fae30cd672a", size = 118755 }, + { url = "https://files.pythonhosted.org/packages/d1/06/95ac63fa1ee118a4d5824aa0a6b0dc3a2934a2f4ce695bf6747e1744d813/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:265d9f31b4ac8f688eebef0bd4c814ffb37a16f769ad0c8c8b8c24a84db8eab5", size = 128053 }, + { url = "https://files.pythonhosted.org/packages/8a/27/3d6dbbd128e1b965bae198bffa4b5552cd635397e3d2bbcc7d9592890ca9/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2de436a9d61dae877e4e811fb3e2594e2a1df1b18f4280878f318aef48a562b9", size = 117316 }, + { url = "https://files.pythonhosted.org/packages/ed/9a/8b65daf417ff982fa1928ebc6ebdfb081750d426f877f0056288aaa689e8/geventhttpclient-2.3.1-cp311-cp311-win32.whl", hash = "sha256:83e22178b9480b0a95edf0053d4f30b717d0b696b3c262beabe6964d9c5224b1", size = 47598 }, + { url = "https://files.pythonhosted.org/packages/ab/83/ed0d14787861cf30beddd3aadc29ad07d75555de43c629ba514ddd2978d0/geventhttpclient-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:97b072a282233384c1302a7dee88ad8bfedc916f06b1bc1da54f84980f1406a9", size = 48301 }, + { url = "https://files.pythonhosted.org/packages/82/ee/bf3d26170a518d2b1254f44202f2fa4490496b476ee24046ff6c34e79c08/geventhttpclient-2.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e1c90abcc2735cd8dd2d2572a13da32f6625392dc04862decb5c6476a3ddee22", size = 71742 }, + { url = "https://files.pythonhosted.org/packages/77/72/bd64b2a491094a3fbf7f3c314bb3c3918afb652783a8a9db07b86072da35/geventhttpclient-2.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5deb41c2f51247b4e568c14964f59d7b8e537eff51900564c88af3200004e678", size = 52070 }, + { url = "https://files.pythonhosted.org/packages/85/96/e25becfde16c5551ba04ed2beac1f018e2efc70275ec19ae3765ff634ff2/geventhttpclient-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c6f1a56a66a90c4beae2f009b5e9d42db9a58ced165aa35441ace04d69cb7b37", size = 51650 }, + { url = "https://files.pythonhosted.org/packages/5d/b8/fe6e938a369b3742103d04e5771e1ec7b18c047ac30b06a8e9704e2d34fc/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ee6e741849c29e3129b1ec3828ac3a5e5dcb043402f852ea92c52334fb8cabf", size = 118507 }, + { url = "https://files.pythonhosted.org/packages/68/0b/381d01de049b02dc70addbcc1c8e24d15500bff6a9e89103c4aa8eb352c3/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d0972096a63b1ddaa73fa3dab2c7a136e3ab8bf7999a2f85a5dee851fa77cdd", size = 124061 }, + { url = "https://files.pythonhosted.org/packages/c6/e6/7c97b5bf41cc403b2936a0887a85550b3153aa4b60c0c5062c49cd6286f2/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00675ba682fb7d19d659c14686fa8a52a65e3f301b56c2a4ee6333b380dd9467", size = 115060 }, + { url = "https://files.pythonhosted.org/packages/45/1f/3e02464449c74a8146f27218471578c1dfabf18731cf047520b76e1b6331/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea77b67c186df90473416f4403839728f70ef6cf1689cec97b4f6bbde392a8a8", size = 113762 }, + { url = "https://files.pythonhosted.org/packages/4f/a4/08551776f7d6b219d6f73ca25be88806007b16af51a1dbfed7192528e1c3/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ddcc3f0fdffd9a3801e1005b73026202cffed8199863fdef9315bea9a860a032", size = 122018 }, + { url = "https://files.pythonhosted.org/packages/70/14/ba91417ac7cbce8d553f72c885a19c6b9d7f9dc7de81b7814551cf020a57/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:c9f1ef4ec048563cc621a47ff01a4f10048ff8b676d7a4d75e5433ed8e703e56", size = 118884 }, + { url = "https://files.pythonhosted.org/packages/7c/78/e1f2c30e11bda8347a74b3a7254f727ff53ea260244da77d76b96779a006/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:a364b30bec7a0a00dbe256e2b6807e4dc866bead7ac84aaa51ca5e2c3d15c258", size = 128224 }, + { url = "https://files.pythonhosted.org/packages/ac/2f/b7fd96e9cfa9d9719b0c9feb50b4cbb341d1940e34fd3305006efa8c3e33/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:25d255383d3d6a6fbd643bb51ae1a7e4f6f7b0dbd5f3225b537d0bd0432eaf39", size = 117758 }, + { url = "https://files.pythonhosted.org/packages/fb/e0/1384c9a76379ab257b75df92283797861dcae592dd98e471df254f87c635/geventhttpclient-2.3.1-cp312-cp312-win32.whl", hash = "sha256:ad0b507e354d2f398186dcb12fe526d0594e7c9387b514fb843f7a14fdf1729a", size = 47595 }, + { url = "https://files.pythonhosted.org/packages/54/e3/6b8dbb24e3941e20abbe7736e59290c5d4182057ea1d984d46c853208bcd/geventhttpclient-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:7924e0883bc2b177cfe27aa65af6bb9dd57f3e26905c7675a2d1f3ef69df7cca", size = 48271 }, + { url = "https://files.pythonhosted.org/packages/ee/9f/251b1b7e665523137a8711f0f0029196cf18b57741135f01aea80a56f16c/geventhttpclient-2.3.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c31431e38df45b3c79bf3c9427c796adb8263d622bc6fa25e2f6ba916c2aad93", size = 49827 }, + { url = "https://files.pythonhosted.org/packages/74/c7/ad4c23de669191e1c83cfa28c51d3b50fc246d72e1ee40d4d5b330532492/geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:855ab1e145575769b180b57accb0573a77cd6a7392f40a6ef7bc9a4926ebd77b", size = 54017 }, + { url = "https://files.pythonhosted.org/packages/04/7b/59fc8c8fbd10596abfc46dc103654e3d9676de64229d8eee4b0a4ac2e890/geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a374aad77c01539e786d0c7829bec2eba034ccd45733c1bf9811ad18d2a8ecd", size = 58359 }, + { url = "https://files.pythonhosted.org/packages/94/b7/743552b0ecda75458c83d55d62937e29c9ee9a42598f57d4025d5de70004/geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c1e97460608304f400485ac099736fff3566d3d8db2038533d466f8cf5de5a", size = 54262 }, + { url = "https://files.pythonhosted.org/packages/18/60/10f6215b6cc76b5845a7f4b9c3d1f47d7ecd84ce8769b1e27e0482d605d7/geventhttpclient-2.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4f843f81ee44ba4c553a1b3f73115e0ad8f00044023c24db29f5b1df3da08465", size = 48343 }, +] + +[[package]] +name = "greenlet" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/90/5234a78dc0ef6496a6eb97b67a42a8e96742a56f7dc808cb954a85390448/greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", size = 271235 }, + { url = "https://files.pythonhosted.org/packages/7c/16/cd631fa0ab7d06ef06387135b7549fdcc77d8d859ed770a0d28e47b20972/greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", size = 637168 }, + { url = "https://files.pythonhosted.org/packages/2f/b1/aed39043a6fec33c284a2c9abd63ce191f4f1a07319340ffc04d2ed3256f/greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", size = 648826 }, + { url = "https://files.pythonhosted.org/packages/76/25/40e0112f7f3ebe54e8e8ed91b2b9f970805143efef16d043dfc15e70f44b/greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", size = 644443 }, + { url = "https://files.pythonhosted.org/packages/fb/2f/3850b867a9af519794784a7eeed1dd5bc68ffbcc5b28cef703711025fd0a/greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", size = 643295 }, + { url = "https://files.pythonhosted.org/packages/cf/69/79e4d63b9387b48939096e25115b8af7cd8a90397a304f92436bcb21f5b2/greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", size = 599544 }, + { url = "https://files.pythonhosted.org/packages/46/1d/44dbcb0e6c323bd6f71b8c2f4233766a5faf4b8948873225d34a0b7efa71/greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", size = 1125456 }, + { url = "https://files.pythonhosted.org/packages/e0/1d/a305dce121838d0278cee39d5bb268c657f10a5363ae4b726848f833f1bb/greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", size = 1149111 }, + { url = "https://files.pythonhosted.org/packages/96/28/d62835fb33fb5652f2e98d34c44ad1a0feacc8b1d3f1aecab035f51f267d/greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", size = 298392 }, + { url = "https://files.pythonhosted.org/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", size = 272479 }, + { url = "https://files.pythonhosted.org/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", size = 640404 }, + { url = "https://files.pythonhosted.org/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", size = 652813 }, + { url = "https://files.pythonhosted.org/packages/49/93/d5f93c84241acdea15a8fd329362c2c71c79e1a507c3f142a5d67ea435ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", size = 648517 }, + { url = "https://files.pythonhosted.org/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", size = 647831 }, + { url = "https://files.pythonhosted.org/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", size = 602413 }, + { url = "https://files.pythonhosted.org/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", size = 1129619 }, + { url = "https://files.pythonhosted.org/packages/f4/fb/201e1b932e584066e0f0658b538e73c459b34d44b4bd4034f682423bc801/greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", size = 1155198 }, + { url = "https://files.pythonhosted.org/packages/12/da/b9ed5e310bb8b89661b80cbcd4db5a067903bbcd7fc854923f5ebb4144f0/greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", size = 298930 }, + { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260 }, + { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064 }, + { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420 }, + { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035 }, + { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105 }, + { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077 }, + { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975 }, + { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955 }, + { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655 }, + { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990 }, + { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175 }, + { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425 }, + { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736 }, + { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347 }, + { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583 }, + { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039 }, + { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716 }, + { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490 }, + { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731 }, + { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304 }, + { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537 }, + { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506 }, + { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753 }, + { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731 }, + { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112 }, +] + +[[package]] +name = "gunicorn" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029 }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + +[[package]] +name = "httpcore" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/56/78a38490b834fa0942cbe6d39bd8a7fd76316e8940319305a98d2b320366/httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535", size = 81036 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/ba/78b0a99c4da0ff8b0f59defa2f13ca4668189b134bd9840b6202a93d9a0f/httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7", size = 76943 }, +] + +[[package]] +name = "httptools" +version = "0.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/6f/972f8eb0ea7d98a1c6be436e2142d51ad2a64ee18e02b0e7ff1f62171ab1/httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0", size = 198780 }, + { url = "https://files.pythonhosted.org/packages/6a/b0/17c672b4bc5c7ba7f201eada4e96c71d0a59fbc185e60e42580093a86f21/httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da", size = 103297 }, + { url = "https://files.pythonhosted.org/packages/92/5e/b4a826fe91971a0b68e8c2bd4e7db3e7519882f5a8ccdb1194be2b3ab98f/httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1", size = 443130 }, + { url = "https://files.pythonhosted.org/packages/b0/51/ce61e531e40289a681a463e1258fa1e05e0be54540e40d91d065a264cd8f/httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50", size = 442148 }, + { url = "https://files.pythonhosted.org/packages/ea/9e/270b7d767849b0c96f275c695d27ca76c30671f8eb8cc1bab6ced5c5e1d0/httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959", size = 415949 }, + { url = "https://files.pythonhosted.org/packages/81/86/ced96e3179c48c6f656354e106934e65c8963d48b69be78f355797f0e1b3/httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4", size = 417591 }, + { url = "https://files.pythonhosted.org/packages/75/73/187a3f620ed3175364ddb56847d7a608a6fc42d551e133197098c0143eca/httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c", size = 88344 }, + { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029 }, + { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492 }, + { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891 }, + { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788 }, + { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214 }, + { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120 }, + { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565 }, + { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683 }, + { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337 }, + { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796 }, + { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837 }, + { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289 }, + { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779 }, + { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634 }, + { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214 }, + { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431 }, + { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121 }, + { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805 }, + { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858 }, + { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042 }, + { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682 }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, +] + +[[package]] +name = "huggingface-hub" +version = "0.29.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/b2/f8b3c9842a794e8203448725aefa02d7c9e0da42d5f22f4ed806057cc36e/huggingface_hub-0.29.2.tar.gz", hash = "sha256:590b29c0dcbd0ee4b7b023714dc1ad8563fe4a68a91463438b74e980d28afaf3", size = 389816 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/5f/088ff08dc41808fcd99d9972b9bcfa7e3a35e30e8b0a3155b57938f1611c/huggingface_hub-0.29.2-py3-none-any.whl", hash = "sha256:c56f20fca09ef19da84dcde2b76379ecdaddf390b083f59f166715584953307d", size = 468087 }, +] + +[[package]] +name = "humanfriendly" +version = "10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyreadline3", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794 }, +] + +[[package]] +name = "idna" +version = "3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 }, +] + +[[package]] +name = "imageio" +version = "2.33.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/38/f4c568318c656352d211eec6954460dc3af0b7583a6682308f8a66e4c19b/imageio-2.33.1.tar.gz", hash = "sha256:78722d40b137bd98f5ec7312119f8aea9ad2049f76f434748eb306b6937cc1ce", size = 387374 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/69/3aaa69cb0748e33e644fda114c9abd3186ce369edd4fca11107e9f39c6a7/imageio-2.33.1-py3-none-any.whl", hash = "sha256:c5094c48ccf6b2e6da8b4061cd95e1209380afafcbeae4a4e280938cce227e1d", size = 313345 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "insightface" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "albumentations" }, + { name = "cython" }, + { name = "easydict" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "onnx" }, + { name = "pillow" }, + { name = "prettytable" }, + { name = "requests" }, + { name = "scikit-image" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/8d/0f4af90999ca96cf8cb846eb5ae27c5ef5b390f9c090dd19e4fa76364c13/insightface-0.7.3.tar.gz", hash = "sha256:f191f719612ebb37018f41936814500544cd0f86e6fcd676c023f354c668ddf7", size = 439490 } + +[[package]] +name = "itsdangerous" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/a1/d3fb83e7a61fa0c0d3d08ad0a94ddbeff3731c05212617dff3a94e097f08/itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a", size = 56143 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/5f/447e04e828f47465eeab35b5d408b7ebaaaee207f48b7136c5a7267a30ae/itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", size = 15749 }, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, +] + +[[package]] +name = "joblib" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/0f/d3b33b9f106dddef461f6df1872b7881321b247f3d255b87f61a7636f7fe/joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1", size = 1987720 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/40/d551139c85db202f1f384ba8bcf96aca2f329440a844f924c8a0040b6d02/joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9", size = 302207 }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2d/226779e405724344fc678fcc025b812587617ea1a48b9442628b688e85ea/kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec", size = 97552 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/56/cb02dcefdaab40df636b91e703b172966b444605a0ea313549f3ffc05bd3/kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af", size = 127397 }, + { url = "https://files.pythonhosted.org/packages/0e/c1/d084f8edb26533a191415d5173157080837341f9a06af9dd1a75f727abb4/kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3", size = 68125 }, + { url = "https://files.pythonhosted.org/packages/23/11/6fb190bae4b279d712a834e7b1da89f6dcff6791132f7399aa28a57c3565/kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4", size = 66211 }, + { url = "https://files.pythonhosted.org/packages/b3/13/5e9e52feb33e9e063f76b2c5eb09cb977f5bba622df3210081bfb26ec9a3/kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1", size = 1637145 }, + { url = "https://files.pythonhosted.org/packages/6f/40/4ab1fdb57fced80ce5903f04ae1aed7c1d5939dda4fd0c0aa526c12fe28a/kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff", size = 1617849 }, + { url = "https://files.pythonhosted.org/packages/49/ca/61ef43bd0832c7253b370735b0c38972c140c8774889b884372a629a8189/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a", size = 1400921 }, + { url = "https://files.pythonhosted.org/packages/68/6f/854f6a845c00b4257482468e08d8bc386f4929ee499206142378ba234419/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa", size = 1513009 }, + { url = "https://files.pythonhosted.org/packages/50/65/76f303377167d12eb7a9b423d6771b39fe5c4373e4a42f075805b1f581ae/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c", size = 1444819 }, + { url = "https://files.pythonhosted.org/packages/7e/ee/98cdf9dde129551467138b6e18cc1cc901e75ecc7ffb898c6f49609f33b1/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b", size = 1817054 }, + { url = "https://files.pythonhosted.org/packages/e6/5b/ab569016ec4abc7b496f6cb8a3ab511372c99feb6a23d948cda97e0db6da/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770", size = 1918613 }, + { url = "https://files.pythonhosted.org/packages/93/ac/39b9f99d2474b1ac7af1ddfe5756ddf9b6a8f24c5f3a32cd4c010317fc6b/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0", size = 1872650 }, + { url = "https://files.pythonhosted.org/packages/40/5b/be568548266516b114d1776120281ea9236c732fb6032a1f8f3b1e5e921c/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525", size = 1827415 }, + { url = "https://files.pythonhosted.org/packages/d4/80/c0c13d2a17a12937a19ef378bf35e94399fd171ed6ec05bcee0f038e1eaf/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b", size = 1838094 }, + { url = "https://files.pythonhosted.org/packages/70/d1/5ab93ee00ca5af708929cc12fbe665b6f1ed4ad58088e70dc00e87e0d107/kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238", size = 46585 }, + { url = "https://files.pythonhosted.org/packages/4a/a1/8a9c9be45c642fa12954855d8b3a02d9fd8551165a558835a19508fec2e6/kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276", size = 56095 }, + { url = "https://files.pythonhosted.org/packages/2a/eb/9e099ad7c47c279995d2d20474e1821100a5f10f847739bd65b1c1f02442/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5", size = 127403 }, + { url = "https://files.pythonhosted.org/packages/a6/94/695922e71288855fc7cace3bdb52edda9d7e50edba77abb0c9d7abb51e96/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90", size = 68156 }, + { url = "https://files.pythonhosted.org/packages/4a/fe/23d7fa78f7c66086d196406beb1fb2eaf629dd7adc01c3453033303d17fa/kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797", size = 66166 }, + { url = "https://files.pythonhosted.org/packages/f1/68/f472bf16c9141bb1bea5c0b8c66c68fc1ccb048efdbd8f0872b92125724e/kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9", size = 1334300 }, + { url = "https://files.pythonhosted.org/packages/8d/26/b4569d1f29751fca22ee915b4ebfef5974f4ef239b3335fc072882bd62d9/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437", size = 1426579 }, + { url = "https://files.pythonhosted.org/packages/f3/a3/804fc7c8bf233806ec0321c9da35971578620f2ab4fafe67d76100b3ce52/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9", size = 1541360 }, + { url = "https://files.pythonhosted.org/packages/07/ef/286e1d26524854f6fbd6540e8364d67a8857d61038ac743e11edc42fe217/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da", size = 1470091 }, + { url = "https://files.pythonhosted.org/packages/17/ba/17a706b232308e65f57deeccae503c268292e6a091313f6ce833a23093ea/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e", size = 1426259 }, + { url = "https://files.pythonhosted.org/packages/d0/f3/a0925611c9d6c2f37c5935a39203cadec6883aa914e013b46c84c4c2e641/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8", size = 1847516 }, + { url = "https://files.pythonhosted.org/packages/da/85/82d59bb8f7c4c9bb2785138b72462cb1b161668f8230c58bbb28c0403cd5/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d", size = 1946228 }, + { url = "https://files.pythonhosted.org/packages/34/3c/6a37f444c0233993881e5db3a6a1775925d4d9d2f2609bb325bb1348ed94/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0", size = 1901716 }, + { url = "https://files.pythonhosted.org/packages/cd/7e/180425790efc00adfd47db14e1e341cb4826516982334129012b971121a6/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f", size = 1852871 }, + { url = "https://files.pythonhosted.org/packages/1b/9a/13c68b2edb1fa74321e60893a9a5829788e135138e68060cf44e2d92d2c3/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f", size = 1870265 }, + { url = "https://files.pythonhosted.org/packages/9f/0a/fa56a0fdee5da2b4c79899c0f6bd1aefb29d9438c2d66430e78793571c6b/kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac", size = 46649 }, + { url = "https://files.pythonhosted.org/packages/1e/37/d3c2d4ba2719059a0f12730947bbe1ad5ee8bff89e8c35319dcb2c9ddb4c/kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355", size = 56116 }, + { url = "https://files.pythonhosted.org/packages/f3/7a/debbce859be1a2711eb8437818107137192007b88d17b5cfdb556f457b42/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a", size = 125484 }, + { url = "https://files.pythonhosted.org/packages/2d/e0/bf8df75ba93b9e035cc6757dd5dcaf63084fdc1c846ae134e818bd7e0f03/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192", size = 67332 }, + { url = "https://files.pythonhosted.org/packages/26/61/58bb691f6880588be3a4801d199bd776032ece07203faf3e4a8b377f7d9b/kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45", size = 64987 }, + { url = "https://files.pythonhosted.org/packages/8e/a3/96ac5413068b237c006f54dd8d70114e8756d70e3da7613c5aef20627e22/kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7", size = 1370613 }, + { url = "https://files.pythonhosted.org/packages/4d/12/f48539e6e17068b59c7f12f4d6214b973431b8e3ac83af525cafd27cebec/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db", size = 1463183 }, + { url = "https://files.pythonhosted.org/packages/f3/70/26c99be8eb034cc8e3f62e0760af1fbdc97a842a7cbc252f7978507d41c2/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff", size = 1581248 }, + { url = "https://files.pythonhosted.org/packages/17/f6/f75f20e543639b09b2de7fc864274a5a9b96cda167a6210a1d9d19306b9d/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228", size = 1508815 }, + { url = "https://files.pythonhosted.org/packages/e3/d5/bc0f22ac108743062ab703f8d6d71c9c7b077b8839fa358700bfb81770b8/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16", size = 1466042 }, + { url = "https://files.pythonhosted.org/packages/75/18/98142500f21d6838bcab49ec919414a1f0c6d049d21ddadf139124db6a70/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9", size = 1885159 }, + { url = "https://files.pythonhosted.org/packages/21/49/a241eff9e0ee013368c1d17957f9d345b0957493c3a43d82ebb558c90b0a/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162", size = 1981694 }, + { url = "https://files.pythonhosted.org/packages/90/90/9490c3de4788123041b1d600d64434f1eed809a2ce9f688075a22166b289/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4", size = 1941579 }, + { url = "https://files.pythonhosted.org/packages/b7/bb/a0cc488ef2aa92d7d304318c8549d3ec8dfe6dd3c2c67a44e3922b77bc4f/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3", size = 1888168 }, + { url = "https://files.pythonhosted.org/packages/4f/e9/9c0de8e45fef3d63f85eed3b1757f9aa511065942866331ef8b99421f433/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a", size = 1908464 }, + { url = "https://files.pythonhosted.org/packages/a3/60/4f0fd50b08f5be536ea0cef518ac7255d9dab43ca40f3b93b60e3ddf80dd/kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20", size = 46473 }, + { url = "https://files.pythonhosted.org/packages/63/50/2746566bdf4a6a842d117367d05c90cfb87ac04e9e2845aa1fa21f071362/kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9", size = 56004 }, +] + +[[package]] +name = "lazy-loader" +version = "0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/3a/1630a735bfdf9eb857a3b9a53317a1e1658ea97a1b4b39dcb0f71dae81f8/lazy_loader-0.3.tar.gz", hash = "sha256:3b68898e34f5b2a29daaaac172c6555512d0f32074f147e2254e4a6d9d838f37", size = 12268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/c3/65b3814e155836acacf720e5be3b5757130346670ac454fee29d3eda1381/lazy_loader-0.3-py3-none-any.whl", hash = "sha256:1e9e76ee8631e264c62ce10006718e80b2cfc74340d17d1031e0f84af7478554", size = 9087 }, +] + +[[package]] +name = "locust" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "configargparse" }, + { name = "flask" }, + { name = "flask-cors" }, + { name = "flask-login" }, + { name = "gevent", marker = "python_full_version != '3.13.*'" }, + { name = "geventhttpclient" }, + { name = "msgpack" }, + { name = "psutil" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "pyzmq" }, + { name = "requests" }, + { name = "setuptools" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/d4/873b1415c8a667982c5f229c6b74abed9fe0ead29ad87d862e5116ea2679/locust-2.33.0.tar.gz", hash = "sha256:ba291b7ab2349cc2db540adb8888bc93feb89ea4e4e10d80b935e5065091e8e9", size = 2237622 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/e5/57b58d9a545fbb2981de59ca25534d5ab4abb6742e6400bb49576ecab5ec/locust-2.33.0-py3-none-any.whl", hash = "sha256:77fcc5cc35cceee5e12d99f5bb23bc441d145bdef6967c2e93d6e4d93451553e", size = 2254520 }, +] + +[[package]] +name = "machine-learning" +version = "1.129.0" +source = { editable = "." } +dependencies = [ + { name = "aiocache" }, + { name = "fastapi" }, + { name = "ftfy" }, + { name = "gunicorn" }, + { name = "huggingface-hub" }, + { name = "insightface" }, + { name = "opencv-python-headless" }, + { name = "orjson" }, + { name = "pillow" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "rich" }, + { name = "tokenizers" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[package.optional-dependencies] +armnn = [ + { name = "onnxruntime" }, +] +cpu = [ + { name = "onnxruntime" }, +] +cuda = [ + { name = "onnxruntime-gpu" }, +] +openvino = [ + { name = "onnxruntime-openvino" }, +] + +[package.dev-dependencies] +dev = [ + { name = "black" }, + { name = "httpx" }, + { name = "locust" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-mock" }, + { name = "ruff" }, + { name = "types-pyyaml" }, + { name = "types-requests" }, + { name = "types-setuptools" }, + { name = "types-simplejson" }, + { name = "types-ujson" }, +] +lint = [ + { name = "black" }, + { name = "mypy" }, + { name = "ruff" }, + { name = "types-pyyaml" }, + { name = "types-requests" }, + { name = "types-setuptools" }, + { name = "types-simplejson" }, + { name = "types-ujson" }, +] +test = [ + { name = "httpx" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-mock" }, +] +types = [ + { name = "types-pyyaml" }, + { name = "types-requests" }, + { name = "types-setuptools" }, + { name = "types-simplejson" }, + { name = "types-ujson" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiocache", specifier = ">=0.12.1,<1.0" }, + { name = "fastapi", specifier = ">=0.95.2,<1.0" }, + { name = "ftfy", specifier = ">=6.1.1" }, + { name = "gunicorn", specifier = ">=21.1.0" }, + { name = "huggingface-hub", specifier = ">=0.20.1,<1.0" }, + { name = "insightface", specifier = ">=0.7.3,<1.0" }, + { name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.15.0,<2" }, + { name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.15.0,<2" }, + { name = "onnxruntime-gpu", marker = "extra == 'cuda'", specifier = ">=1.17.0,<2", index = "https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/" }, + { name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.17.1,<1.19.0" }, + { name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" }, + { name = "orjson", specifier = ">=3.9.5" }, + { name = "pillow", specifier = ">=9.5.0,<11.0" }, + { name = "pydantic", specifier = ">=2.0.0,<3" }, + { name = "pydantic-settings", specifier = ">=2.5.2,<3" }, + { name = "python-multipart", specifier = ">=0.0.6,<1.0" }, + { name = "rich", specifier = ">=13.4.2" }, + { name = "tokenizers", specifier = ">=0.15.0,<1.0" }, + { name = "uvicorn", extras = ["standard"], specifier = ">=0.22.0,<1.0" }, +] +provides-extras = ["cpu", "cuda", "openvino", "armnn"] + +[package.metadata.requires-dev] +dev = [ + { name = "black", specifier = ">=23.3.0" }, + { name = "httpx", specifier = ">=0.24.1" }, + { name = "locust", specifier = ">=2.15.1" }, + { name = "mypy", specifier = ">=1.3.0" }, + { name = "pytest", specifier = ">=7.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.21.0" }, + { name = "pytest-cov", specifier = ">=4.1.0" }, + { name = "pytest-mock", specifier = ">=3.11.1" }, + { name = "ruff", specifier = ">=0.0.272" }, + { name = "types-pyyaml", specifier = ">=6.0.12.20241230" }, + { name = "types-requests", specifier = ">=2.32.0.20250306" }, + { name = "types-setuptools", specifier = ">=75.8.2.20250305" }, + { name = "types-simplejson", specifier = ">=3.20.0.20250218" }, + { name = "types-ujson", specifier = ">=5.10.0.20240515" }, +] +lint = [ + { name = "black", specifier = ">=23.3.0" }, + { name = "mypy", specifier = ">=1.3.0" }, + { name = "ruff", specifier = ">=0.0.272" }, + { name = "types-pyyaml", specifier = ">=6.0.12.20241230" }, + { name = "types-requests", specifier = ">=2.32.0.20250306" }, + { name = "types-setuptools", specifier = ">=75.8.2.20250305" }, + { name = "types-simplejson", specifier = ">=3.20.0.20250218" }, + { name = "types-ujson", specifier = ">=5.10.0.20240515" }, +] +test = [ + { name = "httpx", specifier = ">=0.24.1" }, + { name = "pytest", specifier = ">=7.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.21.0" }, + { name = "pytest-cov", specifier = ">=4.1.0" }, + { name = "pytest-mock", specifier = ">=3.11.1" }, +] +types = [ + { name = "types-pyyaml", specifier = ">=6.0.12.20241230" }, + { name = "types-requests", specifier = ">=2.32.0.20250306" }, + { name = "types-setuptools", specifier = ">=75.8.2.20250305" }, + { name = "types-simplejson", specifier = ">=3.20.0.20250218" }, + { name = "types-ujson", specifier = ">=5.10.0.20240515" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "markupsafe" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/7c/59a3248f411813f8ccba92a55feaac4bf360d29e2ff05ee7d8e1ef2d7dbf/MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", size = 19132 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/1d/713d443799d935f4d26a4f1510c9e61b1d288592fb869845e5cc92a1e055/MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", size = 17846 }, + { url = "https://files.pythonhosted.org/packages/f7/9c/86cbd8e0e1d81f0ba420f20539dd459c50537c7751e28102dbfee2b6f28c/MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", size = 13720 }, + { url = "https://files.pythonhosted.org/packages/a6/56/f1d4ee39e898a9e63470cbb7fae1c58cce6874f25f54220b89213a47f273/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", size = 26498 }, + { url = "https://files.pythonhosted.org/packages/12/b3/d9ed2c0971e1435b8a62354b18d3060b66c8cb1d368399ec0b9baa7c0ee5/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", size = 25691 }, + { url = "https://files.pythonhosted.org/packages/bf/b7/c5ba9b7ad9ad21fc4a60df226615cf43ead185d328b77b0327d603d00cc5/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", size = 25366 }, + { url = "https://files.pythonhosted.org/packages/71/61/f5673d7aac2cf7f203859008bb3fc2b25187aa330067c5e9955e5c5ebbab/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", size = 30505 }, + { url = "https://files.pythonhosted.org/packages/47/26/932140621773bfd4df3223fbdd9e78de3477f424f0d2987c313b1cb655ff/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", size = 29616 }, + { url = "https://files.pythonhosted.org/packages/3c/c8/74d13c999cbb49e3460bf769025659a37ef4a8e884de629720ab4e42dcdb/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", size = 29891 }, + { url = "https://files.pythonhosted.org/packages/96/e4/4db3b1abc5a1fe7295aa0683eafd13832084509c3b8236f3faf8dd4eff75/MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", size = 16525 }, + { url = "https://files.pythonhosted.org/packages/84/a8/c4aebb8a14a1d39d5135eb8233a0b95831cdc42c4088358449c3ed657044/MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", size = 17083 }, + { url = "https://files.pythonhosted.org/packages/fe/09/c31503cb8150cf688c1534a7135cc39bb9092f8e0e6369ec73494d16ee0e/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", size = 17862 }, + { url = "https://files.pythonhosted.org/packages/c0/c7/171f5ac6b065e1425e8fabf4a4dfbeca76fd8070072c6a41bd5c07d90d8b/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", size = 13738 }, + { url = "https://files.pythonhosted.org/packages/a2/f7/9175ad1b8152092f7c3b78c513c1bdfe9287e0564447d1c2d3d1a2471540/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", size = 28891 }, + { url = "https://files.pythonhosted.org/packages/fe/21/2eff1de472ca6c99ec3993eab11308787b9879af9ca8bbceb4868cf4f2ca/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", size = 28096 }, + { url = "https://files.pythonhosted.org/packages/f4/a0/103f94793c3bf829a18d2415117334ece115aeca56f2df1c47fa02c6dbd6/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", size = 27631 }, + { url = "https://files.pythonhosted.org/packages/43/70/f24470f33b2035b035ef0c0ffebf57006beb2272cf3df068fc5154e04ead/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", size = 33863 }, + { url = "https://files.pythonhosted.org/packages/32/d4/ce98c4ca713d91c4a17c1a184785cc00b9e9c25699d618956c2b9999500a/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", size = 32591 }, + { url = "https://files.pythonhosted.org/packages/bb/82/f88ccb3ca6204a4536cf7af5abdad7c3657adac06ab33699aa67279e0744/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", size = 33186 }, + { url = "https://files.pythonhosted.org/packages/44/53/93405d37bb04a10c43b1bdd6f548097478d494d7eadb4b364e3e1337f0cc/MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", size = 16537 }, + { url = "https://files.pythonhosted.org/packages/be/bb/08b85bc194034efbf572e70c3951549c8eca0ada25363afc154386b5390a/MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", size = 17089 }, + { url = "https://files.pythonhosted.org/packages/89/5a/ee546f2aa73a1d6fcfa24272f356fe06d29acca81e76b8d32ca53e429a2e/MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", size = 17849 }, + { url = "https://files.pythonhosted.org/packages/3a/72/9f683a059bde096776e8acf9aa34cbbba21ddc399861fe3953790d4f2cde/MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", size = 13700 }, + { url = "https://files.pythonhosted.org/packages/9d/78/92f15eb9b1e8f1668a9787ba103cf6f8d19a9efed8150245404836145c24/MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11", size = 29319 }, + { url = "https://files.pythonhosted.org/packages/51/94/9a04085114ff2c24f7424dbc890a281d73c5a74ea935dc2e69c66a3bd558/MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", size = 28314 }, + { url = "https://files.pythonhosted.org/packages/ec/53/fcb3214bd370185e223b209ce6bb010fb887ea57173ca4f75bd211b24e10/MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", size = 27696 }, + { url = "https://files.pythonhosted.org/packages/e7/33/54d29854716725d7826079b8984dd235fac76dab1c32321e555d493e61f5/MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", size = 33746 }, + { url = "https://files.pythonhosted.org/packages/11/40/ea7f85e2681d29bc9301c757257de561923924f24de1802d9c3baa396bb4/MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", size = 32131 }, + { url = "https://files.pythonhosted.org/packages/41/f1/bc770c37ecd58638c18f8ec85df205dacb818ccf933692082fd93010a4bc/MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", size = 32878 }, + { url = "https://files.pythonhosted.org/packages/49/74/bf95630aab0a9ed6a67556cd4e54f6aeb0e74f4cb0fd2f229154873a4be4/MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", size = 16426 }, + { url = "https://files.pythonhosted.org/packages/44/44/dbaf65876e258facd65f586dde158387ab89963e7f2235551afc9c2e24c2/MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", size = 16979 }, +] + +[[package]] +name = "matplotlib" +version = "3.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/ab/38a0e94cb01dacb50f06957c2bed1c83b8f9dac6618988a37b2487862944/matplotlib-3.8.2.tar.gz", hash = "sha256:01a978b871b881ee76017152f1f1a0cbf6bd5f7b8ff8c96df0df1bd57d8755a1", size = 35866957 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/d0/fc5f6796a1956f5b9a33555611d01a3cec038f000c3d70ecb051b1631ac4/matplotlib-3.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:09796f89fb71a0c0e1e2f4bdaf63fb2cefc84446bb963ecdeb40dfee7dfa98c7", size = 7590640 }, + { url = "https://files.pythonhosted.org/packages/57/44/007b592809f50883c910db9ec4b81b16dfa0136407250fb581824daabf03/matplotlib-3.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9c6976748a25e8b9be51ea028df49b8e561eed7809146da7a47dbecebab367", size = 7484350 }, + { url = "https://files.pythonhosted.org/packages/01/87/c7b24f3048234fe10184560263be2173311376dc3d1fa329de7f012d6ce5/matplotlib-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78e4f2cedf303869b782071b55fdde5987fda3038e9d09e58c91cc261b5ad18", size = 11382388 }, + { url = "https://files.pythonhosted.org/packages/19/e5/a4ea514515f270224435c69359abb7a3d152ed31b9ee3ba5e63017461945/matplotlib-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e208f46cf6576a7624195aa047cb344a7f802e113bb1a06cfd4bee431de5e31", size = 11611959 }, + { url = "https://files.pythonhosted.org/packages/09/23/ab5a562c9acb81e351b084bea39f65b153918417fb434619cf5a19f44a55/matplotlib-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a569130ff53798ea5f50afce7406e91fdc471ca1e0e26ba976a8c734c9427a", size = 9536938 }, + { url = "https://files.pythonhosted.org/packages/46/37/b5e27ab30ecc0a3694c8a78287b5ef35dad0c3095c144fcc43081170bfd6/matplotlib-3.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:830f00640c965c5b7f6bc32f0d4ce0c36dfe0379f7dd65b07a00c801713ec40a", size = 7643836 }, + { url = "https://files.pythonhosted.org/packages/a9/0d/53afb186adafc7326d093b8333e8a79974c495095771659f4304626c4bc7/matplotlib-3.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d86593ccf546223eb75a39b44c32788e6f6440d13cfc4750c1c15d0fcb850b63", size = 7593458 }, + { url = "https://files.pythonhosted.org/packages/ce/25/a557ee10ac9dce1300850024707ce1850a6958f1673a9194be878b99d631/matplotlib-3.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a5430836811b7652991939012f43d2808a2db9b64ee240387e8c43e2e5578c8", size = 7486840 }, + { url = "https://files.pythonhosted.org/packages/e7/3d/72712b3895ee180f6e342638a8591c31912fbcc09ce9084cc256da16d0a0/matplotlib-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9576723858a78751d5aacd2497b8aef29ffea6d1c95981505877f7ac28215c6", size = 11387332 }, + { url = "https://files.pythonhosted.org/packages/92/1a/cd3e0c90d1a763ad90073e13b189b4702f11becf4e71dbbad70a7a149811/matplotlib-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba9cbd8ac6cf422f3102622b20f8552d601bf8837e49a3afed188d560152788", size = 11616911 }, + { url = "https://files.pythonhosted.org/packages/78/4a/bad239071477305a3758eb4810615e310a113399cddd7682998be9f01e97/matplotlib-3.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:03f9d160a29e0b65c0790bb07f4f45d6a181b1ac33eb1bb0dd225986450148f0", size = 9549260 }, + { url = "https://files.pythonhosted.org/packages/26/5a/27fd341e4510257789f19a4b4be8bb90d1113b8f176c3dab562b4f21466e/matplotlib-3.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:3773002da767f0a9323ba1a9b9b5d00d6257dbd2a93107233167cfb581f64717", size = 7645742 }, + { url = "https://files.pythonhosted.org/packages/e4/1b/864d28d5a72d586ac137f4ca54d5afc8b869720e30d508dbd9adcce4d231/matplotlib-3.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c318c1e95e2f5926fba326f68177dee364aa791d6df022ceb91b8221bd0a627", size = 7590988 }, + { url = "https://files.pythonhosted.org/packages/9a/b0/dd2b60f2dd90fbc21d1d3129c36a453c322d7995d5e3589f5b3c59ee528d/matplotlib-3.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:091275d18d942cf1ee9609c830a1bc36610607d8223b1b981c37d5c9fc3e46a4", size = 7483594 }, + { url = "https://files.pythonhosted.org/packages/33/da/9942533ad9f96753bde0e5a5d48eacd6c21de8ea1ad16570e31bda8a017f/matplotlib-3.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b0f3b8ea0e99e233a4bcc44590f01604840d833c280ebb8fe5554fd3e6cfe8d", size = 11380843 }, + { url = "https://files.pythonhosted.org/packages/fc/52/bfd36eb4745a3b21b3946c2c3a15679b620e14574fe2b98e9451b65ef578/matplotlib-3.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b1704a530395aaf73912be741c04d181f82ca78084fbd80bc737be04848331", size = 11604608 }, + { url = "https://files.pythonhosted.org/packages/6d/8c/0cdfbf604d4ea3dfa77435176c51e233cc408ad8f3efbf8d2c9f57cbdafb/matplotlib-3.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533b0e3b0c6768eef8cbe4b583731ce25a91ab54a22f830db2b031e83cca9213", size = 9545252 }, + { url = "https://files.pythonhosted.org/packages/2e/51/c77a14869b7eb9d6fb440e811b754fc3950d6868c38ace57d0632b674415/matplotlib-3.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:0f4fc5d72b75e2c18e55eb32292659cf731d9d5b312a6eb036506304f4675630", size = 7645067 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, +] + +[[package]] +name = "msgpack" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/d5/5662032db1571110b5b51647aed4b56dfbd01bfae789fa566a2be1f385d1/msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87", size = 166311 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/3a/2e2e902afcd751738e38d88af976fc4010b16e8e821945f4cbf32f75f9c3/msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862", size = 304827 }, + { url = "https://files.pythonhosted.org/packages/86/a6/490792a524a82e855bdf3885ecb73d7b3a0b17744b3cf4a40aea13ceca38/msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329", size = 234959 }, + { url = "https://files.pythonhosted.org/packages/ad/72/d39ed43bfb2ec6968d768318477adb90c474bdc59b2437170c6697ee4115/msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b", size = 231970 }, + { url = "https://files.pythonhosted.org/packages/a2/90/2d769e693654f036acfb462b54dacb3ae345699999897ca34f6bd9534fe9/msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6", size = 522440 }, + { url = "https://files.pythonhosted.org/packages/46/95/d0440400485eab1bf50f1efe5118967b539f3191d994c3dfc220657594cd/msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee", size = 530797 }, + { url = "https://files.pythonhosted.org/packages/76/33/35df717bc095c6e938b3c65ed117b95048abc24d1614427685123fb2f0af/msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d", size = 520372 }, + { url = "https://files.pythonhosted.org/packages/af/d1/abbdd58a43827fbec5d98427a7a535c620890289b9d927154465313d6967/msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d", size = 527287 }, + { url = "https://files.pythonhosted.org/packages/0c/ac/66625b05091b97ca2c7418eb2d2af152f033d969519f9315556a4ed800fe/msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1", size = 560715 }, + { url = "https://files.pythonhosted.org/packages/de/4e/a0e8611f94bac32d2c1c4ad05bb1c0ae61132e3398e0b44a93e6d7830968/msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681", size = 532614 }, + { url = "https://files.pythonhosted.org/packages/9b/07/0b3f089684ca330602b2994248eda2898a7232e4b63882b9271164ef672e/msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9", size = 216340 }, + { url = "https://files.pythonhosted.org/packages/4b/14/c62fbc8dff118f1558e43b9469d56a1f37bbb35febadc3163efaedd01500/msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415", size = 222828 }, + { url = "https://files.pythonhosted.org/packages/f9/b3/309de40dc7406b7f3492332c5ee2b492a593c2a9bb97ea48ebf2f5279999/msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84", size = 305096 }, + { url = "https://files.pythonhosted.org/packages/15/56/a677cd761a2cefb2e3ffe7e684633294dccb161d78e8ea6da9277e45b4a2/msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93", size = 235210 }, + { url = "https://files.pythonhosted.org/packages/f5/4e/1ab4a982cbd90f988e49f849fc1212f2c04a59870c59daabf8950617e2aa/msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8", size = 231952 }, + { url = "https://files.pythonhosted.org/packages/6d/74/bd02044eb628c7361ad2bd8c1a6147af5c6c2bbceb77b3b1da20f4a8a9c5/msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46", size = 549511 }, + { url = "https://files.pythonhosted.org/packages/df/09/dee50913ba5cc047f7fd7162f09453a676e7935c84b3bf3a398e12108677/msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b", size = 557980 }, + { url = "https://files.pythonhosted.org/packages/26/a5/78a7d87f5f8ffe4c32167afa15d4957db649bab4822f909d8d765339bbab/msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e", size = 545547 }, + { url = "https://files.pythonhosted.org/packages/d4/53/698c10913947f97f6fe7faad86a34e6aa1b66cea2df6f99105856bd346d9/msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002", size = 554669 }, + { url = "https://files.pythonhosted.org/packages/f5/3f/9730c6cb574b15d349b80cd8523a7df4b82058528339f952ea1c32ac8a10/msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c", size = 583353 }, + { url = "https://files.pythonhosted.org/packages/4c/bc/dc184d943692671149848438fb3bed3a3de288ce7998cb91bc98f40f201b/msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e", size = 557455 }, + { url = "https://files.pythonhosted.org/packages/cf/7b/1bc69d4a56c8d2f4f2dfbe4722d40344af9a85b6fb3b09cfb350ba6a42f6/msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1", size = 216367 }, + { url = "https://files.pythonhosted.org/packages/b4/3d/c8dd23050eefa3d9b9c5b8329ed3308c2f2f80f65825e9ea4b7fa621cdab/msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82", size = 222860 }, + { url = "https://files.pythonhosted.org/packages/d7/47/20dff6b4512cf3575550c8801bc53fe7d540f4efef9c5c37af51760fcdcf/msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b", size = 305759 }, + { url = "https://files.pythonhosted.org/packages/6f/8a/34f1726d2c9feccec3d946776e9bce8f20ae09d8b91899fc20b296c942af/msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4", size = 235330 }, + { url = "https://files.pythonhosted.org/packages/9c/f6/e64c72577d6953789c3cb051b059a4b56317056b3c65013952338ed8a34e/msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee", size = 232537 }, + { url = "https://files.pythonhosted.org/packages/89/75/1ed3a96e12941873fd957e016cc40c0c178861a872bd45e75b9a188eb422/msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5", size = 546561 }, + { url = "https://files.pythonhosted.org/packages/e5/0a/c6a1390f9c6a31da0fecbbfdb86b1cb39ad302d9e24f9cca3d9e14c364f0/msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672", size = 559009 }, + { url = "https://files.pythonhosted.org/packages/a5/74/99f6077754665613ea1f37b3d91c10129f6976b7721ab4d0973023808e5a/msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075", size = 543882 }, + { url = "https://files.pythonhosted.org/packages/9c/7e/dc0dc8de2bf27743b31691149258f9b1bd4bf3c44c105df3df9b97081cd1/msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba", size = 546949 }, + { url = "https://files.pythonhosted.org/packages/78/61/91bae9474def032f6c333d62889bbeda9e1554c6b123375ceeb1767efd78/msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c", size = 579836 }, + { url = "https://files.pythonhosted.org/packages/5d/4d/d98592099d4f18945f89cf3e634dc0cb128bb33b1b93f85a84173d35e181/msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5", size = 556587 }, + { url = "https://files.pythonhosted.org/packages/5e/44/6556ffe169bf2c0e974e2ea25fb82a7e55ebcf52a81b03a5e01820de5f84/msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9", size = 216509 }, + { url = "https://files.pythonhosted.org/packages/dc/c1/63903f30d51d165e132e5221a2a4a1bbfab7508b68131c871d70bffac78a/msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf", size = 223287 }, +] + +[[package]] +name = "mypy" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/f8/65a7ce8d0e09b6329ad0c8d40330d100ea343bd4dd04c4f8ae26462d0a17/mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13", size = 10738433 }, + { url = "https://files.pythonhosted.org/packages/b4/95/9c0ecb8eacfe048583706249439ff52105b3f552ea9c4024166c03224270/mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559", size = 9861472 }, + { url = "https://files.pythonhosted.org/packages/84/09/9ec95e982e282e20c0d5407bc65031dfd0f0f8ecc66b69538296e06fcbee/mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b", size = 11611424 }, + { url = "https://files.pythonhosted.org/packages/78/13/f7d14e55865036a1e6a0a69580c240f43bc1f37407fe9235c0d4ef25ffb0/mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3", size = 12365450 }, + { url = "https://files.pythonhosted.org/packages/48/e1/301a73852d40c241e915ac6d7bcd7fedd47d519246db2d7b86b9d7e7a0cb/mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b", size = 12551765 }, + { url = "https://files.pythonhosted.org/packages/77/ba/c37bc323ae5fe7f3f15a28e06ab012cd0b7552886118943e90b15af31195/mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828", size = 9274701 }, + { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338 }, + { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540 }, + { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051 }, + { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751 }, + { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783 }, + { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618 }, + { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981 }, + { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175 }, + { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675 }, + { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020 }, + { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582 }, + { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614 }, + { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592 }, + { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611 }, + { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443 }, + { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541 }, + { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348 }, + { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648 }, + { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "networkx" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/80/a84676339aaae2f1cfdf9f418701dd634aef9cc76f708ef55c36ff39c3ca/networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6", size = 2073928 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/f0/8fbc882ca80cf077f1b246c0e3c3465f7f415439bdea6b899f6b19f61f70/networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2", size = 1647772 }, +] + +[[package]] +name = "numpy" +version = "1.26.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468 }, + { url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411 }, + { url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016 }, + { url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889 }, + { url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746 }, + { url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620 }, + { url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659 }, + { url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905 }, + { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554 }, + { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127 }, + { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994 }, + { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005 }, + { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297 }, + { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567 }, + { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812 }, + { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913 }, + { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901 }, + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868 }, + { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109 }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613 }, + { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172 }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643 }, + { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803 }, + { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754 }, +] + +[[package]] +name = "onnx" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fe/0978403c8d710ece2f34006367e78de80410743fe0e7680c8f33f2dab20d/onnx-1.16.0.tar.gz", hash = "sha256:237c6987c6c59d9f44b6136f5819af79574f8d96a760a1fa843bede11f3822f7", size = 12303017 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/0b/f4705e4a3fa6fd0de971302fdae17ad176b024eca8c24360f0e37c00f9df/onnx-1.16.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:9eadbdce25b19d6216f426d6d99b8bc877a65ed92cbef9707751c6669190ba4f", size = 16514483 }, + { url = "https://files.pythonhosted.org/packages/b8/1c/50310a559857951fc6e069cf5d89deebe34287997d1c5928bca435456f62/onnx-1.16.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:034ae21a2aaa2e9c14119a840d2926d213c27aad29e5e3edaa30145a745048e1", size = 15012939 }, + { url = "https://files.pythonhosted.org/packages/ef/6e/96be6692ebcd8da568084d753f386ce08efa1f99b216f346ee281edd6cc3/onnx-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec22a43d74eb1f2303373e2fbe7fbcaa45fb225f4eb146edfed1356ada7a9aea", size = 15791856 }, + { url = "https://files.pythonhosted.org/packages/49/5f/d8e1a24247f506a77cbe22341c72ca91bea3b468c5d6bca2047d885ea3c6/onnx-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:298f28a2b5ac09145fa958513d3d1e6b349ccf86a877dbdcccad57713fe360b3", size = 15922279 }, + { url = "https://files.pythonhosted.org/packages/cb/14/562e4ac22cdf41f4465e3b114ef1a9467d513eeff0b9c2285c2da5db6ed1/onnx-1.16.0-cp310-cp310-win32.whl", hash = "sha256:66300197b52beca08bc6262d43c103289c5d45fde43fb51922ed1eb83658cf0c", size = 14335703 }, + { url = "https://files.pythonhosted.org/packages/3b/e2/471ff83b3862967791d67f630000afce038756afbdf0665a3d767677c851/onnx-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:ae0029f5e47bf70a1a62e7f88c80bca4ef39b844a89910039184221775df5e43", size = 14435099 }, + { url = "https://files.pythonhosted.org/packages/a4/b8/7accf3f93eee498711f0b7f07f6e93906e031622473e85ce9cd3578f6a92/onnx-1.16.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:f51179d4af3372b4f3800c558d204b592c61e4b4a18b8f61e0eea7f46211221a", size = 16514376 }, + { url = "https://files.pythonhosted.org/packages/cc/24/a328236b594d5fea23f70a3a8139e730cb43334f0b24693831c47c9064f0/onnx-1.16.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:5202559070afec5144332db216c20f2fff8323cf7f6512b0ca11b215eacc5bf3", size = 15012839 }, + { url = "https://files.pythonhosted.org/packages/80/12/57187bab3f830a47fa65eafe4fbaef01dfdf5042cf82a41fa440fab68766/onnx-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77579e7c15b4df39d29465b216639a5f9b74026bdd9e4b6306cd19a32dcfe67c", size = 15791944 }, + { url = "https://files.pythonhosted.org/packages/df/48/63f68b65d041aedffab41eea930563ca52aab70dbaa7d4820501618c1a70/onnx-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e60ca76ac24b65c25860d0f2d2cdd96d6320d062a01dd8ce87c5743603789b8", size = 15922450 }, + { url = "https://files.pythonhosted.org/packages/08/1b/4bdf4534f5ff08973725ba5409f95bbf64e2789cd20be615880dae689973/onnx-1.16.0-cp311-cp311-win32.whl", hash = "sha256:81b4ee01bc554e8a2b11ac6439882508a5377a1c6b452acd69a1eebb83571117", size = 14335808 }, + { url = "https://files.pythonhosted.org/packages/aa/d0/0514d02d2e84e7bb48a105877eae4065e54d7dabb60d0b60214fe2677346/onnx-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:7449241e70b847b9c3eb8dae622df8c1b456d11032a9d7e26e0ee8a698d5bf86", size = 14434905 }, + { url = "https://files.pythonhosted.org/packages/42/87/577adadda30ee08041e81ef02a331ca9d1a8df93a2e4c4c53ec56fbbc2ac/onnx-1.16.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:03a627488b1a9975d95d6a55582af3e14c7f3bb87444725b999935ddd271d352", size = 16516304 }, + { url = "https://files.pythonhosted.org/packages/e3/1b/6e1ea37e081cc49a28f0e4d3830b4c8525081354cf9f5529c6c92268fc77/onnx-1.16.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c392faeabd9283ee344ccb4b067d1fea9dfc614fa1f0de7c47589efd79e15e78", size = 15016538 }, + { url = "https://files.pythonhosted.org/packages/6d/07/f8fefd5eb0984be42ef677f0b7db7527edc4529224a34a3c31f7b12ec80d/onnx-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0efeb46985de08f0efe758cb54ad3457e821a05c2eaf5ba2ccb8cd1602c08084", size = 15790415 }, + { url = "https://files.pythonhosted.org/packages/11/71/c219ce6d4b5205c77405af7f2de2511ad4eeffbfeb77a422151e893de0ea/onnx-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddf14a3d32234f23e44abb73a755cb96a423fac7f004e8f046f36b10214151ee", size = 15922224 }, + { url = "https://files.pythonhosted.org/packages/8e/a4/554a6e5741b42406c5b1970d04685d7f2012019d4178408ed4b3ec953033/onnx-1.16.0-cp312-cp312-win32.whl", hash = "sha256:62a2e27ae8ba5fc9b4a2620301446a517b5ffaaf8566611de7a7c2160f5bcf4c", size = 14336234 }, + { url = "https://files.pythonhosted.org/packages/e9/a1/8aecec497010ad34e7656408df1868d94483c5c56bc991f4088c06150896/onnx-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:3e0860fea94efde777e81a6f68f65761ed5e5f3adea2e050d7fbe373a9ae05b3", size = 14436591 }, +] + +[[package]] +name = "onnxruntime" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coloredlogs" }, + { name = "flatbuffers" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "sympy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/28/99f903b0eb1cd6f3faa0e343217d9fb9f47b84bca98bd9859884631336ee/onnxruntime-1.20.1-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:e50ba5ff7fed4f7d9253a6baf801ca2883cc08491f9d32d78a80da57256a5439", size = 30996314 }, + { url = "https://files.pythonhosted.org/packages/6d/c6/c4c0860bee2fde6037bdd9dcd12d323f6e38cf00fcc9a5065b394337fc55/onnxruntime-1.20.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b2908b50101a19e99c4d4e97ebb9905561daf61829403061c1adc1b588bc0de", size = 11954010 }, + { url = "https://files.pythonhosted.org/packages/63/47/3dc0b075ab539f16b3d8b09df6b504f51836086ee709690a6278d791737d/onnxruntime-1.20.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d82daaec24045a2e87598b8ac2b417b1cce623244e80e663882e9fe1aae86410", size = 13330452 }, + { url = "https://files.pythonhosted.org/packages/27/ef/80fab86289ecc01a734b7ddf115dfb93d8b2e004bd1e1977e12881c72b12/onnxruntime-1.20.1-cp310-cp310-win32.whl", hash = "sha256:4c4b251a725a3b8cf2aab284f7d940c26094ecd9d442f07dd81ab5470e99b83f", size = 9813849 }, + { url = "https://files.pythonhosted.org/packages/a9/e6/33ab10066c9875a29d55e66ae97c3bf91b9b9b987179455d67c32261a49c/onnxruntime-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:d3b616bb53a77a9463707bb313637223380fc327f5064c9a782e8ec69c22e6a2", size = 11329702 }, + { url = "https://files.pythonhosted.org/packages/95/8d/2634e2959b34aa8a0037989f4229e9abcfa484e9c228f99633b3241768a6/onnxruntime-1.20.1-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:06bfbf02ca9ab5f28946e0f912a562a5f005301d0c419283dc57b3ed7969bb7b", size = 30998725 }, + { url = "https://files.pythonhosted.org/packages/a5/da/c44bf9bd66cd6d9018a921f053f28d819445c4d84b4dd4777271b0fe52a2/onnxruntime-1.20.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6243e34d74423bdd1edf0ae9596dd61023b260f546ee17d701723915f06a9f7", size = 11955227 }, + { url = "https://files.pythonhosted.org/packages/11/ac/4120dfb74c8e45cce1c664fc7f7ce010edd587ba67ac41489f7432eb9381/onnxruntime-1.20.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5eec64c0269dcdb8d9a9a53dc4d64f87b9e0c19801d9321246a53b7eb5a7d1bc", size = 13331703 }, + { url = "https://files.pythonhosted.org/packages/12/f1/cefacac137f7bb7bfba57c50c478150fcd3c54aca72762ac2c05ce0532c1/onnxruntime-1.20.1-cp311-cp311-win32.whl", hash = "sha256:a19bc6e8c70e2485a1725b3d517a2319603acc14c1f1a017dda0afe6d4665b41", size = 9813977 }, + { url = "https://files.pythonhosted.org/packages/2c/2d/2d4d202c0bcfb3a4cc2b171abb9328672d7f91d7af9ea52572722c6d8d96/onnxruntime-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:8508887eb1c5f9537a4071768723ec7c30c28eb2518a00d0adcd32c89dea3221", size = 11329895 }, + { url = "https://files.pythonhosted.org/packages/e5/39/9335e0874f68f7d27103cbffc0e235e32e26759202df6085716375c078bb/onnxruntime-1.20.1-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:22b0655e2bf4f2161d52706e31f517a0e54939dc393e92577df51808a7edc8c9", size = 31007580 }, + { url = "https://files.pythonhosted.org/packages/c5/9d/a42a84e10f1744dd27c6f2f9280cc3fb98f869dd19b7cd042e391ee2ab61/onnxruntime-1.20.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f56e898815963d6dc4ee1c35fc6c36506466eff6d16f3cb9848cea4e8c8172", size = 11952833 }, + { url = "https://files.pythonhosted.org/packages/47/42/2f71f5680834688a9c81becbe5c5bb996fd33eaed5c66ae0606c3b1d6a02/onnxruntime-1.20.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb71a814f66517a65628c9e4a2bb530a6edd2cd5d87ffa0af0f6f773a027d99e", size = 13333903 }, + { url = "https://files.pythonhosted.org/packages/c8/f1/aabfdf91d013320aa2fc46cf43c88ca0182860ff15df872b4552254a9680/onnxruntime-1.20.1-cp312-cp312-win32.whl", hash = "sha256:bd386cc9ee5f686ee8a75ba74037750aca55183085bf1941da8efcfe12d5b120", size = 9814562 }, + { url = "https://files.pythonhosted.org/packages/dd/80/76979e0b744307d488c79e41051117634b956612cc731f1028eb17ee7294/onnxruntime-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:19c2d843eb074f385e8bbb753a40df780511061a63f9def1b216bf53860223fb", size = 11331482 }, + { url = "https://files.pythonhosted.org/packages/f7/71/c5d980ac4189589267a06f758bd6c5667d07e55656bed6c6c0580733ad07/onnxruntime-1.20.1-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:cc01437a32d0042b606f462245c8bbae269e5442797f6213e36ce61d5abdd8cc", size = 31007574 }, + { url = "https://files.pythonhosted.org/packages/81/0d/13bbd9489be2a6944f4a940084bfe388f1100472f38c07080a46fbd4ab96/onnxruntime-1.20.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb44b08e017a648924dbe91b82d89b0c105b1adcfe31e90d1dc06b8677ad37be", size = 11951459 }, + { url = "https://files.pythonhosted.org/packages/c0/ea/4454ae122874fd52bbb8a961262de81c5f932edeb1b72217f594c700d6ef/onnxruntime-1.20.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bda6aebdf7917c1d811f21d41633df00c58aff2bef2f598f69289c1f1dabc4b3", size = 13331620 }, + { url = "https://files.pythonhosted.org/packages/d8/e0/50db43188ca1c945decaa8fc2a024c33446d31afed40149897d4f9de505f/onnxruntime-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:d30367df7e70f1d9fc5a6a68106f5961686d39b54d3221f760085524e8d38e16", size = 11331758 }, + { url = "https://files.pythonhosted.org/packages/d8/55/3821c5fd60b52a6c82a00bba18531793c93c4addfe64fbf061e235c5617a/onnxruntime-1.20.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9158465745423b2b5d97ed25aa7740c7d38d2993ee2e5c3bfacb0c4145c49d8", size = 11950342 }, + { url = "https://files.pythonhosted.org/packages/14/56/fd990ca222cef4f9f4a9400567b9a15b220dee2eafffb16b2adbc55c8281/onnxruntime-1.20.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0df6f2df83d61f46e842dbcde610ede27218947c33e994545a22333491e72a3b", size = 13337040 }, +] + +[[package]] +name = "onnxruntime-gpu" +version = "1.19.2" +source = { registry = "https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/" } +dependencies = [ + { name = "coloredlogs" }, + { name = "flatbuffers" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "sympy" }, +] +wheels = [ + { url = "https://aiinfra.pkgs.visualstudio.com/2692857e-05ef-43b4-ba9c-ccf1c22c437c/_packaging/9387c3aa-d9ad-4513-968c-383f6f7f53b8/pypi/download/onnxruntime-gpu/1.19.2/onnxruntime_gpu-1.19.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a49740e079e7c5215830d30cde3df792e903df007aa0b0fd7aa797937061b27a" }, + { url = "https://aiinfra.pkgs.visualstudio.com/2692857e-05ef-43b4-ba9c-ccf1c22c437c/_packaging/9387c3aa-d9ad-4513-968c-383f6f7f53b8/pypi/download/onnxruntime-gpu/1.19.2/onnxruntime_gpu-1.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:b895920bb5e4241299f68874e0becdc2635ea0142939c11e7ff5ae5b28993613" }, + { url = "https://aiinfra.pkgs.visualstudio.com/2692857e-05ef-43b4-ba9c-ccf1c22c437c/_packaging/9387c3aa-d9ad-4513-968c-383f6f7f53b8/pypi/download/onnxruntime-gpu/1.19.2/onnxruntime_gpu-1.19.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:562fc7c755393eaad9751e56149339dd201ffbfdb3ef5f43ff21d0619ba9045f" }, + { url = "https://aiinfra.pkgs.visualstudio.com/2692857e-05ef-43b4-ba9c-ccf1c22c437c/_packaging/9387c3aa-d9ad-4513-968c-383f6f7f53b8/pypi/download/onnxruntime-gpu/1.19.2/onnxruntime_gpu-1.19.2-cp311-cp311-win_amd64.whl", hash = "sha256:522f7495918176cb8c1a3c78bde7152d984f7096acc786c73a27643af8af87c9" }, + { url = "https://aiinfra.pkgs.visualstudio.com/2692857e-05ef-43b4-ba9c-ccf1c22c437c/_packaging/9387c3aa-d9ad-4513-968c-383f6f7f53b8/pypi/download/onnxruntime-gpu/1.19.2/onnxruntime_gpu-1.19.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:554a02a3fac0119707eb87327908afd21c4e6f0fa5bf9a034398f098adc316c5" }, + { url = "https://aiinfra.pkgs.visualstudio.com/2692857e-05ef-43b4-ba9c-ccf1c22c437c/_packaging/9387c3aa-d9ad-4513-968c-383f6f7f53b8/pypi/download/onnxruntime-gpu/1.19.2/onnxruntime_gpu-1.19.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c6165a405027e3c0f11d189ae7013b5d66919b3381f9bfb3405c0c0cf07968" }, +] + +[[package]] +name = "onnxruntime-openvino" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coloredlogs" }, + { name = "flatbuffers" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "sympy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/57/e9a080f2477b2a4c16925f766e4615fc545098b0f4e20cf8ad803e7a9672/onnxruntime_openvino-1.18.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:565b874d21bcd48126da7d62f57db019f5ec0e1f82ae9b0740afa2ad91f8d331", size = 41971800 }, + { url = "https://files.pythonhosted.org/packages/34/7d/b75913bce58f4ee9bf6a02d1b513b9fc82303a496ec698e6fb1f9d597cb4/onnxruntime_openvino-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:7f1931060f710a6c8e32121bb73044c4772ef5925802fc8776d3fe1e87ab3f75", size = 5963263 }, + { url = "https://files.pythonhosted.org/packages/7e/d3/8299b7285dc8fa7bd986b6f0d7c50b7f0fd13db50dd3b88b93ec269b1e08/onnxruntime_openvino-1.18.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb1723d386f70a8e26398d983ebe35d2c25ba56e9cdb382670ebbf1f5139f8ba", size = 41971927 }, + { url = "https://files.pythonhosted.org/packages/88/d9/ca0bfd7ed37153d9664ccdcfb4d0e5b1963563553b05cb4338b46968feb2/onnxruntime_openvino-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:874a1e263dd86674593e5a879257650b06a8609c4d5768c3d8ed8dc4ae874b9c", size = 5963464 }, +] + +[[package]] +name = "opencv-python-headless" +version = "4.11.0.86" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/2f/5b2b3ba52c864848885ba988f24b7f105052f68da9ab0e693cc7c25b0b30/opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798", size = 95177929 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/53/2c50afa0b1e05ecdb4603818e85f7d174e683d874ef63a6abe3ac92220c8/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:48128188ade4a7e517237c8e1e11a9cdf5c282761473383e77beb875bb1e61ca", size = 37326460 }, + { url = "https://files.pythonhosted.org/packages/3b/43/68555327df94bb9b59a1fd645f63fafb0762515344d2046698762fc19d58/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:a66c1b286a9de872c343ee7c3553b084244299714ebb50fbdcd76f07ebbe6c81", size = 56723330 }, + { url = "https://files.pythonhosted.org/packages/45/be/1438ce43ebe65317344a87e4b150865c5585f4c0db880a34cdae5ac46881/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6efabcaa9df731f29e5ea9051776715b1bdd1845d7c9530065c7951d2a2899eb", size = 29487060 }, + { url = "https://files.pythonhosted.org/packages/dd/5c/c139a7876099916879609372bfa513b7f1257f7f1a908b0bdc1c2328241b/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e0a27c19dd1f40ddff94976cfe43066fbbe9dfbb2ec1907d66c19caef42a57b", size = 49969856 }, + { url = "https://files.pythonhosted.org/packages/95/dd/ed1191c9dc91abcc9f752b499b7928aacabf10567bb2c2535944d848af18/opencv_python_headless-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:f447d8acbb0b6f2808da71fddd29c1cdd448d2bc98f72d9bb78a7a898fc9621b", size = 29324425 }, + { url = "https://files.pythonhosted.org/packages/86/8a/69176a64335aed183529207ba8bc3d329c2999d852b4f3818027203f50e6/opencv_python_headless-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca", size = 39402386 }, +] + +[[package]] +name = "orjson" +version = "3.10.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/5dea21763eeff8c1590076918a446ea3d6140743e0e36f58f369928ed0f4/orjson-3.10.15.tar.gz", hash = "sha256:05ca7fe452a2e9d8d9d706a2984c95b9c2ebc5db417ce0b7a49b91d50642a23e", size = 5282482 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/09/e5ff18ad009e6f97eb7edc5f67ef98b3ce0c189da9c3eaca1f9587cd4c61/orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04", size = 249532 }, + { url = "https://files.pythonhosted.org/packages/bd/b8/a75883301fe332bd433d9b0ded7d2bb706ccac679602c3516984f8814fb5/orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8", size = 125229 }, + { url = "https://files.pythonhosted.org/packages/83/4b/22f053e7a364cc9c685be203b1e40fc5f2b3f164a9b2284547504eec682e/orjson-3.10.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c2c79fa308e6edb0ffab0a31fd75a7841bf2a79a20ef08a3c6e3b26814c8ca8", size = 150148 }, + { url = "https://files.pythonhosted.org/packages/63/64/1b54fc75ca328b57dd810541a4035fe48c12a161d466e3cf5b11a8c25649/orjson-3.10.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cb85490aa6bf98abd20607ab5c8324c0acb48d6da7863a51be48505646c814", size = 139748 }, + { url = "https://files.pythonhosted.org/packages/5e/ff/ff0c5da781807bb0a5acd789d9a7fbcb57f7b0c6e1916595da1f5ce69f3c/orjson-3.10.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763dadac05e4e9d2bc14938a45a2d0560549561287d41c465d3c58aec818b164", size = 154559 }, + { url = "https://files.pythonhosted.org/packages/4e/9a/11e2974383384ace8495810d4a2ebef5f55aacfc97b333b65e789c9d362d/orjson-3.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a330b9b4734f09a623f74a7490db713695e13b67c959713b78369f26b3dee6bf", size = 130349 }, + { url = "https://files.pythonhosted.org/packages/2d/c4/dd9583aea6aefee1b64d3aed13f51d2aadb014028bc929fe52936ec5091f/orjson-3.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a61a4622b7ff861f019974f73d8165be1bd9a0855e1cad18ee167acacabeb061", size = 138514 }, + { url = "https://files.pythonhosted.org/packages/53/3e/dcf1729230654f5c5594fc752de1f43dcf67e055ac0d300c8cdb1309269a/orjson-3.10.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd271247691574416b3228db667b84775c497b245fa275c6ab90dc1ffbbd2b3", size = 130940 }, + { url = "https://files.pythonhosted.org/packages/e8/2b/b9759fe704789937705c8a56a03f6c03e50dff7df87d65cba9a20fec5282/orjson-3.10.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4759b109c37f635aa5c5cc93a1b26927bfde24b254bcc0e1149a9fada253d2d", size = 414713 }, + { url = "https://files.pythonhosted.org/packages/a7/6b/b9dfdbd4b6e20a59238319eb203ae07c3f6abf07eef909169b7a37ae3bba/orjson-3.10.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e992fd5cfb8b9f00bfad2fd7a05a4299db2bbe92e6440d9dd2fab27655b3182", size = 141028 }, + { url = "https://files.pythonhosted.org/packages/7c/b5/40f5bbea619c7caf75eb4d652a9821875a8ed04acc45fe3d3ef054ca69fb/orjson-3.10.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f95fb363d79366af56c3f26b71df40b9a583b07bbaaf5b317407c4d58497852e", size = 129715 }, + { url = "https://files.pythonhosted.org/packages/38/60/2272514061cbdf4d672edbca6e59c7e01cd1c706e881427d88f3c3e79761/orjson-3.10.15-cp310-cp310-win32.whl", hash = "sha256:f9875f5fea7492da8ec2444839dcc439b0ef298978f311103d0b7dfd775898ab", size = 142473 }, + { url = "https://files.pythonhosted.org/packages/11/5d/be1490ff7eafe7fef890eb4527cf5bcd8cfd6117f3efe42a3249ec847b60/orjson-3.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:17085a6aa91e1cd70ca8533989a18b5433e15d29c574582f76f821737c8d5806", size = 133564 }, + { url = "https://files.pythonhosted.org/packages/7a/a2/21b25ce4a2c71dbb90948ee81bd7a42b4fbfc63162e57faf83157d5540ae/orjson-3.10.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c4cc83960ab79a4031f3119cc4b1a1c627a3dc09df125b27c4201dff2af7eaa6", size = 249533 }, + { url = "https://files.pythonhosted.org/packages/b2/85/2076fc12d8225698a51278009726750c9c65c846eda741e77e1761cfef33/orjson-3.10.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddbeef2481d895ab8be5185f2432c334d6dec1f5d1933a9c83014d188e102cef", size = 125230 }, + { url = "https://files.pythonhosted.org/packages/06/df/a85a7955f11274191eccf559e8481b2be74a7c6d43075d0a9506aa80284d/orjson-3.10.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e590a0477b23ecd5b0ac865b1b907b01b3c5535f5e8a8f6ab0e503efb896334", size = 150148 }, + { url = "https://files.pythonhosted.org/packages/37/b3/94c55625a29b8767c0eed194cb000b3787e3c23b4cdd13be17bae6ccbb4b/orjson-3.10.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6be38bd103d2fd9bdfa31c2720b23b5d47c6796bcb1d1b598e3924441b4298d", size = 139749 }, + { url = "https://files.pythonhosted.org/packages/53/ba/c608b1e719971e8ddac2379f290404c2e914cf8e976369bae3cad88768b1/orjson-3.10.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff4f6edb1578960ed628a3b998fa54d78d9bb3e2eb2cfc5c2a09732431c678d0", size = 154558 }, + { url = "https://files.pythonhosted.org/packages/b2/c4/c1fb835bb23ad788a39aa9ebb8821d51b1c03588d9a9e4ca7de5b354fdd5/orjson-3.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0482b21d0462eddd67e7fce10b89e0b6ac56570424662b685a0d6fccf581e13", size = 130349 }, + { url = "https://files.pythonhosted.org/packages/78/14/bb2b48b26ab3c570b284eb2157d98c1ef331a8397f6c8bd983b270467f5c/orjson-3.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bb5cc3527036ae3d98b65e37b7986a918955f85332c1ee07f9d3f82f3a6899b5", size = 138513 }, + { url = "https://files.pythonhosted.org/packages/4a/97/d5b353a5fe532e92c46467aa37e637f81af8468aa894cd77d2ec8a12f99e/orjson-3.10.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d569c1c462912acdd119ccbf719cf7102ea2c67dd03b99edcb1a3048651ac96b", size = 130942 }, + { url = "https://files.pythonhosted.org/packages/b5/5d/a067bec55293cca48fea8b9928cfa84c623be0cce8141d47690e64a6ca12/orjson-3.10.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1e6d33efab6b71d67f22bf2962895d3dc6f82a6273a965fab762e64fa90dc399", size = 414717 }, + { url = "https://files.pythonhosted.org/packages/6f/9a/1485b8b05c6b4c4db172c438cf5db5dcfd10e72a9bc23c151a1137e763e0/orjson-3.10.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c33be3795e299f565681d69852ac8c1bc5c84863c0b0030b2b3468843be90388", size = 141033 }, + { url = "https://files.pythonhosted.org/packages/f8/d2/fc67523656e43a0c7eaeae9007c8b02e86076b15d591e9be11554d3d3138/orjson-3.10.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eea80037b9fae5339b214f59308ef0589fc06dc870578b7cce6d71eb2096764c", size = 129720 }, + { url = "https://files.pythonhosted.org/packages/79/42/f58c7bd4e5b54da2ce2ef0331a39ccbbaa7699b7f70206fbf06737c9ed7d/orjson-3.10.15-cp311-cp311-win32.whl", hash = "sha256:d5ac11b659fd798228a7adba3e37c010e0152b78b1982897020a8e019a94882e", size = 142473 }, + { url = "https://files.pythonhosted.org/packages/00/f8/bb60a4644287a544ec81df1699d5b965776bc9848d9029d9f9b3402ac8bb/orjson-3.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:cf45e0214c593660339ef63e875f32ddd5aa3b4adc15e662cdb80dc49e194f8e", size = 133570 }, + { url = "https://files.pythonhosted.org/packages/66/85/22fe737188905a71afcc4bf7cc4c79cd7f5bbe9ed1fe0aac4ce4c33edc30/orjson-3.10.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d11c0714fc85bfcf36ada1179400862da3288fc785c30e8297844c867d7505a", size = 249504 }, + { url = "https://files.pythonhosted.org/packages/48/b7/2622b29f3afebe938a0a9037e184660379797d5fd5234e5998345d7a5b43/orjson-3.10.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba5a1e85d554e3897fa9fe6fbcff2ed32d55008973ec9a2b992bd9a65d2352d", size = 125080 }, + { url = "https://files.pythonhosted.org/packages/ce/8f/0b72a48f4403d0b88b2a41450c535b3e8989e8a2d7800659a967efc7c115/orjson-3.10.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7723ad949a0ea502df656948ddd8b392780a5beaa4c3b5f97e525191b102fff0", size = 150121 }, + { url = "https://files.pythonhosted.org/packages/06/ec/acb1a20cd49edb2000be5a0404cd43e3c8aad219f376ac8c60b870518c03/orjson-3.10.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fd9bc64421e9fe9bd88039e7ce8e58d4fead67ca88e3a4014b143cec7684fd4", size = 139796 }, + { url = "https://files.pythonhosted.org/packages/33/e1/f7840a2ea852114b23a52a1c0b2bea0a1ea22236efbcdb876402d799c423/orjson-3.10.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dadba0e7b6594216c214ef7894c4bd5f08d7c0135f4dd0145600be4fbcc16767", size = 154636 }, + { url = "https://files.pythonhosted.org/packages/fa/da/31543337febd043b8fa80a3b67de627669b88c7b128d9ad4cc2ece005b7a/orjson-3.10.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48f59114fe318f33bbaee8ebeda696d8ccc94c9e90bc27dbe72153094e26f41", size = 130621 }, + { url = "https://files.pythonhosted.org/packages/ed/78/66115dc9afbc22496530d2139f2f4455698be444c7c2475cb48f657cefc9/orjson-3.10.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:035fb83585e0f15e076759b6fedaf0abb460d1765b6a36f48018a52858443514", size = 138516 }, + { url = "https://files.pythonhosted.org/packages/22/84/cd4f5fb5427ffcf823140957a47503076184cb1ce15bcc1165125c26c46c/orjson-3.10.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d13b7fe322d75bf84464b075eafd8e7dd9eae05649aa2a5354cfa32f43c59f17", size = 130762 }, + { url = "https://files.pythonhosted.org/packages/93/1f/67596b711ba9f56dd75d73b60089c5c92057f1130bb3a25a0f53fb9a583b/orjson-3.10.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7066b74f9f259849629e0d04db6609db4cf5b973248f455ba5d3bd58a4daaa5b", size = 414700 }, + { url = "https://files.pythonhosted.org/packages/7c/0c/6a3b3271b46443d90efb713c3e4fe83fa8cd71cda0d11a0f69a03f437c6e/orjson-3.10.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88dc3f65a026bd3175eb157fea994fca6ac7c4c8579fc5a86fc2114ad05705b7", size = 141077 }, + { url = "https://files.pythonhosted.org/packages/3b/9b/33c58e0bfc788995eccd0d525ecd6b84b40d7ed182dd0751cd4c1322ac62/orjson-3.10.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b342567e5465bd99faa559507fe45e33fc76b9fb868a63f1642c6bc0735ad02a", size = 129898 }, + { url = "https://files.pythonhosted.org/packages/01/c1/d577ecd2e9fa393366a1ea0a9267f6510d86e6c4bb1cdfb9877104cac44c/orjson-3.10.15-cp312-cp312-win32.whl", hash = "sha256:0a4f27ea5617828e6b58922fdbec67b0aa4bb844e2d363b9244c47fa2180e665", size = 142566 }, + { url = "https://files.pythonhosted.org/packages/ed/eb/a85317ee1732d1034b92d56f89f1de4d7bf7904f5c8fb9dcdd5b1c83917f/orjson-3.10.15-cp312-cp312-win_amd64.whl", hash = "sha256:ef5b87e7aa9545ddadd2309efe6824bd3dd64ac101c15dae0f2f597911d46eaa", size = 133732 }, + { url = "https://files.pythonhosted.org/packages/06/10/fe7d60b8da538e8d3d3721f08c1b7bff0491e8fa4dd3bf11a17e34f4730e/orjson-3.10.15-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bae0e6ec2b7ba6895198cd981b7cca95d1487d0147c8ed751e5632ad16f031a6", size = 249399 }, + { url = "https://files.pythonhosted.org/packages/6b/83/52c356fd3a61abd829ae7e4366a6fe8e8863c825a60d7ac5156067516edf/orjson-3.10.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93ce145b2db1252dd86af37d4165b6faa83072b46e3995ecc95d4b2301b725a", size = 125044 }, + { url = "https://files.pythonhosted.org/packages/55/b2/d06d5901408e7ded1a74c7c20d70e3a127057a6d21355f50c90c0f337913/orjson-3.10.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c203f6f969210128af3acae0ef9ea6aab9782939f45f6fe02d05958fe761ef9", size = 150066 }, + { url = "https://files.pythonhosted.org/packages/75/8c/60c3106e08dc593a861755781c7c675a566445cc39558677d505878d879f/orjson-3.10.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8918719572d662e18b8af66aef699d8c21072e54b6c82a3f8f6404c1f5ccd5e0", size = 139737 }, + { url = "https://files.pythonhosted.org/packages/6a/8c/ae00d7d0ab8a4490b1efeb01ad4ab2f1982e69cc82490bf8093407718ff5/orjson-3.10.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f71eae9651465dff70aa80db92586ad5b92df46a9373ee55252109bb6b703307", size = 154804 }, + { url = "https://files.pythonhosted.org/packages/22/86/65dc69bd88b6dd254535310e97bc518aa50a39ef9c5a2a5d518e7a223710/orjson-3.10.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e117eb299a35f2634e25ed120c37c641398826c2f5a3d3cc39f5993b96171b9e", size = 130583 }, + { url = "https://files.pythonhosted.org/packages/bb/00/6fe01ededb05d52be42fabb13d93a36e51f1fd9be173bd95707d11a8a860/orjson-3.10.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13242f12d295e83c2955756a574ddd6741c81e5b99f2bef8ed8d53e47a01e4b7", size = 138465 }, + { url = "https://files.pythonhosted.org/packages/db/2f/4cc151c4b471b0cdc8cb29d3eadbce5007eb0475d26fa26ed123dca93b33/orjson-3.10.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7946922ada8f3e0b7b958cc3eb22cfcf6c0df83d1fe5521b4a100103e3fa84c8", size = 130742 }, + { url = "https://files.pythonhosted.org/packages/9f/13/8a6109e4b477c518498ca37963d9c0eb1508b259725553fb53d53b20e2ea/orjson-3.10.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b7155eb1623347f0f22c38c9abdd738b287e39b9982e1da227503387b81b34ca", size = 414669 }, + { url = "https://files.pythonhosted.org/packages/22/7b/1d229d6d24644ed4d0a803de1b0e2df832032d5beda7346831c78191b5b2/orjson-3.10.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:208beedfa807c922da4e81061dafa9c8489c6328934ca2a562efa707e049e561", size = 141043 }, + { url = "https://files.pythonhosted.org/packages/cc/d3/6dc91156cf12ed86bed383bcb942d84d23304a1e57b7ab030bf60ea130d6/orjson-3.10.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eca81f83b1b8c07449e1d6ff7074e82e3fd6777e588f1a6632127f286a968825", size = 129826 }, + { url = "https://files.pythonhosted.org/packages/b3/38/c47c25b86f6996f1343be721b6ea4367bc1c8bc0fc3f6bbcd995d18cb19d/orjson-3.10.15-cp313-cp313-win32.whl", hash = "sha256:c03cd6eea1bd3b949d0d007c8d57049aa2b39bd49f58b4b2af571a5d3833d890", size = 142542 }, + { url = "https://files.pythonhosted.org/packages/27/f1/1d7ec15b20f8ce9300bc850de1e059132b88990e46cd0ccac29cbf11e4f9/orjson-3.10.15-cp313-cp313-win_amd64.whl", hash = "sha256:fd56a26a04f6ba5fb2045b0acc487a63162a958ed837648c5781e1fe3316cfbf", size = 133444 }, +] + +[[package]] +name = "packaging" +version = "23.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/2b/9b9c33ffed44ee921d0967086d653047286054117d584f1b1a7c22ceaf7b/packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", size = 146714 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/1a/610693ac4ee14fcdf2d9bf3c493370e4f2ef7ae2e19217d7a237ff42367d/packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7", size = 53011 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "pillow" +version = "10.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/69/a31cccd538ca0b5272be2a38347f8839b97a14be104ea08b0db92f749c74/pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", size = 3509271 }, + { url = "https://files.pythonhosted.org/packages/9a/9e/4143b907be8ea0bce215f2ae4f7480027473f8b61fcedfda9d851082a5d2/pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", size = 3375658 }, + { url = "https://files.pythonhosted.org/packages/8a/25/1fc45761955f9359b1169aa75e241551e74ac01a09f487adaaf4c3472d11/pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", size = 4332075 }, + { url = "https://files.pythonhosted.org/packages/5e/dd/425b95d0151e1d6c951f45051112394f130df3da67363b6bc75dc4c27aba/pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", size = 4444808 }, + { url = "https://files.pythonhosted.org/packages/b1/84/9a15cc5726cbbfe7f9f90bfb11f5d028586595907cd093815ca6644932e3/pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", size = 4356290 }, + { url = "https://files.pythonhosted.org/packages/b5/5b/6651c288b08df3b8c1e2f8c1152201e0b25d240e22ddade0f1e242fc9fa0/pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", size = 4525163 }, + { url = "https://files.pythonhosted.org/packages/07/8b/34854bf11a83c248505c8cb0fcf8d3d0b459a2246c8809b967963b6b12ae/pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", size = 4463100 }, + { url = "https://files.pythonhosted.org/packages/78/63/0632aee4e82476d9cbe5200c0cdf9ba41ee04ed77887432845264d81116d/pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", size = 4592880 }, + { url = "https://files.pythonhosted.org/packages/df/56/b8663d7520671b4398b9d97e1ed9f583d4afcbefbda3c6188325e8c297bd/pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", size = 2235218 }, + { url = "https://files.pythonhosted.org/packages/f4/72/0203e94a91ddb4a9d5238434ae6c1ca10e610e8487036132ea9bf806ca2a/pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", size = 2554487 }, + { url = "https://files.pythonhosted.org/packages/bd/52/7e7e93d7a6e4290543f17dc6f7d3af4bd0b3dd9926e2e8a35ac2282bc5f4/pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1", size = 2243219 }, + { url = "https://files.pythonhosted.org/packages/a7/62/c9449f9c3043c37f73e7487ec4ef0c03eb9c9afc91a92b977a67b3c0bbc5/pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", size = 3509265 }, + { url = "https://files.pythonhosted.org/packages/f4/5f/491dafc7bbf5a3cc1845dc0430872e8096eb9e2b6f8161509d124594ec2d/pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", size = 3375655 }, + { url = "https://files.pythonhosted.org/packages/73/d5/c4011a76f4207a3c151134cd22a1415741e42fa5ddecec7c0182887deb3d/pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", size = 4340304 }, + { url = "https://files.pythonhosted.org/packages/ac/10/c67e20445a707f7a610699bba4fe050583b688d8cd2d202572b257f46600/pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", size = 4452804 }, + { url = "https://files.pythonhosted.org/packages/a9/83/6523837906d1da2b269dee787e31df3b0acb12e3d08f024965a3e7f64665/pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", size = 4365126 }, + { url = "https://files.pythonhosted.org/packages/ba/e5/8c68ff608a4203085158cff5cc2a3c534ec384536d9438c405ed6370d080/pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", size = 4533541 }, + { url = "https://files.pythonhosted.org/packages/f4/7c/01b8dbdca5bc6785573f4cee96e2358b0918b7b2c7b60d8b6f3abf87a070/pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", size = 4471616 }, + { url = "https://files.pythonhosted.org/packages/c8/57/2899b82394a35a0fbfd352e290945440e3b3785655a03365c0ca8279f351/pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", size = 4600802 }, + { url = "https://files.pythonhosted.org/packages/4d/d7/a44f193d4c26e58ee5d2d9db3d4854b2cfb5b5e08d360a5e03fe987c0086/pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", size = 2235213 }, + { url = "https://files.pythonhosted.org/packages/c1/d0/5866318eec2b801cdb8c82abf190c8343d8a1cd8bf5a0c17444a6f268291/pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", size = 2554498 }, + { url = "https://files.pythonhosted.org/packages/d4/c8/310ac16ac2b97e902d9eb438688de0d961660a87703ad1561fd3dfbd2aa0/pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", size = 2243219 }, + { url = "https://files.pythonhosted.org/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350 }, + { url = "https://files.pythonhosted.org/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980 }, + { url = "https://files.pythonhosted.org/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799 }, + { url = "https://files.pythonhosted.org/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973 }, + { url = "https://files.pythonhosted.org/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054 }, + { url = "https://files.pythonhosted.org/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484 }, + { url = "https://files.pythonhosted.org/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375 }, + { url = "https://files.pythonhosted.org/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773 }, + { url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690 }, + { url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951 }, + { url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427 }, + { url = "https://files.pythonhosted.org/packages/c3/00/706cebe7c2c12a6318aabe5d354836f54adff7156fd9e1bd6c89f4ba0e98/pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", size = 3525685 }, + { url = "https://files.pythonhosted.org/packages/cf/76/f658cbfa49405e5ecbfb9ba42d07074ad9792031267e782d409fd8fe7c69/pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", size = 3374883 }, + { url = "https://files.pythonhosted.org/packages/46/2b/99c28c4379a85e65378211971c0b430d9c7234b1ec4d59b2668f6299e011/pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", size = 4339837 }, + { url = "https://files.pythonhosted.org/packages/f1/74/b1ec314f624c0c43711fdf0d8076f82d9d802afd58f1d62c2a86878e8615/pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", size = 4455562 }, + { url = "https://files.pythonhosted.org/packages/4a/2a/4b04157cb7b9c74372fa867096a1607e6fedad93a44deeff553ccd307868/pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", size = 4366761 }, + { url = "https://files.pythonhosted.org/packages/ac/7b/8f1d815c1a6a268fe90481232c98dd0e5fa8c75e341a75f060037bd5ceae/pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", size = 4536767 }, + { url = "https://files.pythonhosted.org/packages/e5/77/05fa64d1f45d12c22c314e7b97398ffb28ef2813a485465017b7978b3ce7/pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", size = 4477989 }, + { url = "https://files.pythonhosted.org/packages/12/63/b0397cfc2caae05c3fb2f4ed1b4fc4fc878f0243510a7a6034ca59726494/pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", size = 4610255 }, + { url = "https://files.pythonhosted.org/packages/7b/f9/cfaa5082ca9bc4a6de66ffe1c12c2d90bf09c309a5f52b27759a596900e7/pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", size = 2235603 }, + { url = "https://files.pythonhosted.org/packages/01/6a/30ff0eef6e0c0e71e55ded56a38d4859bf9d3634a94a88743897b5f96936/pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", size = 2554972 }, + { url = "https://files.pythonhosted.org/packages/48/2c/2e0a52890f269435eee38b21c8218e102c621fe8d8df8b9dd06fabf879ba/pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", size = 2243375 }, + { url = "https://files.pythonhosted.org/packages/38/30/095d4f55f3a053392f75e2eae45eba3228452783bab3d9a920b951ac495c/pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", size = 3493889 }, + { url = "https://files.pythonhosted.org/packages/f3/e8/4ff79788803a5fcd5dc35efdc9386af153569853767bff74540725b45863/pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", size = 3346160 }, + { url = "https://files.pythonhosted.org/packages/d7/ac/4184edd511b14f760c73f5bb8a5d6fd85c591c8aff7c2229677a355c4179/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", size = 3435020 }, + { url = "https://files.pythonhosted.org/packages/da/21/1749cd09160149c0a246a81d646e05f35041619ce76f6493d6a96e8d1103/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", size = 3490539 }, + { url = "https://files.pythonhosted.org/packages/b6/f5/f71fe1888b96083b3f6dfa0709101f61fc9e972c0c8d04e9d93ccef2a045/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", size = 3476125 }, + { url = "https://files.pythonhosted.org/packages/96/b9/c0362c54290a31866c3526848583a2f45a535aa9d725fd31e25d318c805f/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", size = 3579373 }, + { url = "https://files.pythonhosted.org/packages/52/3b/ce7a01026a7cf46e5452afa86f97a5e88ca97f562cafa76570178ab56d8d/pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", size = 2554661 }, +] + +[[package]] +name = "platformdirs" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/62/d1/7feaaacb1a3faeba96c06e6c5091f90695cc0f94b7e8e1a3a3fe2b33ff9a/platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420", size = 19760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/53/42fe5eab4a09d251a76d0043e018172db324a23fcdac70f77a551c11f618/platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380", size = 17420 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "prettytable" +version = "3.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/c0/5e9c4d2a643a00a6f67578ef35485173de273a4567279e4f0c200c01386b/prettytable-3.9.0.tar.gz", hash = "sha256:f4ed94803c23073a90620b201965e5dc0bccf1760b7a7eaf3158cab8aaffdf34", size = 47874 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/81/316b6a55a0d1f327d04cc7b0ba9d04058cb62de6c3a4d4b0df280cbe3b0b/prettytable-3.9.0-py3-none-any.whl", hash = "sha256:a71292ab7769a5de274b146b276ce938786f56c31cf7cea88b6f3775d82fe8c8", size = 27772 }, +] + +[[package]] +name = "protobuf" +version = "4.25.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/a5/05ea470f4e793c9408bc975ce1c6957447e3134ce7f7a58c13be8b2c216f/protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e", size = 380282 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/2f/01f63896ddf22cbb0173ab51f54fde70b0208ca6c2f5e8416950977930e1/protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6", size = 392408 }, + { url = "https://files.pythonhosted.org/packages/c1/00/c3ae19cabb36cfabc94ff0b102aac21b471c9f91a1357f8aafffb9efe8e0/protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9", size = 413397 }, + { url = "https://files.pythonhosted.org/packages/b3/81/0017aefacf23273d4efd1154ef958a27eed9c177c4cc09d2d4ba398fb47f/protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d", size = 394159 }, + { url = "https://files.pythonhosted.org/packages/23/17/405ba44f60a693dfe96c7a18e843707cffa0fcfad80bd8fc4f227f499ea5/protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62", size = 293698 }, + { url = "https://files.pythonhosted.org/packages/81/9e/63501b8d5b4e40c7260049836bd15ec3270c936e83bc57b85e4603cc212c/protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020", size = 294609 }, + { url = "https://files.pythonhosted.org/packages/ff/52/5d23df1fe3b368133ec3e2436fb3dd4ccedf44c8d5ac7f4a88087c75180b/protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830", size = 156463 }, +] + +[[package]] +name = "psutil" +version = "5.9.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/d0/c9ae661a302931735237791f04cb7086ac244377f78692ba3b3eae3a9619/psutil-5.9.7.tar.gz", hash = "sha256:3f02134e82cfb5d089fddf20bb2e03fd5cd52395321d1c8458a9e58500ff417c", size = 498429 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/63/86a4ccc640b4ee1193800f57bbd20b766853c0cdbdbb248a27cdfafe6cbf/psutil-5.9.7-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ea36cc62e69a13ec52b2f625c27527f6e4479bca2b340b7a452af55b34fcbe2e", size = 245972 }, + { url = "https://files.pythonhosted.org/packages/58/80/cc6666b3968646f2d94de66bbc63d701d501f4aa04de43dd7d1f5dc477dd/psutil-5.9.7-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1132704b876e58d277168cd729d64750633d5ff0183acf5b3c986b8466cd0284", size = 282514 }, + { url = "https://files.pythonhosted.org/packages/be/fa/f1f626620e3b47e6237dcc64cb8cc1472f139e99422e5b9fa5bbcf457f48/psutil-5.9.7-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8b7f07948f1304497ce4f4684881250cd859b16d06a1dc4d7941eeb6233bfe", size = 285469 }, + { url = "https://files.pythonhosted.org/packages/7c/b8/dc6ebfc030b47cccc5f5229eeb15e64142b4782796c3ce169ccd60b4d511/psutil-5.9.7-cp37-abi3-win32.whl", hash = "sha256:c727ca5a9b2dd5193b8644b9f0c883d54f1248310023b5ad3e92036c5e2ada68", size = 248406 }, + { url = "https://files.pythonhosted.org/packages/50/28/92b74d95dd991c837813ffac0c79a581a3d129eb0fa7c1dd616d9901e0f3/psutil-5.9.7-cp37-abi3-win_amd64.whl", hash = "sha256:f37f87e4d73b79e6c5e749440c3113b81d1ee7d26f21c19c47371ddea834f414", size = 252245 }, + { url = "https://files.pythonhosted.org/packages/ba/8a/000d0e80156f0b96c55bda6c60f5ed6543d7b5e893ccab83117e50de1400/psutil-5.9.7-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:032f4f2c909818c86cea4fe2cc407f1c0f0cde8e6c6d702b28b8ce0c0d143340", size = 246739 }, +] + +[[package]] +name = "pycparser" +version = "2.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206", size = 170877 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", size = 118697 }, +] + +[[package]] +name = "pydantic" +version = "2.10.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, +] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938 }, + { url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684 }, + { url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169 }, + { url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227 }, + { url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695 }, + { url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662 }, + { url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370 }, + { url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813 }, + { url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287 }, + { url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414 }, + { url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301 }, + { url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685 }, + { url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876 }, + { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, + { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, + { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, + { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, + { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, + { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, + { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, + { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, + { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, + { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, + { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, + { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, + { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, + { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, + { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, + { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, + { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, + { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, + { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, + { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, + { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, + { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, + { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, + { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, + { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, + { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, + { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, + { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, + { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, + { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, + { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, + { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, + { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, + { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, + { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, + { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, + { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, + { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, + { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, + { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, + { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, + { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, + { url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159 }, + { url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331 }, + { url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467 }, + { url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797 }, + { url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839 }, + { url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861 }, + { url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582 }, + { url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985 }, + { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 }, +] + +[[package]] +name = "pydantic-settings" +version = "2.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 }, +] + +[[package]] +name = "pygments" +version = "2.17.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/55/59/8bccf4157baf25e4aa5a0bb7fa3ba8600907de105ebc22b0c78cfbf6f565/pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367", size = 4827772 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", size = 1179756 }, +] + +[[package]] +name = "pyparsing" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/fe/65c989f70bd630b589adfbbcd6ed238af22319e90f059946c26b4835e44b/pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db", size = 884814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/92/8486ede85fcc088f1b3dba4ce92dd29d126fd96b0008ea213167940a2475/pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb", size = 103139 }, +] + +[[package]] +name = "pyreadline3" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/86/3d61a61f36a0067874a00cb4dceb9028d34b6060e47828f7fc86fb9f7ee9/pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae", size = 86465 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/fc/a3c13ded7b3057680c8ae95a9b6cc83e63657c38e0005c400a5d018a33a7/pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb", size = 95203 }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, +] + +[[package]] +name = "pytest-asyncio" +version = "0.25.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/a8/ecbc8ede70921dd2f544ab1cadd3ff3bf842af27f87bbdea774c7baa1d38/pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a", size = 54239 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/17/3493c5624e48fd97156ebaec380dcaafee9506d7e2c46218ceebbb57d7de/pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3", size = 19467 }, +] + +[[package]] +name = "pytest-cov" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, +] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/90/a955c3ab35ccd41ad4de556596fa86685bf4fc5ffcc62d22d856cfd4e29a/pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0", size = 32814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863 }, +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", size = 357324 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9", size = 247702 }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/06/1ef763af20d0572c032fa22882cfbfb005fba6e7300715a37840858c919e/python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba", size = 37399 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/2f/62ea1c8b593f4e093cc1a7768f0d46112107e790c3e478532329e434f00b/python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a", size = 19482 }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, +] + +[[package]] +name = "pywin32" +version = "306" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/dc/28c668097edfaf4eac4617ef7adf081b9cf50d254672fcf399a70f5efc41/pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d", size = 8506422 }, + { url = "https://files.pythonhosted.org/packages/d3/d6/891894edec688e72c2e308b3243fad98b4066e1839fd2fe78f04129a9d31/pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8", size = 9226392 }, + { url = "https://files.pythonhosted.org/packages/8b/1e/fc18ad83ca553e01b97aa8393ff10e33c1fb57801db05488b83282ee9913/pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407", size = 8507689 }, + { url = "https://files.pythonhosted.org/packages/7e/9e/ad6b1ae2a5ad1066dc509350e0fbf74d8d50251a51e420a2a8feaa0cecbd/pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e", size = 9227547 }, + { url = "https://files.pythonhosted.org/packages/91/20/f744bff1da8f43388498503634378dbbefbe493e65675f2cc52f7185c2c2/pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a", size = 10388324 }, + { url = "https://files.pythonhosted.org/packages/14/91/17e016d5923e178346aabda3dfec6629d1a26efe587d19667542105cf0a6/pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b", size = 8507705 }, + { url = "https://files.pythonhosted.org/packages/83/1c/25b79fc3ec99b19b0a0730cc47356f7e2959863bf9f3cd314332bddb4f68/pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e", size = 9227429 }, + { url = "https://files.pythonhosted.org/packages/1c/43/e3444dc9a12f8365d9603c2145d16bf0a2f8180f343cf87be47f5579e547/pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040", size = 10388145 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/e5/af35f7ea75cf72f2cd079c95ee16797de7cd71f29ea7c68ae5ce7be1eda0/PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", size = 125201 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/06/4beb652c0fe16834032e54f0956443d4cc797fe645527acee59e7deaa0a2/PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", size = 189447 }, + { url = "https://files.pythonhosted.org/packages/5b/07/10033a403b23405a8fc48975444463d3d10a5c2736b7eb2550b07b367429/PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f", size = 169264 }, + { url = "https://files.pythonhosted.org/packages/f1/26/55e4f21db1f72eaef092015d9017c11510e7e6301c62a6cfee91295d13c6/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", size = 677003 }, + { url = "https://files.pythonhosted.org/packages/ba/91/090818dfa62e85181f3ae23dd1e8b7ea7f09684864a900cab72d29c57346/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", size = 699070 }, + { url = "https://files.pythonhosted.org/packages/29/61/bf33c6c85c55bc45a29eee3195848ff2d518d84735eb0e2d8cb42e0d285e/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", size = 705525 }, + { url = "https://files.pythonhosted.org/packages/07/91/45dfd0ef821a7f41d9d0136ea3608bb5b1653e42fd56a7970532cb5c003f/PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", size = 707514 }, + { url = "https://files.pythonhosted.org/packages/b6/a0/b6700da5d49e9fed49dc3243d3771b598dad07abb37cc32e524607f96adc/PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", size = 130488 }, + { url = "https://files.pythonhosted.org/packages/24/97/9b59b43431f98d01806b288532da38099cc6f2fea0f3d712e21e269c0279/PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", size = 145338 }, + { url = "https://files.pythonhosted.org/packages/ec/0d/26fb23e8863e0aeaac0c64e03fd27367ad2ae3f3cccf3798ee98ce160368/PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", size = 187867 }, + { url = "https://files.pythonhosted.org/packages/28/09/55f715ddbf95a054b764b547f617e22f1d5e45d83905660e9a088078fe67/PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", size = 167530 }, + { url = "https://files.pythonhosted.org/packages/5e/94/7d5ee059dfb92ca9e62f4057dcdec9ac08a9e42679644854dc01177f8145/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", size = 732244 }, + { url = "https://files.pythonhosted.org/packages/06/92/e0224aa6ebf9dc54a06a4609da37da40bb08d126f5535d81bff6b417b2ae/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", size = 752871 }, + { url = "https://files.pythonhosted.org/packages/7b/5e/efd033ab7199a0b2044dab3b9f7a4f6670e6a52c089de572e928d2873b06/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", size = 757729 }, + { url = "https://files.pythonhosted.org/packages/03/5c/c4671451b2f1d76ebe352c0945d4cd13500adb5d05f5a51ee296d80152f7/PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", size = 748528 }, + { url = "https://files.pythonhosted.org/packages/73/9c/766e78d1efc0d1fca637a6b62cea1b4510a7fb93617eb805223294fef681/PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", size = 130286 }, + { url = "https://files.pythonhosted.org/packages/b3/34/65bb4b2d7908044963ebf614fe0fdb080773fc7030d7e39c8d3eddcd4257/PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", size = 144699 }, + { url = "https://files.pythonhosted.org/packages/bc/06/1b305bf6aa704343be85444c9d011f626c763abb40c0edc1cad13bfd7f86/PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", size = 178692 }, + { url = "https://files.pythonhosted.org/packages/84/02/404de95ced348b73dd84f70e15a41843d817ff8c1744516bf78358f2ffd2/PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", size = 165622 }, + { url = "https://files.pythonhosted.org/packages/c7/4c/4a2908632fc980da6d918b9de9c1d9d7d7e70b2672b1ad5166ed27841ef7/PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", size = 696937 }, + { url = "https://files.pythonhosted.org/packages/b4/33/720548182ffa8344418126017aa1d4ab4aeec9a2275f04ce3f3573d8ace8/PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", size = 724969 }, + { url = "https://files.pythonhosted.org/packages/4f/78/77b40157b6cb5f2d3d31a3d9b2efd1ba3505371f76730d267e8b32cf4b7f/PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", size = 712604 }, + { url = "https://files.pythonhosted.org/packages/2e/97/3e0e089ee85e840f4b15bfa00e4e63d84a3691ababbfea92d6f820ea6f21/PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", size = 126098 }, + { url = "https://files.pythonhosted.org/packages/2b/9f/fbade56564ad486809c27b322d0f7e6a89c01f6b4fe208402e90d4443a99/PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", size = 138675 }, +] + +[[package]] +name = "pyzmq" +version = "25.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/33/1a3683fc9a4bd64d8ccc0290da75c8f042184a1a49c146d28398414d3341/pyzmq-25.1.2.tar.gz", hash = "sha256:93f1aa311e8bb912e34f004cf186407a4e90eec4f0ecc0efd26056bf7eda0226", size = 1402339 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/f4/901edb48b2b2c00ad73de0db2ee76e24ce5903ef815ad0ad10e14555d989/pyzmq-25.1.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:e624c789359f1a16f83f35e2c705d07663ff2b4d4479bad35621178d8f0f6ea4", size = 1872310 }, + { url = "https://files.pythonhosted.org/packages/5e/46/2de69c7c79fd78bf4c22a9e8165fa6312f5d49410f1be6ddab51a6fe7236/pyzmq-25.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49151b0efece79f6a79d41a461d78535356136ee70084a1c22532fc6383f4ad0", size = 1249619 }, + { url = "https://files.pythonhosted.org/packages/d1/f5/d6b9755713843bf9701ae86bf6fd97ec294a52cf2af719cd14fdf9392f65/pyzmq-25.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9a5f194cf730f2b24d6af1f833c14c10f41023da46a7f736f48b6d35061e76e", size = 897360 }, + { url = "https://files.pythonhosted.org/packages/7c/88/c1aef8820f12e710d136024d231e70e24684a01314aa1814f0758960ba01/pyzmq-25.1.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:faf79a302f834d9e8304fafdc11d0d042266667ac45209afa57e5efc998e3872", size = 1156959 }, + { url = "https://files.pythonhosted.org/packages/82/1b/b25d2c4ac3b4dae238c98e63395dbb88daf11968b168948d3c6289c3e95c/pyzmq-25.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f51a7b4ead28d3fca8dda53216314a553b0f7a91ee8fc46a72b402a78c3e43d", size = 1100585 }, + { url = "https://files.pythonhosted.org/packages/67/bf/6bc0977acd934b66eacab79cec303ecf08ae4a6150d57c628aa919615488/pyzmq-25.1.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0ddd6d71d4ef17ba5a87becf7ddf01b371eaba553c603477679ae817a8d84d75", size = 1109267 }, + { url = "https://files.pythonhosted.org/packages/64/fb/4f07424e56c6a5fb47306d9ba744c3c250250c2e7272f9c81efbf8daaccf/pyzmq-25.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:246747b88917e4867e2367b005fc8eefbb4a54b7db363d6c92f89d69abfff4b6", size = 1431853 }, + { url = "https://files.pythonhosted.org/packages/a2/10/2b88c1d4beb59a1d45c13983c4b7c5dcd6ef7988db3c03d23b0cabc5adca/pyzmq-25.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:00c48ae2fd81e2a50c3485de1b9d5c7c57cd85dc8ec55683eac16846e57ac979", size = 1766212 }, + { url = "https://files.pythonhosted.org/packages/bc/ab/c9a22eacfd5bd82620501ae426a3dd6ffa374ac335b21e54209d7a93d3fb/pyzmq-25.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a68d491fc20762b630e5db2191dd07ff89834086740f70e978bb2ef2668be08", size = 1653737 }, + { url = "https://files.pythonhosted.org/packages/d6/e5/71bd89e47eedb7ebec31ef9a49dcdb0517dbbb063bd5de363980a6911eb1/pyzmq-25.1.2-cp310-cp310-win32.whl", hash = "sha256:09dfe949e83087da88c4a76767df04b22304a682d6154de2c572625c62ad6886", size = 906288 }, + { url = "https://files.pythonhosted.org/packages/9d/5f/2defc8a579e8b5679d92720ab3a4cb93e3a77d923070bf4c1a103d3ae478/pyzmq-25.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:fa99973d2ed20417744fca0073390ad65ce225b546febb0580358e36aa90dba6", size = 1170923 }, + { url = "https://files.pythonhosted.org/packages/35/de/7579518bc58cebf92568b48e354a702fb52525d0fab166dc544f2a0615dc/pyzmq-25.1.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:82544e0e2d0c1811482d37eef297020a040c32e0687c1f6fc23a75b75db8062c", size = 1870360 }, + { url = "https://files.pythonhosted.org/packages/ce/f9/58b6cc9a110b1832f666fa6b5a67dc4d520fabfc680ca87a8167b2061d5d/pyzmq-25.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01171fc48542348cd1a360a4b6c3e7d8f46cdcf53a8d40f84db6707a6768acc1", size = 1249008 }, + { url = "https://files.pythonhosted.org/packages/bc/4a/ac6469c01813cb3652ab4e30ec4a37815cc9949afc18af33f64e2ec704aa/pyzmq-25.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc69c96735ab501419c432110016329bf0dea8898ce16fab97c6d9106dc0b348", size = 904394 }, + { url = "https://files.pythonhosted.org/packages/77/b7/8cee519b11bdd3f76c1a6eb537ab13c1bfef2964d725717705c86f524e4c/pyzmq-25.1.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e124e6b1dd3dfbeb695435dff0e383256655bb18082e094a8dd1f6293114642", size = 1161453 }, + { url = "https://files.pythonhosted.org/packages/b6/1d/c35a956a44b333b064ae1b1c588c2dfa0e01b7ec90884c1972bfcef119c3/pyzmq-25.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7598d2ba821caa37a0f9d54c25164a4fa351ce019d64d0b44b45540950458840", size = 1105501 }, + { url = "https://files.pythonhosted.org/packages/18/d1/b3d1e985318ed7287737ea9e6b6e21748cc7c89accc2443347cd2c8d5f0f/pyzmq-25.1.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d1299d7e964c13607efd148ca1f07dcbf27c3ab9e125d1d0ae1d580a1682399d", size = 1109513 }, + { url = "https://files.pythonhosted.org/packages/14/9b/341cdfb47440069010101403298dc24d449150370c6cb322e73bfa1949bd/pyzmq-25.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4e6f689880d5ad87918430957297c975203a082d9a036cc426648fcbedae769b", size = 1433541 }, + { url = "https://files.pythonhosted.org/packages/fa/52/c6d4e76e020c554e965459d41a98201b4d45277a288648f53a4e5a2429cc/pyzmq-25.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cc69949484171cc961e6ecd4a8911b9ce7a0d1f738fcae717177c231bf77437b", size = 1766133 }, + { url = "https://files.pythonhosted.org/packages/1d/6d/0cbd8dd5b8979fd6b9cf1852ed067b9d2cd6fa0c09c3bafe6874d2d2e03c/pyzmq-25.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9880078f683466b7f567b8624bfc16cad65077be046b6e8abb53bed4eeb82dd3", size = 1653636 }, + { url = "https://files.pythonhosted.org/packages/f5/af/d90eed9cf3840685d54d4a35d5f9e242a8a48b5410d41146f14c1e098302/pyzmq-25.1.2-cp311-cp311-win32.whl", hash = "sha256:4e5837af3e5aaa99a091302df5ee001149baff06ad22b722d34e30df5f0d9097", size = 904865 }, + { url = "https://files.pythonhosted.org/packages/20/d2/09443dc73053ad01c846d7fb77e09fe9d93c09d4e900215f3c8b7b56bfec/pyzmq-25.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:25c2dbb97d38b5ac9fd15586e048ec5eb1e38f3d47fe7d92167b0c77bb3584e9", size = 1171332 }, + { url = "https://files.pythonhosted.org/packages/6e/f0/d71cf69dc039c9adc8b625efc3bad3684f3660a570e47f0f0c64df787b41/pyzmq-25.1.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:11e70516688190e9c2db14fcf93c04192b02d457b582a1f6190b154691b4c93a", size = 1871111 }, + { url = "https://files.pythonhosted.org/packages/68/62/d365773edf56ad71993579ee574105f02f83530caf600ebf28bea15d88d0/pyzmq-25.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:313c3794d650d1fccaaab2df942af9f2c01d6217c846177cfcbc693c7410839e", size = 1248844 }, + { url = "https://files.pythonhosted.org/packages/72/55/cc3163e20f40615a49245fa7041badec6103e8ee7e482dbb0feea00a7b84/pyzmq-25.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b3cbba2f47062b85fe0ef9de5b987612140a9ba3a9c6d2543c6dec9f7c2ab27", size = 899373 }, + { url = "https://files.pythonhosted.org/packages/40/aa/ae292bd85deda637230970bbc53c1dc53696a99e82fc7cd6d373ec173853/pyzmq-25.1.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc31baa0c32a2ca660784d5af3b9487e13b61b3032cb01a115fce6588e1bed30", size = 1160901 }, + { url = "https://files.pythonhosted.org/packages/93/b7/6e291eafbbbc66d0e87658dd21383ec2b4ab35edcfb283902c580a6db76f/pyzmq-25.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c9087b109070c5ab0b383079fa1b5f797f8d43e9a66c07a4b8b8bdecfd88ee", size = 1101147 }, + { url = "https://files.pythonhosted.org/packages/3a/f1/e296d5a507eac519d1fe1382851b1a4575f690bc2b2d2c8eca2ed7e4bd1f/pyzmq-25.1.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f8429b17cbb746c3e043cb986328da023657e79d5ed258b711c06a70c2ea7537", size = 1105315 }, + { url = "https://files.pythonhosted.org/packages/56/63/5c2abb556ab4cf013d98e01782d5bd642238a0ed9b019e965a7d7e957f56/pyzmq-25.1.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5074adeacede5f810b7ef39607ee59d94e948b4fd954495bdb072f8c54558181", size = 1427747 }, + { url = "https://files.pythonhosted.org/packages/b1/71/5dba5f6b12ef54fb977c9b9279075e151c04fc0dd6851e9663d9e66b593f/pyzmq-25.1.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7ae8f354b895cbd85212da245f1a5ad8159e7840e37d78b476bb4f4c3f32a9fe", size = 1762221 }, + { url = "https://files.pythonhosted.org/packages/cf/49/54d7e8bb3df82a3509325b11491d33450dc91580d4826b62fa5e554bb9cf/pyzmq-25.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b264bf2cc96b5bc43ce0e852be995e400376bd87ceb363822e2cb1964fcdc737", size = 1649505 }, + { url = "https://files.pythonhosted.org/packages/34/14/58e5037229bc37963e2ce804c2c075a3a541e3f84bf1c231e7c9779d36f1/pyzmq-25.1.2-cp312-cp312-win32.whl", hash = "sha256:02bbc1a87b76e04fd780b45e7f695471ae6de747769e540da909173d50ff8e2d", size = 954891 }, + { url = "https://files.pythonhosted.org/packages/2c/2d/04fab685ef3a8e6e955220fd2a54dc99efaee960a88675bf5c92cd277164/pyzmq-25.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:ced111c2e81506abd1dc142e6cd7b68dd53747b3b7ae5edbea4578c5eeff96b7", size = 1252773 }, + { url = "https://files.pythonhosted.org/packages/6b/fe/ed38fe12c540bafc1cae32c3ff638e9df32528f5cf91b5e400e6a8f5b3ec/pyzmq-25.1.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a8c1d566344aee826b74e472e16edae0a02e2a044f14f7c24e123002dcff1c05", size = 963654 }, + { url = "https://files.pythonhosted.org/packages/44/97/a760a2dff0672c408f22f726f2ea10a7a516ffa5001ca5a3641e355a45f9/pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759cfd391a0996345ba94b6a5110fca9c557ad4166d86a6e81ea526c376a01e8", size = 609436 }, + { url = "https://files.pythonhosted.org/packages/41/81/ace39daa19c78b2f4fc12ef217d9d5f1ac658d5828d692bbbb68240cd55b/pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c61e346ac34b74028ede1c6b4bcecf649d69b707b3ff9dc0fab453821b04d1e", size = 843396 }, + { url = "https://files.pythonhosted.org/packages/4c/43/150b0b203f5461a9aeadaa925c55167e2b4215c9322b6911a64360d2243e/pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb8fc1f8d69b411b8ec0b5f1ffbcaf14c1db95b6bccea21d83610987435f1a4", size = 800856 }, + { url = "https://files.pythonhosted.org/packages/5f/91/a618b56aaabe40dddcd25db85624d7408768fd32f5bfcf81bc0af5b1ce75/pyzmq-25.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3c00c9b7d1ca8165c610437ca0c92e7b5607b2f9076f4eb4b095c85d6e680a1d", size = 413836 }, +] + +[[package]] +name = "qudida" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "opencv-python-headless" }, + { name = "scikit-learn" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/2d/bab8babd9dc9a9e4df6eb115540cee4322c1a74078fb6f3b3ebc452a22b3/qudida-0.0.4.tar.gz", hash = "sha256:db198e2887ab0c9aa0023e565afbff41dfb76b361f85fd5e13f780d75ba18cc8", size = 3100 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/a1/a5f4bebaa31d109003909809d88aeb0d4b201463a9ea29308d9e4f9e7655/qudida-0.0.4-py3-none-any.whl", hash = "sha256:4519714c40cd0f2e6c51e1735edae8f8b19f4efe1f33be13e9d644ca5f736dd6", size = 3478 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "rich" +version = "13.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, +] + +[[package]] +name = "ruff" +version = "0.9.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/c3/418441a8170e8d53d05c0b9dad69760dbc7b8a12c10dbe6db1e1205d2377/ruff-0.9.9.tar.gz", hash = "sha256:0062ed13f22173e85f8f7056f9a24016e692efeea8704d1a5e8011b8aa850933", size = 3717448 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/c3/2c4afa9ba467555d074b146d9aed0633a56ccdb900839fb008295d037b89/ruff-0.9.9-py3-none-linux_armv6l.whl", hash = "sha256:628abb5ea10345e53dff55b167595a159d3e174d6720bf19761f5e467e68d367", size = 10027252 }, + { url = "https://files.pythonhosted.org/packages/33/d1/439e58487cf9eac26378332e25e7d5ade4b800ce1eec7dc2cfc9b0d7ca96/ruff-0.9.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6cd1428e834b35d7493354723543b28cc11dc14d1ce19b685f6e68e07c05ec7", size = 10840721 }, + { url = "https://files.pythonhosted.org/packages/50/44/fead822c38281ba0122f1b76b460488a175a9bd48b130650a6fb6dbcbcf9/ruff-0.9.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5ee162652869120ad260670706f3cd36cd3f32b0c651f02b6da142652c54941d", size = 10161439 }, + { url = "https://files.pythonhosted.org/packages/11/ae/d404a2ab8e61ddf6342e09cc6b7f7846cce6b243e45c2007dbe0ca928a5d/ruff-0.9.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aa0f6b75082c9be1ec5a1db78c6d4b02e2375c3068438241dc19c7c306cc61a", size = 10336264 }, + { url = "https://files.pythonhosted.org/packages/6a/4e/7c268aa7d84cd709fb6f046b8972313142cffb40dfff1d2515c5e6288d54/ruff-0.9.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:584cc66e89fb5f80f84b05133dd677a17cdd86901d6479712c96597a3f28e7fe", size = 9908774 }, + { url = "https://files.pythonhosted.org/packages/cc/26/c618a878367ef1b76270fd027ca93692657d3f6122b84ba48911ef5f2edc/ruff-0.9.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf3369325761a35aba75cd5c55ba1b5eb17d772f12ab168fbfac54be85cf18c", size = 11428127 }, + { url = "https://files.pythonhosted.org/packages/d7/9a/c5588a93d9bfed29f565baf193fe802fa676a0c837938137ea6cf0576d8c/ruff-0.9.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3403a53a32a90ce929aa2f758542aca9234befa133e29f4933dcef28a24317be", size = 12133187 }, + { url = "https://files.pythonhosted.org/packages/3e/ff/e7980a7704a60905ed7e156a8d73f604c846d9bd87deda9cabfa6cba073a/ruff-0.9.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:18454e7fa4e4d72cffe28a37cf6a73cb2594f81ec9f4eca31a0aaa9ccdfb1590", size = 11602937 }, + { url = "https://files.pythonhosted.org/packages/24/78/3690444ad9e3cab5c11abe56554c35f005b51d1d118b429765249095269f/ruff-0.9.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fadfe2c88724c9617339f62319ed40dcdadadf2888d5afb88bf3adee7b35bfb", size = 13771698 }, + { url = "https://files.pythonhosted.org/packages/6e/bf/e477c2faf86abe3988e0b5fd22a7f3520e820b2ee335131aca2e16120038/ruff-0.9.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6df104d08c442a1aabcfd254279b8cc1e2cbf41a605aa3e26610ba1ec4acf0b0", size = 11249026 }, + { url = "https://files.pythonhosted.org/packages/f7/82/cdaffd59e5a8cb5b14c408c73d7a555a577cf6645faaf83e52fe99521715/ruff-0.9.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d7c62939daf5b2a15af48abbd23bea1efdd38c312d6e7c4cedf5a24e03207e17", size = 10220432 }, + { url = "https://files.pythonhosted.org/packages/fe/a4/2507d0026225efa5d4412b6e294dfe54725a78652a5c7e29e6bd0fc492f3/ruff-0.9.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9494ba82a37a4b81b6a798076e4a3251c13243fc37967e998efe4cce58c8a8d1", size = 9874602 }, + { url = "https://files.pythonhosted.org/packages/d5/be/f3aab1813846b476c4bcffe052d232244979c3cd99d751c17afb530ca8e4/ruff-0.9.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4efd7a96ed6d36ef011ae798bf794c5501a514be369296c672dab7921087fa57", size = 10851212 }, + { url = "https://files.pythonhosted.org/packages/8b/45/8e5fd559bea0d2f57c4e12bf197a2fade2fac465aa518284f157dfbca92b/ruff-0.9.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ab90a7944c5a1296f3ecb08d1cbf8c2da34c7e68114b1271a431a3ad30cb660e", size = 11327490 }, + { url = "https://files.pythonhosted.org/packages/42/55/e6c90f13880aeef327746052907e7e930681f26a164fe130ddac28b08269/ruff-0.9.9-py3-none-win32.whl", hash = "sha256:6b4c376d929c25ecd6d87e182a230fa4377b8e5125a4ff52d506ee8c087153c1", size = 10227912 }, + { url = "https://files.pythonhosted.org/packages/35/b2/da925693cb82a1208aa34966c0f36cb222baca94e729dd22a587bc22d0f3/ruff-0.9.9-py3-none-win_amd64.whl", hash = "sha256:837982ea24091d4c1700ddb2f63b7070e5baec508e43b01de013dc7eff974ff1", size = 11355632 }, + { url = "https://files.pythonhosted.org/packages/31/d8/de873d1c1b020d668d8ec9855d390764cb90cf8f6486c0983da52be8b7b7/ruff-0.9.9-py3-none-win_arm64.whl", hash = "sha256:3ac78f127517209fe6d96ab00f3ba97cafe38718b23b1db3e96d8b2d39e37ddf", size = 10435860 }, +] + +[[package]] +name = "scikit-image" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "imageio" }, + { name = "lazy-loader" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "scipy" }, + { name = "tifffile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/c1/a49da20845f0f0e1afbb1c2586d406dc0acb84c26ae293bad6d7e7f718bc/scikit_image-0.22.0.tar.gz", hash = "sha256:018d734df1d2da2719087d15f679d19285fce97cd37695103deadfaef2873236", size = 22685018 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/8c/381ae42b37cf3e9e99a1deb3ffe76ca5ff5dd18ffa368293476164507fad/scikit_image-0.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74ec5c1d4693506842cc7c9487c89d8fc32aed064e9363def7af08b8f8cbb31d", size = 13905039 }, + { url = "https://files.pythonhosted.org/packages/16/06/4bfba08f5cce26d5070bb2cf4e3f9f479480978806355d1c5bea6f26a17c/scikit_image-0.22.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:a05ae4fe03d802587ed8974e900b943275548cde6a6807b785039d63e9a7a5ff", size = 13279212 }, + { url = "https://files.pythonhosted.org/packages/74/57/dbf744ca00eea2a09b1848c9dec28a43978c16dc049b1fba949cb050bedf/scikit_image-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a92dca3d95b1301442af055e196a54b5a5128c6768b79fc0a4098f1d662dee6", size = 14091779 }, + { url = "https://files.pythonhosted.org/packages/f1/6c/49f5a0ce8ddcdbdac5ac69c129654938cc6de0a936303caa6cad495ceb2a/scikit_image-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3663d063d8bf2fb9bdfb0ca967b9ee3b6593139c860c7abc2d2351a8a8863938", size = 14682042 }, + { url = "https://files.pythonhosted.org/packages/86/f0/18895318109f9b508f2310f136922e455a453550826a8240b412063c2528/scikit_image-0.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:ebdbdc901bae14dab637f8d5c99f6d5cc7aaf4a3b6f4003194e003e9f688a6fc", size = 24492345 }, + { url = "https://files.pythonhosted.org/packages/9f/d9/dc99e527d1a0050f0353d2fff3548273b4df6151884806e324f26572fd6b/scikit_image-0.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95d6da2d8a44a36ae04437c76d32deb4e3c993ffc846b394b9949fd8ded73cb2", size = 13883619 }, + { url = "https://files.pythonhosted.org/packages/80/37/7670020b112ff9a47e49b1e36f438d000db5b632aab8a8fd7e6be545d065/scikit_image-0.22.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:2c6ef454a85f569659b813ac2a93948022b0298516b757c9c6c904132be327e2", size = 13264761 }, + { url = "https://files.pythonhosted.org/packages/ad/85/dadf1194793ac1c895370f3ed048bb91dda083775b42e11d9672a50494d5/scikit_image-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e87872f067444ee90a00dd49ca897208308645382e8a24bd3e76f301af2352cd", size = 14070710 }, + { url = "https://files.pythonhosted.org/packages/d4/34/e27bf2bfe7b52b884b49bd71ea91ff81e4737246735ee5ea383314c31876/scikit_image-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5c378db54e61b491b9edeefff87e49fcf7fdf729bb93c777d7a5f15d36f743e", size = 14664172 }, + { url = "https://files.pythonhosted.org/packages/ce/d0/a3f60c9f57ed295b3076e4acdb29a37bbd8823452562ab2ad51b03d6f377/scikit_image-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:2bcb74adb0634258a67f66c2bb29978c9a3e222463e003b67ba12056c003971b", size = 24491321 }, + { url = "https://files.pythonhosted.org/packages/da/a4/b0b69bde4d6360e801d647691591dc9967a25a18a4c63ecf7f87d94e3fac/scikit_image-0.22.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:003ca2274ac0fac252280e7179ff986ff783407001459ddea443fe7916e38cff", size = 13968808 }, + { url = "https://files.pythonhosted.org/packages/e4/65/3c0f77e7a9bae100a8f7f5cebde410fca1a3cf64e1ecdd343666e27b11d4/scikit_image-0.22.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:cf3c0c15b60ae3e557a0c7575fbd352f0c3ce0afca562febfe3ab80efbeec0e9", size = 13323763 }, + { url = "https://files.pythonhosted.org/packages/4a/ed/7faf9f7a55d5b3095d33990a85603b66866cce2a608b27f0e1487d70a451/scikit_image-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b23908dd4d120e6aecb1ed0277563e8cbc8d6c0565bdc4c4c6475d53608452", size = 13877233 }, + { url = "https://files.pythonhosted.org/packages/ae/9d/09d06f36ce71fa276e1d9453fb4b04250a7038292b13b8c273a5a1a8f7c0/scikit_image-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be79d7493f320a964f8fcf603121595ba82f84720de999db0fcca002266a549a", size = 14954814 }, + { url = "https://files.pythonhosted.org/packages/dc/35/e6327ae498c6f557cb0a7c3fc284effe7958d2d1c43fb61cd77804fc2c4f/scikit_image-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:722b970aa5da725dca55252c373b18bbea7858c1cdb406e19f9b01a4a73b30b2", size = 25004857 }, +] + +[[package]] +name = "scikit-learn" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/00/835e3d280fdd7784e76bdef91dd9487582d7951a7254f59fc8004fc8b213/scikit-learn-1.3.2.tar.gz", hash = "sha256:a2f54c76accc15a34bfb9066e6c7a56c1e7235dda5762b990792330b52ccfb05", size = 7510251 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/53/570b55a6e10b8694ac1e3024d2df5cd443f1b4ff6d28430845da8b9019b3/scikit_learn-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e326c0eb5cf4d6ba40f93776a20e9a7a69524c4db0757e7ce24ba222471ee8a1", size = 10209999 }, + { url = "https://files.pythonhosted.org/packages/70/d0/50ace22129f79830e3cf682d0a2bd4843ef91573299d43112d52790163a8/scikit_learn-1.3.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:535805c2a01ccb40ca4ab7d081d771aea67e535153e35a1fd99418fcedd1648a", size = 9479353 }, + { url = "https://files.pythonhosted.org/packages/8f/46/fcc35ed7606c50d3072eae5a107a45cfa5b7f5fa8cc48610edd8cc8e8550/scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1215e5e58e9880b554b01187b8c9390bf4dc4692eedeaf542d3273f4785e342c", size = 10304705 }, + { url = "https://files.pythonhosted.org/packages/d0/0b/26ad95cf0b747be967b15fb71a06f5ac67aba0fd2f9cd174de6edefc4674/scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ee107923a623b9f517754ea2f69ea3b62fc898a3641766cb7deb2f2ce450161", size = 10827807 }, + { url = "https://files.pythonhosted.org/packages/69/8a/cf17d6443f5f537e099be81535a56ab68a473f9393fbffda38cd19899fc8/scikit_learn-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:35a22e8015048c628ad099da9df5ab3004cdbf81edc75b396fd0cff8699ac58c", size = 9255427 }, + { url = "https://files.pythonhosted.org/packages/08/5d/e5acecd6e99a6b656e42e7a7b18284e2f9c9f512e8ed6979e1e75d25f05f/scikit_learn-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6fb6bc98f234fda43163ddbe36df8bcde1d13ee176c6dc9b92bb7d3fc842eb66", size = 10116376 }, + { url = "https://files.pythonhosted.org/packages/40/c6/2e91eefb757822e70d351e02cc38d07c137212ae7c41ac12746415b4860a/scikit_learn-1.3.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:18424efee518a1cde7b0b53a422cde2f6625197de6af36da0b57ec502f126157", size = 9383415 }, + { url = "https://files.pythonhosted.org/packages/fa/fd/b3637639e73bb72b12803c5245f2a7299e09b2acd85a0f23937c53369a1c/scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3271552a5eb16f208a6f7f617b8cc6d1f137b52c8a1ef8edf547db0259b2c9fb", size = 10279163 }, + { url = "https://files.pythonhosted.org/packages/0c/2a/d3ff6091406bc2207e0adb832ebd15e40ac685811c7e2e3b432bfd969b71/scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4144a5004a676d5022b798d9e573b05139e77f271253a4703eed295bde0433", size = 10884422 }, + { url = "https://files.pythonhosted.org/packages/4e/ba/ce9bd1cd4953336a0e213b29cb80bb11816f2a93de8c99f88ef0b446ad0c/scikit_learn-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:67f37d708f042a9b8d59551cf94d30431e01374e00dc2645fa186059c6c5d78b", size = 9207060 }, + { url = "https://files.pythonhosted.org/packages/26/7e/2c3b82c8c29aa384c8bf859740419278627d2cdd0050db503c8840e72477/scikit_learn-1.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8db94cd8a2e038b37a80a04df8783e09caac77cbe052146432e67800e430c028", size = 9979322 }, + { url = "https://files.pythonhosted.org/packages/cf/fc/6c52ffeb587259b6b893b7cac268f1eb1b5426bcce1aa20e53523bfe6944/scikit_learn-1.3.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:61a6efd384258789aa89415a410dcdb39a50e19d3d8410bd29be365bcdd512d5", size = 9270688 }, + { url = "https://files.pythonhosted.org/packages/e5/a7/6f4ae76f72ae9de162b97acbf1f53acbe404c555f968d13da21e4112a002/scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb06f8dce3f5ddc5dee1715a9b9f19f20d295bed8e3cd4fa51e1d050347de525", size = 10280398 }, + { url = "https://files.pythonhosted.org/packages/5d/b7/ee35904c07a0666784349529412fbb9814a56382b650d30fd9d6be5e5054/scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b2de18d86f630d68fe1f87af690d451388bb186480afc719e5f770590c2ef6c", size = 10796478 }, + { url = "https://files.pythonhosted.org/packages/fe/6b/db949ed5ac367987b1f250f070f340b7715d22f0c9c965bdf07de6ca75a3/scikit_learn-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:0402638c9a7c219ee52c94cbebc8fcb5eb9fe9c773717965c1f4185588ad3107", size = 9133979 }, +] + +[[package]] +name = "scipy" +version = "1.11.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/1f/91144ba78dccea567a6466262922786ffc97be1e9b06ed9574ef0edc11e1/scipy-1.11.4.tar.gz", hash = "sha256:90a2b78e7f5733b9de748f589f09225013685f9b218275257f8a8168ededaeaa", size = 56336202 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/c6/a32add319475d21f89733c034b99c81b3a7c6c7c19f96f80c7ca3ff1bbd4/scipy-1.11.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc9a714581f561af0848e6b69947fda0614915f072dfd14142ed1bfe1b806710", size = 37293259 }, + { url = "https://files.pythonhosted.org/packages/de/0d/4fa68303568c70fd56fbf40668b6c6807cfee4cad975f07d80bdd26d013e/scipy-1.11.4-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cf00bd2b1b0211888d4dc75656c0412213a8b25e80d73898083f402b50f47e41", size = 29760656 }, + { url = "https://files.pythonhosted.org/packages/13/e5/8012be7857db6cbbbdbeea8a154dbacdfae845e95e1e19c028e82236d4a0/scipy-1.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9999c008ccf00e8fbcce1236f85ade5c569d13144f77a1946bef8863e8f6eb4", size = 32922489 }, + { url = "https://files.pythonhosted.org/packages/e0/9e/80e2205d138960a49caea391f3710600895dd8292b6868dc9aff7aa593f9/scipy-1.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933baf588daa8dc9a92c20a0be32f56d43faf3d1a60ab11b3f08c356430f6e56", size = 36442040 }, + { url = "https://files.pythonhosted.org/packages/69/60/30a9c3fbe5066a3a93eefe3e2d44553df13587e6f792e1bff20dfed3d17e/scipy-1.11.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8fce70f39076a5aa62e92e69a7f62349f9574d8405c0a5de6ed3ef72de07f446", size = 36643257 }, + { url = "https://files.pythonhosted.org/packages/f8/ec/b46756f80e3f4c5f0989f6e4492c2851f156d9c239d554754a3c8cffd4e2/scipy-1.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:6550466fbeec7453d7465e74d4f4b19f905642c89a7525571ee91dd7adabb5a3", size = 44149285 }, + { url = "https://files.pythonhosted.org/packages/b8/f2/1aefbd5e54ebd8c6163ccf7f73e5d17bc8cb38738d312befc524fce84bb4/scipy-1.11.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f313b39a7e94f296025e3cffc2c567618174c0b1dde173960cf23808f9fae4be", size = 37159197 }, + { url = "https://files.pythonhosted.org/packages/4b/48/20e77ddb1f473d4717a7d4d3fc8d15557f406f7708496054c59f635b7734/scipy-1.11.4-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1b7c3dca977f30a739e0409fb001056484661cb2541a01aba0bb0029f7b68db8", size = 29675057 }, + { url = "https://files.pythonhosted.org/packages/75/2e/a781862190d0e7e76afa74752ef363488a9a9d6ea86e46d5e5506cee8df6/scipy-1.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00150c5eae7b610c32589dda259eacc7c4f1665aedf25d921907f4d08a951b1c", size = 32882747 }, + { url = "https://files.pythonhosted.org/packages/6b/d4/d62ce38ba00dc67d7ec4ec5cc19d36958d8ed70e63778715ad626bcbc796/scipy-1.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:530f9ad26440e85766509dbf78edcfe13ffd0ab7fec2560ee5c36ff74d6269ff", size = 36402732 }, + { url = "https://files.pythonhosted.org/packages/88/86/827b56aea1ed04adbb044a675672a73c84d81076a350092bbfcfc1ae723b/scipy-1.11.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e347b14fe01003d3b78e196e84bd3f48ffe4c8a7b8a1afbcb8f5505cb710993", size = 36622138 }, + { url = "https://files.pythonhosted.org/packages/43/d0/f3cd75b62e1b90f48dbf091261b2fc7ceec14a700e308c50f6a69c83d337/scipy-1.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:acf8ed278cc03f5aff035e69cb511741e0418681d25fbbb86ca65429c4f4d9cd", size = 44095631 }, + { url = "https://files.pythonhosted.org/packages/df/64/8a690570485b636da614acff35fd725fcbc487f8b1fa9bdb12871b77412f/scipy-1.11.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:028eccd22e654b3ea01ee63705681ee79933652b2d8f873e7949898dda6d11b6", size = 37053653 }, + { url = "https://files.pythonhosted.org/packages/5e/43/abf331745a7e5f4af51f13d40e2a72f516048db41ecbcf3ac6f86ada54a3/scipy-1.11.4-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c6ff6ef9cc27f9b3db93a6f8b38f97387e6e0591600369a297a50a8e96e835d", size = 29641601 }, + { url = "https://files.pythonhosted.org/packages/47/9b/62d0ec086dd2871009da8769c504bec6e39b80f4c182c6ead0fcebd8b323/scipy-1.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b030c6674b9230d37c5c60ab456e2cf12f6784596d15ce8da9365e70896effc4", size = 32272137 }, + { url = "https://files.pythonhosted.org/packages/08/77/f90f7306d755ac68bd159c50bb86fffe38400e533e8c609dd8484bd0f172/scipy-1.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad669df80528aeca5f557712102538f4f37e503f0c5b9541655016dd0932ca79", size = 35777534 }, + { url = "https://files.pythonhosted.org/packages/00/de/b9f6938090c37b5092969ba1c67118e9114e8e6ef9d197251671444e839c/scipy-1.11.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce7fff2e23ab2cc81ff452a9444c215c28e6305f396b2ba88343a567feec9660", size = 35963721 }, + { url = "https://files.pythonhosted.org/packages/c6/a1/357e4cd43af2748e1e0407ae0e9a5ea8aaaa6b702833c81be11670dcbad8/scipy-1.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:36750b7733d960d7994888f0d148d31ea3017ac15eef664194b4ef68d36a4a97", size = 43730653 }, +] + +[[package]] +name = "setuptools" +version = "70.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/d8/10a70e86f6c28ae59f101a9de6d77bf70f147180fbf40c3af0f64080adc3/setuptools-70.3.0.tar.gz", hash = "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5", size = 2333112 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/15/88e46eb9387e905704b69849618e699dc2f54407d8953cc4ec4b8b46528d/setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc", size = 931070 }, +] + +[[package]] +name = "six" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, +] + +[[package]] +name = "sniffio" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/50/d49c388cae4ec10e8109b1b833fd265511840706808576df3ada99ecb0ac/sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", size = 17103 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384", size = 10165 }, +] + +[[package]] +name = "starlette" +version = "0.41.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/da/1fb4bdb72ae12b834becd7e1e7e47001d32f91ec0ce8d7bc1b618d9f0bd9/starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62", size = 2573867 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/43/f185bfd0ca1d213beb4293bed51d92254df23d8ceaf6c0e17146d508a776/starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d", size = 73259 }, +] + +[[package]] +name = "sympy" +version = "1.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/57/3485a1a3dff51bfd691962768b14310dae452431754bfc091250be50dd29/sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8", size = 6722203 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/05/e6600db80270777c4a64238a98d442f0fd07cc8915be2a1c16da7f2b9e74/sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5", size = 5742435 }, +] + +[[package]] +name = "threadpoolctl" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/8a/c05f7831beb32aff70f808766224f11c650f7edfd49b27a8fc6666107006/threadpoolctl-3.2.0.tar.gz", hash = "sha256:c96a0ba3bdddeaca37dc4cc7344aafad41cdb8c313f74fdfe387a867bba93355", size = 36266 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/12/fd4dea011af9d69e1cad05c75f3f7202cdcbeac9b712eea58ca779a72865/threadpoolctl-3.2.0-py3-none-any.whl", hash = "sha256:2b7818516e423bdaebb97c723f86a7c6b0a83d3f3b0970328d66f4d9104dc032", size = 15539 }, +] + +[[package]] +name = "tifffile" +version = "2023.12.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/6c0eadea1ccfcda27e6cce400c366098b5b082138a073f4252fe399f4148/tifffile-2023.12.9.tar.gz", hash = "sha256:9dd1da91180a6453018a241ff219e1905f169384355cd89c9ef4034c1b46cdb8", size = 353467 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/a4/569fc717831969cf48bced350bdaf070cdeab06918d179429899e144358d/tifffile-2023.12.9-py3-none-any.whl", hash = "sha256:9b066e4b1a900891ea42ffd33dab8ba34c537935618b9893ddef42d7d422692f", size = 223627 }, +] + +[[package]] +name = "tokenizers" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/41/c2be10975ca37f6ec40d7abd7e98a5213bb04f284b869c1a24e6504fd94d/tokenizers-0.21.0.tar.gz", hash = "sha256:ee0894bf311b75b0c03079f33859ae4b2334d675d4e93f5a4132e1eae2834fe4", size = 343021 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/5c/8b09607b37e996dc47e70d6a7b6f4bdd4e4d5ab22fe49d7374565c7fefaf/tokenizers-0.21.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3c4c93eae637e7d2aaae3d376f06085164e1660f89304c0ab2b1d08a406636b2", size = 2647461 }, + { url = "https://files.pythonhosted.org/packages/22/7a/88e58bb297c22633ed1c9d16029316e5b5ac5ee44012164c2edede599a5e/tokenizers-0.21.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:f53ea537c925422a2e0e92a24cce96f6bc5046bbef24a1652a5edc8ba975f62e", size = 2563639 }, + { url = "https://files.pythonhosted.org/packages/f7/14/83429177c19364df27d22bc096d4c2e431e0ba43e56c525434f1f9b0fd00/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b177fb54c4702ef611de0c069d9169f0004233890e0c4c5bd5508ae05abf193", size = 2903304 }, + { url = "https://files.pythonhosted.org/packages/7e/db/3433eab42347e0dc5452d8fcc8da03f638c9accffefe5a7c78146666964a/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b43779a269f4629bebb114e19c3fca0223296ae9fea8bb9a7a6c6fb0657ff8e", size = 2804378 }, + { url = "https://files.pythonhosted.org/packages/57/8b/7da5e6f89736c2ade02816b4733983fca1c226b0c42980b1ae9dc8fcf5cc/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aeb255802be90acfd363626753fda0064a8df06031012fe7d52fd9a905eb00e", size = 3095488 }, + { url = "https://files.pythonhosted.org/packages/4d/f6/5ed6711093dc2c04a4e03f6461798b12669bc5a17c8be7cce1240e0b5ce8/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b09dbeb7a8d73ee204a70f94fc06ea0f17dcf0844f16102b9f414f0b7463ba", size = 3121410 }, + { url = "https://files.pythonhosted.org/packages/81/42/07600892d48950c5e80505b81411044a2d969368cdc0d929b1c847bf6697/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:400832c0904f77ce87c40f1a8a27493071282f785724ae62144324f171377273", size = 3388821 }, + { url = "https://files.pythonhosted.org/packages/22/06/69d7ce374747edaf1695a4f61b83570d91cc8bbfc51ccfecf76f56ab4aac/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84ca973b3a96894d1707e189c14a774b701596d579ffc7e69debfc036a61a04", size = 3008868 }, + { url = "https://files.pythonhosted.org/packages/c8/69/54a0aee4d576045b49a0eb8bffdc495634309c823bf886042e6f46b80058/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eb7202d231b273c34ec67767378cd04c767e967fda12d4a9e36208a34e2f137e", size = 8975831 }, + { url = "https://files.pythonhosted.org/packages/f7/f3/b776061e4f3ebf2905ba1a25d90380aafd10c02d406437a8ba22d1724d76/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:089d56db6782a73a27fd8abf3ba21779f5b85d4a9f35e3b493c7bbcbbf0d539b", size = 8920746 }, + { url = "https://files.pythonhosted.org/packages/d8/ee/ce83d5ec8b6844ad4c3ecfe3333d58ecc1adc61f0878b323a15355bcab24/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:c87ca3dc48b9b1222d984b6b7490355a6fdb411a2d810f6f05977258400ddb74", size = 9161814 }, + { url = "https://files.pythonhosted.org/packages/18/07/3e88e65c0ed28fa93aa0c4d264988428eef3df2764c3126dc83e243cb36f/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4145505a973116f91bc3ac45988a92e618a6f83eb458f49ea0790df94ee243ff", size = 9357138 }, + { url = "https://files.pythonhosted.org/packages/15/b0/dc4572ca61555fc482ebc933f26cb407c6aceb3dc19c301c68184f8cad03/tokenizers-0.21.0-cp39-abi3-win32.whl", hash = "sha256:eb1702c2f27d25d9dd5b389cc1f2f51813e99f8ca30d9e25348db6585a97e24a", size = 2202266 }, + { url = "https://files.pythonhosted.org/packages/44/69/d21eb253fa91622da25585d362a874fa4710be600f0ea9446d8d0217cec1/tokenizers-0.21.0-cp39-abi3-win_amd64.whl", hash = "sha256:87841da5a25a3a5f70c102de371db120f41873b854ba65e52bccd57df5a3780c", size = 2389192 }, +] + +[[package]] +name = "tomli" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757 }, +] + +[[package]] +name = "tqdm" +version = "4.66.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/00/6a9b3aedb0b60a80995ade30f718f1a9902612f22a1aaf531c85a02987f7/tqdm-4.66.3.tar.gz", hash = "sha256:23097a41eba115ba99ecae40d06444c15d1c0c698d527a01c6c8bd1c5d0647e5", size = 169551 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/ad/7d47bbf2cae78ff79f29db0bed5016ec9c56b212a93fca624bb88b551a7c/tqdm-4.66.3-py3-none-any.whl", hash = "sha256:4f41d54107ff9a223dca80b53efe4fb654c67efaba7f47bada3ee9d50e05bd53", size = 78374 }, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20241230" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/f9/4d566925bcf9396136c0a2e5dc7e230ff08d86fa011a69888dd184469d80/types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c", size = 17078 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/c1/48474fbead512b70ccdb4f81ba5eb4a58f69d100ba19f17c92c0c4f50ae6/types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6", size = 20029 }, +] + +[[package]] +name = "types-requests" +version = "2.32.0.20250306" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/1a/beaeff79ef9efd186566ba5f0d95b44ae21f6d31e9413bcfbef3489b6ae3/types_requests-2.32.0.20250306.tar.gz", hash = "sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1", size = 23012 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/26/645d89f56004aa0ba3b96fec27793e3c7e62b40982ee069e52568922b6db/types_requests-2.32.0.20250306-py3-none-any.whl", hash = "sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b", size = 20673 }, +] + +[[package]] +name = "types-setuptools" +version = "75.8.2.20250305" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/18/a996861f5225e7d533a8d8b6aa61bcc9183429a6b8bc93b850aa2e22974d/types_setuptools-75.8.2.20250305.tar.gz", hash = "sha256:a987269b49488f21961a1d99aa8d281b611625883def6392a93855b31544e405", size = 42609 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5b/bb33f99239a6d54ed1d8220a088d96d2ccacac7abb317df0d68d2500f3be/types_setuptools-75.8.2.20250305-py3-none-any.whl", hash = "sha256:ba80953fd1f5f49e552285c024f75b5223096a38a5138a54d18ddd3fa8f6a2d4", size = 63727 }, +] + +[[package]] +name = "types-simplejson" +version = "3.20.0.20250218" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/b0/a2e4a46af6b4822b1a281b9ddcaba2451f7ceec0b114b09b1ee9d45ae90b/types_simplejson-3.20.0.20250218.tar.gz", hash = "sha256:5c5c46f67690f211d628d5182546b43ea9ff03936efd759549ac1795b213e3e7", size = 9824 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/8f/4eb139636659bd906483809a3be7e280c56d88eb9023ea1cf5b9e8acd82e/types_simplejson-3.20.0.20250218-py3-none-any.whl", hash = "sha256:dbe00ea8497f8ba2f91dd9654d7064613cc09df545e5a008e8240fc6a407e98f", size = 10333 }, +] + +[[package]] +name = "types-ujson" +version = "5.10.0.20240515" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/49/abb4bcb9f2258f785edbf236b517c3e7ba8a503a8cbce6b5895930586cc0/types-ujson-5.10.0.20240515.tar.gz", hash = "sha256:ceae7127f0dafe4af5dd0ecf98ee13e9d75951ef963b5c5a9b7ea92e0d71f0d7", size = 3571 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/1f/9d018cee3d09ab44a5211f0b5ed9b0422ad9a8c226bf3967f5884498d8f0/types_ujson-5.10.0.20240515-py3-none-any.whl", hash = "sha256:02bafc36b3a93d2511757a64ff88bd505e0a57fba08183a9150fbcfcb2015310", size = 2757 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "urllib3" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/dd/a6b232f449e1bc71802a5b7950dc3675d32c6dbc2a1bd6d71f065551adb6/urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54", size = 263900 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/94/c31f58c7a7f470d5665935262ebd7455c7e4c7782eb525658d3dbf4b9403/urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", size = 104579 }, +] + +[[package]] +name = "uvicorn" +version = "0.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/16/728cc5dde368e6eddb299c5aec4d10eaf25335a5af04e8c0abd68e2e9d32/uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd", size = 2318492 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/c2/27bf858a576b1fa35b5c2c2029c8cec424a8789e87545ed2f25466d1f21d/uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e", size = 1443484 }, + { url = "https://files.pythonhosted.org/packages/4e/35/05b6064b93f4113412d1fd92bdcb6018607e78ae94d1712e63e533f9b2fa/uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428", size = 793850 }, + { url = "https://files.pythonhosted.org/packages/aa/56/b62ab4e10458ce96bb30c98d327c127f989d3bb4ef899e4c410c739f7ef6/uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8", size = 3418601 }, + { url = "https://files.pythonhosted.org/packages/ab/ed/12729fba5e3b7e02ee70b3ea230b88e60a50375cf63300db22607694d2f0/uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849", size = 3416731 }, + { url = "https://files.pythonhosted.org/packages/a2/23/80381a2d728d2a0c36e2eef202f5b77428990004d8fbdd3865558ff49fa5/uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957", size = 4128572 }, + { url = "https://files.pythonhosted.org/packages/6b/23/1ee41a15e1ad15182e2bd12cbfd37bcb6802f01d6bbcaddf6ca136cbb308/uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd", size = 4129235 }, + { url = "https://files.pythonhosted.org/packages/41/2a/608ad69f27f51280098abee440c33e921d3ad203e2c86f7262e241e49c99/uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef", size = 1357681 }, + { url = "https://files.pythonhosted.org/packages/13/00/d0923d66d80c8717983493a4d7af747ce47f1c2147d82df057a846ba6bff/uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2", size = 746421 }, + { url = "https://files.pythonhosted.org/packages/1f/c7/e494c367b0c6e6453f9bed5a78548f5b2ff49add36302cd915a91d347d88/uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1", size = 3481000 }, + { url = "https://files.pythonhosted.org/packages/86/cc/1829b3f740e4cb1baefff8240a1c6fc8db9e3caac7b93169aec7d4386069/uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24", size = 3476361 }, + { url = "https://files.pythonhosted.org/packages/7a/4c/ca87e8f5a30629ffa2038c20907c8ab455c5859ff10e810227b76e60d927/uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533", size = 4169571 }, + { url = "https://files.pythonhosted.org/packages/d2/a9/f947a00c47b1c87c937cac2423243a41ba08f0fb76d04eb0d1d170606e0a/uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12", size = 4170459 }, + { url = "https://files.pythonhosted.org/packages/85/57/6736733bb0e86a4b5380d04082463b289c0baecaa205934ba81e8a1d5ea4/uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650", size = 1355376 }, + { url = "https://files.pythonhosted.org/packages/eb/0c/51339463da912ed34b48d470538d98a91660749b2db56902f23db9b42fdd/uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec", size = 745031 }, + { url = "https://files.pythonhosted.org/packages/e6/fc/f0daaf19f5b2116a2d26eb9f98c4a45084aea87bf03c33bcca7aa1ff36e5/uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc", size = 4077630 }, + { url = "https://files.pythonhosted.org/packages/fd/96/fdc318ffe82ae567592b213ec2fcd8ecedd927b5da068cf84d56b28c51a4/uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6", size = 4159957 }, + { url = "https://files.pythonhosted.org/packages/71/bc/092068ae7fc16dcf20f3e389126ba7800cee75ffba83f78bf1d167aee3cd/uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593", size = 4014951 }, + { url = "https://files.pythonhosted.org/packages/a6/f2/6ce1e73933eb038c89f929e26042e64b2cb8d4453410153eed14918ca9a8/uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3", size = 4100911 }, +] + +[[package]] +name = "watchfiles" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/79/0ee412e1228aaf6f9568aa180b43cb482472de52560fbd7c283c786534af/watchfiles-0.21.0.tar.gz", hash = "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3", size = 37098 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/85/ea2a035b7d86bf0a29ee1c32bc2df8ad4da77e6602806e679d9735ff28cb/watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa", size = 428182 }, + { url = "https://files.pythonhosted.org/packages/b5/e5/240e5eb3ff0ee3da3b028ac5be2019c407bdd0f3fdb02bd75fdf3bd10aff/watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e", size = 418275 }, + { url = "https://files.pythonhosted.org/packages/5b/79/ecd0dfb04443a1900cd3952d7ea6493bf655c2db9a0d3736a5d98a15da39/watchfiles-0.21.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03", size = 1379785 }, + { url = "https://files.pythonhosted.org/packages/41/0e/3333b986b1889bb71f0e44b3fac0591824a679619b8b8ddd70ff8858edc4/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124", size = 1349374 }, + { url = "https://files.pythonhosted.org/packages/18/c4/ad5ad16cad900a29aaa792e0ed121ff70d76f74062b051661090d88c6dfd/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab", size = 1348033 }, + { url = "https://files.pythonhosted.org/packages/4e/d2/769254ff04ba88ceb179a6e892606ac4da17338eb010e85ca7a9c3339234/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303", size = 1464393 }, + { url = "https://files.pythonhosted.org/packages/14/d0/662800e778ca20e7664dd5df57751aa79ef18b6abb92224b03c8c2e852a6/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d", size = 1542953 }, + { url = "https://files.pythonhosted.org/packages/f7/4b/b90dcdc3bbaf3bb2db733e1beea2d01566b601c15fcf8e71dfcc8686c097/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c", size = 1346961 }, + { url = "https://files.pythonhosted.org/packages/92/ff/75cc1b30c5abcad13a2a72e75625ec619c7a393028a111d7d24dba578d5e/watchfiles-0.21.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9", size = 1464393 }, + { url = "https://files.pythonhosted.org/packages/9a/65/12cbeb363bf220482a559c48107edfd87f09248f55e1ac315a36c2098a0f/watchfiles-0.21.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9", size = 1463409 }, + { url = "https://files.pythonhosted.org/packages/f2/08/92e28867c66f0d9638bb131feca739057efc48dbcd391fd7f0a55507e470/watchfiles-0.21.0-cp310-none-win32.whl", hash = "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293", size = 268101 }, + { url = "https://files.pythonhosted.org/packages/4b/ea/80527adf1ad51488a96fc201715730af5879f4dfeccb5e2069ff82d890d4/watchfiles-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235", size = 279675 }, + { url = "https://files.pythonhosted.org/packages/57/b9/2667286003dd305b81d3a3aa824d3dfc63dacbf2a96faae09e72d953c430/watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7", size = 428210 }, + { url = "https://files.pythonhosted.org/packages/a3/87/6793ac60d2e20c9c1883aec7431c2e7b501ee44a839f6da1b747c13baa23/watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef", size = 418196 }, + { url = "https://files.pythonhosted.org/packages/5d/12/e1d1d220c5b99196eea38c9a878964f30a2b55ec9d72fd713191725b35e8/watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586", size = 1380287 }, + { url = "https://files.pythonhosted.org/packages/0e/cf/126f0a8683f326d190c3539a769e45e747a80a5fcbf797de82e738c946ae/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317", size = 1349653 }, + { url = "https://files.pythonhosted.org/packages/20/6e/6cffd795ec65dbc82f15d95b73d3042c1ddaffc4dd25f6c8240bfcf0640f/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b", size = 1348844 }, + { url = "https://files.pythonhosted.org/packages/d5/2a/f9633279d8937ad84c532997405dd106fa6100e8d2b83e364f1c87561f96/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1", size = 1464343 }, + { url = "https://files.pythonhosted.org/packages/d7/49/9b2199bbf3c89e7c8dd795fced9dac29f201be8a28a5df0c8ff625737df6/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d", size = 1542858 }, + { url = "https://files.pythonhosted.org/packages/35/e0/e8a9c1fe30e98c5b3507ad381abc4d9ee2c3b9c0ae62ffe9c164a5838186/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7", size = 1347464 }, + { url = "https://files.pythonhosted.org/packages/ba/66/873739dd7defdfaee4b880114de9463fae18ba13ae2ddd784806b0ee33b6/watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0", size = 1464343 }, + { url = "https://files.pythonhosted.org/packages/bd/51/d7539aa258d8f0e5d7b870af8b9b8964b4f88a1e4517eeb8a2efb838e9b3/watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365", size = 1463338 }, + { url = "https://files.pythonhosted.org/packages/ee/92/219c539a2a93b6870fa7b84eace946983126b20a7e15c6c034d8d0472682/watchfiles-0.21.0-cp311-none-win32.whl", hash = "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400", size = 267658 }, + { url = "https://files.pythonhosted.org/packages/f3/dc/2a8a447b783f5059c4bf7a6bad8fe59375a5a9ce872774763b25c21c2860/watchfiles-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe", size = 280113 }, + { url = "https://files.pythonhosted.org/packages/22/15/e4085181cf0210a6ec6eb29fee0c6088de867ee33d81555076a4a2726e8b/watchfiles-0.21.0-cp311-none-win_arm64.whl", hash = "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078", size = 268688 }, + { url = "https://files.pythonhosted.org/packages/a1/fd/2f009eb17809afd32a143b442856628585c9ce3a9c6d5c1841e44e35a16c/watchfiles-0.21.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a", size = 426902 }, + { url = "https://files.pythonhosted.org/packages/e0/62/a2605f212a136e06f2d056ee7491ede9935ba0f1d5ceafd1f7da2a0c8625/watchfiles-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1", size = 417300 }, + { url = "https://files.pythonhosted.org/packages/69/0e/29f158fa22eb2cc1f188b5ec20fb5c0a64eb801e3901ad5b7ad546cbaed0/watchfiles-0.21.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a", size = 1378126 }, + { url = "https://files.pythonhosted.org/packages/e8/f3/c67865cb5a174201c52d34e870cc7956b8408ee83ce9a02909d6a2a93a14/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915", size = 1348275 }, + { url = "https://files.pythonhosted.org/packages/d7/eb/b6f1184d1c7b9670f5bd1e184e4c221ecf25fd817cf2fcac6adc387882b5/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360", size = 1347255 }, + { url = "https://files.pythonhosted.org/packages/c8/27/e534e4b3fe739f4bf8bd5dc4c26cbc5d3baa427125d8ef78a6556acd6ff4/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6", size = 1462845 }, + { url = "https://files.pythonhosted.org/packages/b0/ba/a0d1c1c55f75e7e47c8f79f2314f7ec670b5177596f6d27764aecc7048cd/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7", size = 1528957 }, + { url = "https://files.pythonhosted.org/packages/1c/3a/4e38518c4dff58090c01fc8cc051fa08ac9ae00b361c855075809b0058ce/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c", size = 1345542 }, + { url = "https://files.pythonhosted.org/packages/9f/b7/783097f8137a710d5cd9ccbfcd92e4b453d38dab05cfcb5dbd2c587752e5/watchfiles-0.21.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235", size = 1462238 }, + { url = "https://files.pythonhosted.org/packages/6b/4c/b741eb38f2c408ae9c5a25235f6506b1dda43486ae0fdb4c462ef75bce11/watchfiles-0.21.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7", size = 1462406 }, + { url = "https://files.pythonhosted.org/packages/77/e4/8d2b3c67364671b0e1c0ce383895a5415f45ecb3e8586982deff4a8e85c9/watchfiles-0.21.0-cp312-none-win32.whl", hash = "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3", size = 266789 }, + { url = "https://files.pythonhosted.org/packages/da/f2/6b1de38aeb21eb9dac1ae6a1ee4521566e79690117032036c737cfab52fa/watchfiles-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094", size = 280292 }, + { url = "https://files.pythonhosted.org/packages/5a/a5/7aba9435beb863c2490bae3173a45f42044ac7a48155d3dd42ab49cfae45/watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6", size = 268026 }, + { url = "https://files.pythonhosted.org/packages/62/66/7463ceb43eabc6deaa795c7969ff4d4fd938de54e655035483dfd1e97c84/watchfiles-0.21.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994", size = 429092 }, + { url = "https://files.pythonhosted.org/packages/fe/a3/42686af3a089f34aba35c39abac852869661938dae7025c1a0580dfe0fbf/watchfiles-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f", size = 419188 }, + { url = "https://files.pythonhosted.org/packages/37/17/4825999346f15d650f4c69093efa64fb040fbff4f706a20e8c4745f64070/watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c", size = 1350366 }, + { url = "https://files.pythonhosted.org/packages/70/76/8d124e14cf51af4d6bba926c7473f253c6efd1539ba62577f079a2d71537/watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc", size = 1346270 }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] + +[[package]] +name = "websockets" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/62/7a7874b7285413c954a4cca3c11fd851f11b2fe5b4ae2d9bee4f6d9bdb10/websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", size = 104994 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/b9/360b86ded0920a93bff0db4e4b0aa31370b0208ca240b2e98d62aad8d082/websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374", size = 124025 }, + { url = "https://files.pythonhosted.org/packages/bb/d3/1eca0d8fb6f0665c96f0dc7c0d0ec8aa1a425e8c003e0c18e1451f65d177/websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be", size = 121261 }, + { url = "https://files.pythonhosted.org/packages/4e/e1/f6c3ecf7f1bfd9209e13949db027d7fdea2faf090c69b5f2d17d1d796d96/websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547", size = 121328 }, + { url = "https://files.pythonhosted.org/packages/74/4d/f88eeceb23cb587c4aeca779e3f356cf54817af2368cb7f2bd41f93c8360/websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2", size = 130925 }, + { url = "https://files.pythonhosted.org/packages/16/17/f63d9ee6ffd9afbeea021d5950d6e8db84cd4aead306c6c2ca523805699e/websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558", size = 129930 }, + { url = "https://files.pythonhosted.org/packages/9a/12/c7a7504f5bf74d6ee0533f6fc7d30d8f4b79420ab179d1df2484b07602eb/websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480", size = 130245 }, + { url = "https://files.pythonhosted.org/packages/e4/6a/3600c7771eb31116d2e77383d7345618b37bb93709d041e328c08e2a8eb3/websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c", size = 134966 }, + { url = "https://files.pythonhosted.org/packages/22/26/df77c4b7538caebb78c9b97f43169ef742a4f445e032a5ea1aaef88f8f46/websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8", size = 134196 }, + { url = "https://files.pythonhosted.org/packages/e5/18/18ce9a4a08203c8d0d3d561e3ea4f453daf32f099601fc831e60c8a9b0f2/websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603", size = 134822 }, + { url = "https://files.pythonhosted.org/packages/45/51/1f823a341fc20a880e67ae62f6c38c4880a24a4b60fbe544a38f516f39a1/websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f", size = 124454 }, + { url = "https://files.pythonhosted.org/packages/41/b0/5ec054cfcf23adfc88d39359b85e81d043af8a141e3ac8ce40f45a5ce5f4/websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf", size = 124974 }, + { url = "https://files.pythonhosted.org/packages/02/73/9c1e168a2e7fdf26841dc98f5f5502e91dea47428da7690a08101f616169/websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4", size = 124047 }, + { url = "https://files.pythonhosted.org/packages/e4/2d/9a683359ad2ed11b2303a7a94800db19c61d33fa3bde271df09e99936022/websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f", size = 121282 }, + { url = "https://files.pythonhosted.org/packages/95/aa/75fa3b893142d6d98a48cb461169bd268141f2da8bfca97392d6462a02eb/websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3", size = 121325 }, + { url = "https://files.pythonhosted.org/packages/6e/a4/51a25e591d645df71ee0dc3a2c880b28e5514c00ce752f98a40a87abcd1e/websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c", size = 131502 }, + { url = "https://files.pythonhosted.org/packages/cd/ea/0ceeea4f5b87398fe2d9f5bcecfa00a1bcd542e2bfcac2f2e5dd612c4e9e/websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45", size = 130491 }, + { url = "https://files.pythonhosted.org/packages/e3/05/f52a60b66d9faf07a4f7d71dc056bffafe36a7e98c4eb5b78f04fe6e4e85/websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04", size = 130872 }, + { url = "https://files.pythonhosted.org/packages/ac/4e/c7361b2d7b964c40fea924d64881145164961fcd6c90b88b7e3ab2c4f431/websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447", size = 136318 }, + { url = "https://files.pythonhosted.org/packages/0a/31/337bf35ae5faeaf364c9cddec66681cdf51dc4414ee7a20f92a18e57880f/websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca", size = 135594 }, + { url = "https://files.pythonhosted.org/packages/95/aa/1ac767825c96f9d7e43c4c95683757d4ef28cf11fa47a69aca42428d3e3a/websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53", size = 136191 }, + { url = "https://files.pythonhosted.org/packages/28/4b/344ec5cfeb6bc417da097f8253607c3aed11d9a305fb58346f506bf556d8/websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402", size = 124453 }, + { url = "https://files.pythonhosted.org/packages/d1/40/6b169cd1957476374f51f4486a3e85003149e62a14e6b78a958c2222337a/websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b", size = 124971 }, + { url = "https://files.pythonhosted.org/packages/a9/6d/23cc898647c8a614a0d9ca703695dd04322fb5135096a20c2684b7c852b6/websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", size = 124061 }, + { url = "https://files.pythonhosted.org/packages/39/34/364f30fdf1a375e4002a26ee3061138d1571dfda6421126127d379d13930/websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", size = 121296 }, + { url = "https://files.pythonhosted.org/packages/2e/00/96ae1c9dcb3bc316ef683f2febd8c97dde9f254dc36c3afc65c7645f734c/websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", size = 121326 }, + { url = "https://files.pythonhosted.org/packages/af/f1/bba1e64430685dd456c1a1fd6b0c791ae33104967b928aefeff261761e8d/websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", size = 131807 }, + { url = "https://files.pythonhosted.org/packages/62/3b/98ee269712f37d892b93852ce07b3e6d7653160ca4c0d4f8c8663f8021f8/websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", size = 130751 }, + { url = "https://files.pythonhosted.org/packages/f1/00/d6f01ca2b191f8b0808e4132ccd2e7691f0453cbd7d0f72330eb97453c3a/websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", size = 131176 }, + { url = "https://files.pythonhosted.org/packages/af/9c/703ff3cd8109dcdee6152bae055d852ebaa7750117760ded697ab836cbcf/websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", size = 136246 }, + { url = "https://files.pythonhosted.org/packages/0b/a5/1a38fb85a456b9dc874ec984f3ff34f6550eafd17a3da28753cd3c1628e8/websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", size = 135466 }, + { url = "https://files.pythonhosted.org/packages/3c/98/1261f289dff7e65a38d59d2f591de6ed0a2580b729aebddec033c4d10881/websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", size = 136083 }, + { url = "https://files.pythonhosted.org/packages/a9/1c/f68769fba63ccb9c13fe0a25b616bd5aebeef1c7ddebc2ccc32462fb784d/websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", size = 124460 }, + { url = "https://files.pythonhosted.org/packages/20/52/8915f51f9aaef4e4361c89dd6cf69f72a0159f14e0d25026c81b6ad22525/websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", size = 124985 }, + { url = "https://files.pythonhosted.org/packages/43/8b/554a8a8bb6da9dd1ce04c44125e2192af7b7beebf6e3dbfa5d0e285cc20f/websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd", size = 121110 }, + { url = "https://files.pythonhosted.org/packages/b0/8e/58b8812940d746ad74d395fb069497255cb5ef50748dfab1e8b386b1f339/websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870", size = 123216 }, + { url = "https://files.pythonhosted.org/packages/81/ee/272cb67ace1786ce6d9f39d47b3c55b335e8b75dd1972a7967aad39178b6/websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077", size = 122821 }, + { url = "https://files.pythonhosted.org/packages/a8/03/387fc902b397729df166763e336f4e5cec09fe7b9d60f442542c94a21be1/websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b", size = 122768 }, + { url = "https://files.pythonhosted.org/packages/50/f0/5939fbc9bc1979d79a774ce5b7c4b33c0cefe99af22fb70f7462d0919640/websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30", size = 125009 }, + { url = "https://files.pythonhosted.org/packages/79/4d/9cc401e7b07e80532ebc8c8e993f42541534da9e9249c59ee0139dcb0352/websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", size = 118370 }, +] + +[[package]] +name = "werkzeug" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/51/2e0fc149e7a810d300422ab543f87f2bcf64d985eb6f1228c4efd6e4f8d4/werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18", size = 803342 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/6e/e792999e816d19d7fcbfa94c730936750036d65656a76a5a688b57a656c4/werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8", size = 227274 }, +] + +[[package]] +name = "zope-event" +version = "5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/c2/427f1867bb96555d1d34342f1dd97f8c420966ab564d58d18469a1db8736/zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd", size = 17350 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/42/f8dbc2b9ad59e927940325a22d6d3931d630c3644dae7e2369ef5d9ba230/zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", size = 6824 }, +] + +[[package]] +name = "zope-interface" +version = "6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/03/6b85c1df2dca1b9acca38b423d1e226d8ffdf30ebd78bcb398c511de8b54/zope.interface-6.1.tar.gz", hash = "sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309", size = 293914 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/ec/c1e7ce928dc10bfe02c6da7e964342d941aaf168f96f8084636167ea50d2/zope.interface-6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb", size = 202417 }, + { url = "https://files.pythonhosted.org/packages/f7/0b/12f269ad049fc40a7a3ab85445d7855b6bc6f1e774c5ca9dd6f5c32becb3/zope.interface-6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92", size = 202528 }, + { url = "https://files.pythonhosted.org/packages/7f/85/3a35144509eb4a5a2208b48ae8d116a969d67de62cc6513d85602144d9cd/zope.interface-6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3", size = 247532 }, + { url = "https://files.pythonhosted.org/packages/50/d6/6176aaa1f6588378f5a5a4a9c6ad50a36824e902b2f844ca8de7f1b0c4a7/zope.interface-6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd", size = 241703 }, + { url = "https://files.pythonhosted.org/packages/4f/20/94d4f221989b4bbdd09004b2afb329958e776b7015b7ea8bc915327e195a/zope.interface-6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41", size = 247078 }, + { url = "https://files.pythonhosted.org/packages/97/7e/b790b4ab9605010816a91df26a715f163e228d60eb36c947c3118fb65190/zope.interface-6.1-cp310-cp310-win_amd64.whl", hash = "sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f", size = 204155 }, + { url = "https://files.pythonhosted.org/packages/4a/0b/1d8817b8a3631384a26ff7faa4c1f3e6726f7e4950c3442721cfef2c95eb/zope.interface-6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1", size = 202441 }, + { url = "https://files.pythonhosted.org/packages/3e/1f/43557bb2b6e8537002a5a26af9b899171e26ddfcdf17a00ff729b00c036b/zope.interface-6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736", size = 202530 }, + { url = "https://files.pythonhosted.org/packages/37/a1/5d2b265f4b7371630cad5873d0873965e35ca3de993d11b9336c720f7259/zope.interface-6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605", size = 249584 }, + { url = "https://files.pythonhosted.org/packages/8b/6d/547bfa7465e5b296adba0aff5c7ace1150f2a9e429fbf6c33d6618275162/zope.interface-6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8", size = 243737 }, + { url = "https://files.pythonhosted.org/packages/db/5f/46946b588c43eb28efe0e46f4cf455b1ed8b2d1ea62a21b0001c6610662f/zope.interface-6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de", size = 249104 }, + { url = "https://files.pythonhosted.org/packages/6c/9c/9d3c0e7e5362ea59da3c42b3b2b9fc073db433a0fe3bc6cae0809ccec395/zope.interface-6.1-cp311-cp311-win_amd64.whl", hash = "sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1", size = 204155 }, + { url = "https://files.pythonhosted.org/packages/3c/91/68a0bbc97c2554f87d39572091954e94d043bcd83897cd6a779ca85cb5cc/zope.interface-6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a", size = 202757 }, + { url = "https://files.pythonhosted.org/packages/e1/84/850092a8ab7e87a3ea615daf3f822f7196c52592e3e92f264621b4cfe5a2/zope.interface-6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7", size = 202654 }, + { url = "https://files.pythonhosted.org/packages/57/23/508f7f79619ae4e025f5b264a9283efc3c805ed4c0ad75cb28c091179ced/zope.interface-6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d", size = 254400 }, + { url = "https://files.pythonhosted.org/packages/7c/0d/db0ccf0d12767015f23b302aebe98d5eca218aaadc70c2e3908b85fecd2a/zope.interface-6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff", size = 248853 }, + { url = "https://files.pythonhosted.org/packages/fd/4f/8e80173ebcdefe0ff4164444c22b171cf8bd72533026befc2adf079f3ac8/zope.interface-6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0", size = 255127 }, + { url = "https://files.pythonhosted.org/packages/0f/d5/81f9789311d9773a02ed048af7452fc6cedce059748dba956c1dc040340a/zope.interface-6.1-cp312-cp312-win_amd64.whl", hash = "sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b", size = 204268 }, +] diff --git a/misc/release/pump-version.sh b/misc/release/pump-version.sh index b90d265bf9..e7fbae34dc 100755 --- a/misc/release/pump-version.sh +++ b/misc/release/pump-version.sh @@ -73,7 +73,7 @@ if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then npm --prefix web i --package-lock-only npm --prefix e2e version "$SERVER_PUMP" npm --prefix e2e i --package-lock-only - poetry --directory machine-learning version "$SERVER_PUMP" + uvx --from=toml-cli toml set --toml-path=pyproject.toml project.version "$SERVER_PUMP" fi if [ "$CURRENT_MOBILE" != "$NEXT_MOBILE" ]; then diff --git a/mobile/.fvmrc b/mobile/.fvmrc index 691c22dd17..b94131913e 100644 --- a/mobile/.fvmrc +++ b/mobile/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.24.5" + "flutter": "3.29.1" } diff --git a/mobile/.gitignore b/mobile/.gitignore index 0db731f953..5f6e15354f 100644 --- a/mobile/.gitignore +++ b/mobile/.gitignore @@ -45,6 +45,7 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release +android/app/.cxx # Fastlane ios/fastlane/report.xml @@ -55,4 +56,4 @@ default.isar.lock libisar.so # FVM Version -.fvm/ +.fvm/ \ No newline at end of file diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml index 2e74f6153a..085449756d 100644 --- a/mobile/analysis_options.yaml +++ b/mobile/analysis_options.yaml @@ -67,9 +67,10 @@ custom_lint: - lib/entities/*.entity.dart - lib/repositories/{album,asset,backup,database,etag,exif_info,user,timeline,partner}.repository.dart - lib/infrastructure/entities/*.entity.dart - - lib/infrastructure/repositories/{store,db,log}.repository.dart + - lib/infrastructure/repositories/*.repository.dart - lib/providers/infrastructure/db.provider.dart # acceptable exceptions for the time being (until Isar is fully replaced) + - lib/providers/app_life_cycle.provider.dart - integration_test/test_utils/general_helper.dart - lib/main.dart - lib/pages/album/album_asset_selection.page.dart @@ -88,10 +89,15 @@ custom_lint: allowed: # required / wanted - lib/repositories/*_api.repository.dart + - lib/infrastructure/repositories/*_api.repository.dart + - lib/infrastructure/utils/*.converter.dart # acceptable exceptions for the time being - lib/entities/{album,asset,exif_info,user}.entity.dart # to convert DTOs to entities + - lib/infrastructure/utils/*.converter.dart - lib/utils/{image_url_builder,openapi_patching}.dart # utils are fine - test/modules/utils/openapi_patching_test.dart # filename is self-explanatory... + - lib/domain/services/sync_stream.service.dart # Making sure to comply with the type from database + # refactor - lib/models/map/map_marker.model.dart - lib/models/server_info/server_{config,disk_info,features,version}.model.dart diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index ac57884eef..eb81dc267b 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -93,9 +93,9 @@ - + diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/BackupWorker.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/BackupWorker.kt index c4c87ff519..0fb75b002c 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/BackupWorker.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/BackupWorker.kt @@ -221,7 +221,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct .setContentTitle(title) .setTicker(title) .setContentText(content) - .setSmallIcon(R.mipmap.ic_launcher) + .setSmallIcon(R.drawable.notification_icon) .build() notificationManager.notify(individualTag, NOTIFICATION_ERROR_ID, notification) } @@ -260,7 +260,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct var builder = if (isDetail) notificationDetailBuilder else notificationBuilder if (builder == null) { builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID) - .setSmallIcon(R.mipmap.ic_launcher) + .setSmallIcon(R.drawable.notification_icon) .setOnlyAlertOnce(true) .setOngoing(true) if (isDetail) { diff --git a/mobile/android/fastlane/Fastfile b/mobile/android/fastlane/Fastfile index 74ee2d415b..1356c468ab 100644 --- a/mobile/android/fastlane/Fastfile +++ b/mobile/android/fastlane/Fastfile @@ -35,8 +35,8 @@ platform :android do task: 'bundle', build_type: 'Release', properties: { - "android.injected.version.code" => 186, - "android.injected.version.name" => "1.128.0", + "android.injected.version.code" => 187, + "android.injected.version.name" => "1.129.0", } ) upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') diff --git a/mobile/assets/i18n/ca-CA.json b/mobile/assets/i18n/ca.json similarity index 63% rename from mobile/assets/i18n/ca-CA.json rename to mobile/assets/i18n/ca.json index cd7fd0e12b..e41ab4b183 100644 --- a/mobile/assets/i18n/ca-CA.json +++ b/mobile/assets/i18n/ca.json @@ -1,24 +1,35 @@ { - "action_common_cancel": "Cancel·la", - "action_common_update": "Actualitza", - "add_to_album_bottom_sheet_added": "S'ha afegit a {album}", - "add_to_album_bottom_sheet_already_exists": "Ja es troba en {album}", + "action_common_back": "Enrere", + "action_common_cancel": "Cancel·lar", + "action_common_clear": "Buida", + "action_common_confirm": "Confirmar", + "action_common_save": "Desa", + "action_common_select": "Selecciona", + "action_common_update": "Actualitzar", + "add_a_name": "Afegeix un nom", + "add_endpoint": "afegir endpoint", + "add_to_album_bottom_sheet_added": "Added to {album}", + "add_to_album_bottom_sheet_already_exists": "Already in {album}", "advanced_settings_log_level_title": "Log level: {}", "advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.", "advanced_settings_prefer_remote_title": "Prefereix imatges remotes", + "advanced_settings_proxy_headers_subtitle": "Definiu les capçaleres de proxy que Immich per enviar amb cada sol·licitud de xarxa", + "advanced_settings_proxy_headers_title": "Capçaleres de proxy", "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates", - "advanced_settings_tile_subtitle": "Configuració avançada", + "advanced_settings_tile_subtitle": "Advanced user's settings", "advanced_settings_tile_title": "Avançat", "advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting", "advanced_settings_troubleshooting_title": "Resolució de problemes", "album_info_card_backup_album_excluded": "Exclosos", "album_info_card_backup_album_included": "Inclosos", - "album_thumbnail_card_item": "1 element", - "album_thumbnail_card_items": "{} elements", - "album_thumbnail_card_shared": " · Compartit", + "albums": "Àlbums", + "album_thumbnail_card_item": "1 item", + "album_thumbnail_card_items": "{} items", + "album_thumbnail_card_shared": " · Shared", "album_thumbnail_owned": "Owned", "album_thumbnail_shared_by": "Compartit per {}", + "album_viewer_appbar_delete_confirm": "Confirmes que vols suprimir aquest àlbum del teu compte?", "album_viewer_appbar_share_delete": "Esborra l'àlbum", "album_viewer_appbar_share_err_delete": "Error al esborrar l'àlbum", "album_viewer_appbar_share_err_leave": "Error al sortir de l'àlbum", @@ -28,25 +39,39 @@ "album_viewer_appbar_share_remove": "Treu de l'àlbum", "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Afegeix usuaris", + "all": "Tot", "all_people_page_title": "Persones", "all_videos_page_title": "Vídeos", "app_bar_signout_dialog_content": "Are you sure you want to sign out?", "app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_title": "Sign out", + "archived": "Arxivat", "archive_page_no_archived_assets": "No s'ha trobat res arxivat", "archive_page_title": "Arxiu({})", - "asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping", - "asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping", - "asset_list_group_by_sub_title": "Group by", + "asset_action_delete_err_read_only": "No es poden esborrar el fitxer(s) de només lectura, ometent", + "asset_action_share_err_offline": "No s'ha pogut obtenir el fitxer(s) sense connexió, ometent", + "asset_list_group_by_sub_title": "Agrupar per", "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", "asset_list_layout_settings_group_automatically": "Automàtic", "asset_list_layout_settings_group_by": "Group assets by", "asset_list_layout_settings_group_by_month": "Month", "asset_list_layout_settings_group_by_month_day": "Month + day", - "asset_list_layout_sub_title": "Layout", + "asset_list_layout_sub_title": "Disseny", "asset_list_settings_subtitle": "Photo grid layout settings", "asset_list_settings_title": "Photo Grid", - "asset_viewer_settings_title": "Asset Viewer", + "asset_restored_successfully": "Element recuperat correctament", + "assets_deleted_permanently": "{} element(s) esborrats permanentment", + "assets_deleted_permanently_from_server": "{} element(s) esborrats permanentment del servidor d'Immich", + "assets_removed_permanently_from_device": "{} element(s) esborrat permanentment del dispositiu", + "assets_restored_successfully": "{} element(s) recuperats correctament", + "assets_trashed": "{} element(s) enviat a la paperera", + "assets_trashed_from_server": "{} element(s) enviat a la paperera del servidor d'Immich", + "asset_viewer_settings_subtitle": "Gestiona la configuració del visualitzador de la galeria", + "asset_viewer_settings_title": "Visor d'arxius", + "automatic_endpoint_switching_subtitle": "Connecteu-vos localment a través de la Wi-Fi designada quan estigui disponible i utilitzeu connexions alternatives en altres llocs", + "automatic_endpoint_switching_title": "Canvi automàtic d'URL", + "background_location_permission": "Permís d'ubicació en segon pla", + "background_location_permission_content": "Per canviar de xarxa quan s'executa en segon pla, Immich ha de *sempre* tenir accés a la ubicació precisa perquè l'aplicació pugui llegir el nom de la xarxa Wi-Fi", "backup_album_selection_page_albums_device": "Àlbums al dispositiu ({})", "backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude", "backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.", @@ -108,9 +133,11 @@ "backup_info_card_assets": "elements", "backup_manual_cancelled": "Cancelled", "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after some time", + "backup_manual_in_progress": "Upload already in progress. Try after sometime", "backup_manual_success": "Success", "backup_manual_title": "Upload status", + "backup_options_page_title": "Opcions de còpia de seguretat", + "backup_setting_subtitle": "Gestiona la configuració de càrrega en segon pla i en primer pla", "cache_settings_album_thumbnails": "Library page thumbnails ({} assets)", "cache_settings_clear_cache_button": "Clear cache", "cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.", @@ -129,73 +156,136 @@ "cache_settings_tile_subtitle": "Control the local storage behaviour", "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Configuració de la memòria cau", + "cancel": "Cancel·la", + "canceled": "Cancel·lat", + "change_display_order": "Canvia l'ordre de visualització", "change_password_form_confirm_password": "Confirma la contrasenya", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_new_password": "New Password", "change_password_form_password_mismatch": "Passwords do not match", "change_password_form_reenter_new_password": "Re-enter New Password", + "check_corrupt_asset_backup": "Comprovar les còpies de seguretat corruptes", + "check_corrupt_asset_backup_button": "Realitzar comprovació", + "check_corrupt_asset_backup_description": "Executeu aquesta comprovació només mitjançant Wi-Fi i un cop s'hagi fet una còpia de seguretat de tots els actius. El procediment pot trigar uns minuts.", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Introdueix la contrasenya", + "client_cert_import": "Importar", + "client_cert_import_success_msg": "S'ha importat el certificat del client", + "client_cert_invalid_msg": "Fitxer de certificat no vàlid o contrasenya incorrecta", + "client_cert_remove": "Eliminar", + "client_cert_remove_msg": "S'ha eliminat el certificat del client", + "client_cert_subtitle": "Només admet el format PKCS12 (.p12, .pfx). La importació/eliminació de certificats només està disponible abans d'iniciar sessió", + "client_cert_title": "Certificat de client SSL", "common_add_to_album": "Add to album", "common_change_password": "Change Password", "common_create_new_album": "Crea un àlbum nou", "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", "common_shared": "Compartit", + "completed": "Completat", + "contextual_search": "Sortida del sol a la platja", "control_bottom_app_bar_add_to_album": "Add to album", "control_bottom_app_bar_album_info": "{} elements", "control_bottom_app_bar_album_info_shared": "{} elements - Compartits", "control_bottom_app_bar_archive": "Arxiu", "control_bottom_app_bar_create_new_album": "Crea un àlbum nou", "control_bottom_app_bar_delete": "Esborra", - "control_bottom_app_bar_delete_from_immich": "Delete from Immich", - "control_bottom_app_bar_delete_from_local": "Delete from device", + "control_bottom_app_bar_delete_from_immich": "Suprimeix del Immich", + "control_bottom_app_bar_delete_from_local": "Suprimeix del dispositiu", + "control_bottom_app_bar_download": "Descarrega", + "control_bottom_app_bar_edit": "Edita", "control_bottom_app_bar_edit_location": "Edit Location", "control_bottom_app_bar_edit_time": "Edit Date & Time", "control_bottom_app_bar_favorite": "Preferit", "control_bottom_app_bar_share": "Share", "control_bottom_app_bar_share_to": "Share To", "control_bottom_app_bar_stack": "Stack", - "control_bottom_app_bar_trash_from_immich": "Move to Trash", + "control_bottom_app_bar_trash_from_immich": "Mou a paperera", "control_bottom_app_bar_unarchive": "Desarxiva", "control_bottom_app_bar_unfavorite": "Unfavorite", "control_bottom_app_bar_upload": "Upload", + "create_album": "Crear àlbum", "create_album_page_untitled": "Untitled", + "create_new": "CREAR NOU", "create_shared_album_page_create": "Create", "create_shared_album_page_share": "Comparteix", "create_shared_album_page_share_add_assets": "AFEGEIX ELEMENTS", "create_shared_album_page_share_select_photos": "Escull fotografies", + "crop": "Retalla", "curated_location_page_title": "Localitzacions", "curated_object_page_title": "Coses", + "current_server_address": "Current server address", "daily_title_text_date": "E, MMM dd", "daily_title_text_date_year": "E, MMM dd, yyyy", "date_format": "E, LLL d, y • h:mm a", "delete_dialog_alert": "These items will be permanently deleted from Immich and from your device", - "delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server", - "delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device", - "delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server", + "delete_dialog_alert_local": "Aquests elements s'eliminaran permanentment del vostre dispositiu, però encara estaran disponibles al servidor Immich", + "delete_dialog_alert_local_non_backed_up": "Alguns dels elements no tenen còpia de seguretat a Immich i s'eliminaran permanentment del dispositiu", + "delete_dialog_alert_remote": "Aquests elements s'eliminaran permanentment del servidor Immich", "delete_dialog_cancel": "Cancel·la", "delete_dialog_ok": "Esborra", - "delete_dialog_ok_force": "Delete Anyway", + "delete_dialog_ok_force": "Suprimeix de totes maneres", "delete_dialog_title": "Esborra permanentment", - "delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only", - "delete_local_dialog_ok_force": "Delete Anyway", + "delete_local_dialog_ok_backed_up_only": "Esborrar només les que tinguin còpia de seguretat", + "delete_local_dialog_ok_force": "Suprimeix de totes maneres", "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", "delete_shared_link_dialog_title": "Delete Shared Link", "description_input_hint_text": "Afegeix descripció...", "description_input_submit_error": "Error updating description, check the log for more details", - "edit_date_time_dialog_date_time": "Date and Time", - "edit_date_time_dialog_timezone": "Timezone", - "edit_location_dialog_title": "Location", + "description_search": "Jornada de senderisme a Sapa", + "download_canceled": "Descàrrega cancel·lada", + "download_complete": "Descàrrega completada", + "download_enqueue": "Descàrrega en cua", + "download_error": "Error de descàrrega", + "download_failed": "Descàrrega ha fallat", + "download_filename": "arxiu: {}", + "download_finished": "Descàrrega acabada", + "downloading": "Descarregant...", + "downloading_media": "Descàrrega multimèdia", + "download_notfound": "No s'ha trobat la descàrrega", + "download_paused": "Descàrrega pausada", + "download_started": "Descàrrega ha començat", + "download_sucess": "Descarregat amb èxit", + "download_sucess_android": "El multimedia s'ha descarregat a DCIM/Immich", + "download_waiting_to_retry": "Esperant per tornar-ho a intentar", + "edit_date_time_dialog_date_time": "Data i Hora", + "edit_date_time_dialog_search_timezone": "Cerca zona horària...", + "edit_date_time_dialog_timezone": "Zona horària", + "edit_image_title": "Editar", + "edit_location_dialog_title": "Ubicació", + "end_date": "Data final", + "enqueued": "En cua", + "enter_wifi_name": "Introdueix el nom de WiFi", + "error_change_sort_album": "No s'ha pogut canviar l'ordre d'ordenació dels àlbums", + "error_saving_image": "Error: {}", "exif_bottom_sheet_description": "Afegeix descripció", "exif_bottom_sheet_details": "DETALLS", "exif_bottom_sheet_location": "UBICACIÓ", "exif_bottom_sheet_location_add": "Add a location", - "exif_bottom_sheet_people": "PEOPLE", - "exif_bottom_sheet_person_add_person": "Add name", + "exif_bottom_sheet_people": "PERSONES", + "exif_bottom_sheet_person_add_person": "Afegir nom", "experimental_settings_new_asset_list_subtitle": "Work in progress", "experimental_settings_new_asset_list_title": "Enable experimental photo grid", "experimental_settings_subtitle": "Use at your own risk!", "experimental_settings_title": "Experimental", + "external_network": "Xarxa externa", + "external_network_sheet_info": "Quan no estigui a la xarxa WiFi preferida, l'aplicació es connectarà al servidor mitjançant el primer dels URL següents a què pot arribar, començant de dalt a baix.", + "failed": "Fallat", + "favorites": "Favorits", "favorites_page_no_favorites": "No s'han trobat preferits", "favorites_page_title": "Favorites", + "filename_search": "Nom o extensió del fitxer", + "filter": "Filtrar", + "get_wifiname_error": "No s'ha pogut obtenir el nom de la Wi-Fi. Assegureu-vos que heu concedit els permisos necessaris i que esteu connectat a una xarxa Wi-Fi", + "grant_permission": "Grant permission", + "haptic_feedback_switch": "Activa la resposta hàptica", + "haptic_feedback_title": "Resposta Hàptica", + "header_settings_add_header_tip": "Afegeix Capçalera", + "header_settings_field_validator_msg": "El valor no pot estar buit", + "header_settings_header_name_input": "Nom de la capçalera", + "header_settings_header_value_input": "Valor de la capçalera", + "header_settings_page_title": "Capçaleres de proxy", + "headers_settings_tile_subtitle": "Definiu les capçaleres de proxy que l'aplicació hauria d'enviar amb cada sol·licitud de xarxa", + "headers_settings_tile_title": "Custom proxy headers", "home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.", "home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping", "home_page_add_to_album_success": "Added {added} assets to album {album}.", @@ -204,16 +294,22 @@ "home_page_archive_err_partner": "Can not archive partner assets, skipping", "home_page_building_timeline": "Building the timeline", "home_page_delete_err_partner": "Can not delete partner assets, skipping", - "home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping", + "home_page_delete_remote_err_local": "Elements locals a la selecció d'eliminació remota, ometent", "home_page_favorite_err_local": "Can not favorite local assets yet, skipping", "home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping", "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).", "home_page_share_err_local": "Can not share local assets via link, skipping", "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", + "ignore_icloud_photos": "Ignora fotos d'iCloud", + "ignore_icloud_photos_description": "Les fotos emmagatzemades a iCloud no es penjaran al servidor Immich", + "image_saved_successfully": "Imatge desada", "image_viewer_page_state_provider_download_error": "Download Error", - "image_viewer_page_state_provider_download_started": "Download Started", + "image_viewer_page_state_provider_download_started": "Descàrrega començada", "image_viewer_page_state_provider_download_success": "Download Success", "image_viewer_page_state_provider_share_error": "Share Error", + "invalid_date": "Data invàlida", + "invalid_date_format": "Format de data invàlid", + "library": "Llibreria", "library_page_albums": "Àlbums", "library_page_archive": "Arxiu", "library_page_device_albums": "Àlbums al Dispositiu", @@ -226,13 +322,17 @@ "library_page_sort_most_oldest_photo": "Oldest photo", "library_page_sort_most_recent_photo": "Most recent photo", "library_page_sort_title": "Album title", - "location_picker_choose_on_map": "Choose on map", - "location_picker_latitude": "Latitude", - "location_picker_latitude_error": "Enter a valid latitude", + "local_network": "Xarxa local", + "local_network_sheet_info": "L'aplicació es connectarà al servidor mitjançant aquest URL quan utilitzeu la xarxa Wi-Fi especificada", + "location_permission": "Permís d'ubicació", + "location_permission_content": "Per utilitzar la funció de canvi automàtic, Immich necessita un permís de ubicació precisa perquè pugui llegir el nom de la xarxa WiFi actual", + "location_picker_choose_on_map": "Escollir en el mapa", + "location_picker_latitude": "Latitud", + "location_picker_latitude_error": "Introdueix una latitud vàlida", "location_picker_latitude_hint": "Enter your latitude here", - "location_picker_longitude": "Longitude", - "location_picker_longitude_error": "Enter a valid longitude", - "location_picker_longitude_hint": "Enter your longitude here", + "location_picker_longitude": "Longitud", + "location_picker_longitude_error": "Introdueix una longitud vàlida", + "location_picker_longitude_hint": "Introdueix aquí la longitud", "login_disabled": "Login has been disabled", "login_form_api_exception": "API exception. Please check the server URL and try again.", "login_form_back_button_text": "Back", @@ -258,12 +358,12 @@ "login_form_server_error": "Could not connect to server.", "login_password_changed_error": "There was an error updating your password", "login_password_changed_success": "Password updated successfully", - "map_assets_in_bound": "{} photo", - "map_assets_in_bounds": "{} photos", + "map_assets_in_bound": "{} foto", + "map_assets_in_bounds": "{} fotos", "map_cannot_get_user_location": "Cannot get user's location", "map_location_dialog_cancel": "Cancel", "map_location_dialog_yes": "Yes", - "map_location_picker_page_use_location": "Use this location", + "map_location_picker_page_use_location": "Utilitzar aquesta ubicació", "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", "map_location_service_disabled_title": "Location Service disabled", "map_no_assets_in_bounds": "No photos in this area", @@ -271,32 +371,44 @@ "map_no_location_permission_title": "Location Permission denied", "map_settings_dark_mode": "Dark mode", "map_settings_date_range_option_all": "All", - "map_settings_date_range_option_day": "Past 24 hours", - "map_settings_date_range_option_days": "Past {} days", - "map_settings_date_range_option_year": "Past year", - "map_settings_date_range_option_years": "Past {} years", + "map_settings_date_range_option_day": "Últimes 24 hores", + "map_settings_date_range_option_days": "Darrers {} dies", + "map_settings_date_range_option_year": "Any passat", + "map_settings_date_range_option_years": "Darrers {} anys", "map_settings_dialog_cancel": "Cancel", "map_settings_dialog_save": "Save", "map_settings_dialog_title": "Map Settings", "map_settings_include_show_archived": "Include Archived", + "map_settings_include_show_partners": "Incloure companys", "map_settings_only_relative_range": "Date range", "map_settings_only_show_favorites": "Show Favorite Only", - "map_settings_theme_settings": "Map Theme", + "map_settings_theme_settings": "Tema del Mapa", "map_zoom_to_see_photos": "Zoom out to see photos", - "memories_all_caught_up": "All caught up", - "memories_check_back_tomorrow": "Check back tomorrow for more memories", - "memories_start_over": "Start Over", - "memories_swipe_to_close": "Swipe up to close", + "memories_all_caught_up": "Posat al dia", + "memories_check_back_tomorrow": "Torna demà per veure més records", + "memories_start_over": "Torna a començar", + "memories_swipe_to_close": "Llisca per tancar", + "memories_year_ago": "Fa un any", + "memories_years_ago": "Fa {} anys", "monthly_title_text_date_format": "MMMM y", "motion_photos_page_title": "Motion Photos", - "multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping", - "multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping", + "multiselect_grid_edit_date_time_err_read_only": "No es pot canviar la data del fitxer(s) de només lectura, ometent", + "multiselect_grid_edit_gps_err_read_only": "No es pot canviar la localització de fitxers de només lectura. Saltant.", + "my_albums": "Els meus àlbums", + "networking_settings": "Xarxes", + "networking_subtitle": "Gestiona la configuració del endpoint del servidor", + "no_assets_to_show": "No hi ha elements per mostrar", + "no_name": "Sense nom", "notification_permission_dialog_cancel": "Cancel·la", "notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.", "notification_permission_dialog_settings": "Configuració", "notification_permission_list_tile_content": "Grant permission to enable notifications.", "notification_permission_list_tile_enable_button": "Activa les notificacions", "notification_permission_list_tile_title": "Notification Permission", + "not_selected": "No seleccionat", + "on_this_device": "En aquest dispositiu", + "partner_list_user_photos": "fotos de {user}", + "partner_list_view_all": "Veure tot", "partner_page_add_partner": "Afegeix company", "partner_page_empty_message": "Your photos are not yet shared with any partner.", "partner_page_no_more_users": "No more users to add", @@ -306,6 +418,9 @@ "partner_page_stop_sharing_content": "{} will no longer be able to access your photos.", "partner_page_stop_sharing_title": "Stop sharing your photos?", "partner_page_title": "Company", + "partners": "Companys", + "paused": "Pausat", + "people": "Persones", "permission_onboarding_back": "Back", "permission_onboarding_continue_anyway": "Continue anyway", "permission_onboarding_get_started": "Get started", @@ -316,7 +431,9 @@ "permission_onboarding_permission_granted": "Permission granted! You are all set.", "permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.", "permission_onboarding_request": "Immich requires permission to view your photos and videos.", - "preferences_settings_title": "Preferences", + "places": "Llocs", + "preferences_settings_subtitle": "Gestiona les preferències de l'aplicació", + "preferences_settings_title": "Preferències", "profile_drawer_app_logs": "Logs", "profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.", "profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.", @@ -328,9 +445,44 @@ "profile_drawer_settings": "Settings", "profile_drawer_sign_out": "Tanca la sessió", "profile_drawer_trash": "Trash", + "recently_added": "Afegit recentment", "recently_added_page_title": "Recently Added", - "scaffold_body_error_occurred": "Error occurred", + "save": "Desa", + "save_to_gallery": "Desa a galeria", + "scaffold_body_error_occurred": "S'ha produït un error", + "search_albums": "Cerca àlbums", "search_bar_hint": "Search your photos", + "search_filter_apply": "Aplicar filtre", + "search_filter_camera": "Càmera", + "search_filter_camera_make": "Marca", + "search_filter_camera_model": "Model", + "search_filter_camera_title": "Selecciona el tipus de càmera", + "search_filter_contextual": "Cerca per contexte", + "search_filter_date": "Data", + "search_filter_date_interval": "{start} a {end}", + "search_filter_date_title": "Selecciona un rang de dates", + "search_filter_description": "Cerca per descripció", + "search_filter_display_option_archive": "Arxivat", + "search_filter_display_option_favorite": "Favorit", + "search_filter_display_option_not_in_album": "No en àlbum", + "search_filter_display_options": "Opcions de Visualització", + "search_filter_display_options_title": "Opcions de visualització", + "search_filter_filename": "Cerca pel nom del fitxer", + "search_filter_location": "Ubicació", + "search_filter_location_city": "Ciutat", + "search_filter_location_country": "País", + "search_filter_location_state": "Estat", + "search_filter_location_title": "Selecciona l'ubicació", + "search_filter_media_type": "Tipus de multimèdia", + "search_filter_media_type_all": "Tot", + "search_filter_media_type_image": "Imatge", + "search_filter_media_type_title": "Selecciona tipus de multimèdia", + "search_filter_media_type_video": "Vídeo", + "search_filter_people": "Persones", + "search_filter_people_hint": "Filtra persones", + "search_filter_people_title": "Selecciona persones", + "search_no_more_result": "No més resultats", + "search_no_result": "No s'han trobat resultats, proveu un terme de cerca o una combinació diferents", "search_page_categories": "Categories", "search_page_favorites": "Preferides", "search_page_motion_photos": "Fotografies animades", @@ -347,6 +499,7 @@ "search_page_places": "Llocs", "search_page_recently_added": "Afegit recentment", "search_page_screenshots": "Captures de pantalla", + "search_page_search_photos_videos": "Cerca les teves fotos i vídeos", "search_page_selfies": "Autofotos", "search_page_things": "Coses", "search_page_videos": "Videos", @@ -359,6 +512,7 @@ "select_additional_user_for_sharing_page_suggestions": "Suggeriments", "select_user_for_sharing_page_err_album": "Error al crear l'àlbum", "select_user_for_sharing_page_share_suggestions": "Suggestions", + "server_endpoint": "Endpoint de Servidor", "server_info_box_app_version": "Versió de l'aplicació", "server_info_box_latest_release": "Latest Version", "server_info_box_server_url": "Server URL", @@ -368,6 +522,10 @@ "setting_image_viewer_original_title": "Load original image", "setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.", "setting_image_viewer_preview_title": "Load preview image", + "setting_image_viewer_title": "Imatges", + "setting_languages_apply": "Aplicar", + "setting_languages_subtitle": "Canvia el llenguatge de l'aplicació", + "setting_languages_title": "Idiomes", "setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}", "setting_notifications_notify_hours": "{} hours", "setting_notifications_notify_immediately": "immediately", @@ -382,9 +540,15 @@ "setting_notifications_total_progress_title": "Show background backup total progress", "setting_pages_app_bar_settings": "Settings", "settings_require_restart": "Please restart Immich to apply this setting", + "setting_video_viewer_looping_subtitle": "Habilita per reproduir automàticament un vídeo al visualitzador de detalls.", + "setting_video_viewer_looping_title": "Bucle", + "setting_video_viewer_original_video_subtitle": "Quan reproduïu un vídeo des del servidor, reproduïu l'original encara que hi hagi una transcodificació disponible. Pot conduir a l'amortització. Els vídeos disponibles localment es reprodueixen en qualitat original independentment d'aquesta configuració.", + "setting_video_viewer_original_video_title": "Força el vídeo original", + "setting_video_viewer_title": "Vídeos", "share_add": "Afegeix", "share_add_photos": "Afegeix fotografies", "share_add_title": "Afegeix un títol", + "share_assets_selected": "{} seleccionats", "share_create_album": "Crea un àlbum", "shared_album_activities_input_disable": "Comment is disabled", "shared_album_activities_input_hint": "Say something", @@ -398,6 +562,7 @@ "shared_album_section_people_owner_label": "Owner", "shared_album_section_people_title": "PEOPLE", "share_dialog_preparing": "Preparing...", + "shared_intent_upload_button_progress_text": "{} / {} Pujat", "shared_link_app_bar_title": "Shared Links", "shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -412,13 +577,15 @@ "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", "shared_link_edit_expire_after": "Expire after", - "shared_link_edit_expire_after_option_day": "1 day", - "shared_link_edit_expire_after_option_days": "{} days", - "shared_link_edit_expire_after_option_hour": "1 hour", - "shared_link_edit_expire_after_option_hours": "{} hours", - "shared_link_edit_expire_after_option_minute": "1 minute", - "shared_link_edit_expire_after_option_minutes": "{} minutes", + "shared_link_edit_expire_after_option_day": "1 dia", + "shared_link_edit_expire_after_option_days": "{} dies", + "shared_link_edit_expire_after_option_hour": "1 hora", + "shared_link_edit_expire_after_option_hours": "{} hores", + "shared_link_edit_expire_after_option_minute": "1 minut", + "shared_link_edit_expire_after_option_minutes": "{} minuts", + "shared_link_edit_expire_after_option_months": "{} mesos", "shared_link_edit_expire_after_option_never": "Never", + "shared_link_edit_expire_after_option_year": "any {}", "shared_link_edit_password": "Password", "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", @@ -426,65 +593,89 @@ "shared_link_empty": "You don't have any shared links", "shared_link_error_server_url_fetch": "Cannot fetch the server url", "shared_link_expired": "Expired", - "shared_link_expires_day": "Expires in {} day", - "shared_link_expires_days": "Expires in {} days", - "shared_link_expires_hour": "Expires in {} hour", - "shared_link_expires_hours": "Expires in {} hours", - "shared_link_expires_minute": "Expires in {} minute", + "shared_link_expires_day": "Caduca d'aquí a {} dia", + "shared_link_expires_days": "Caduca d'aquí a {} dies", + "shared_link_expires_hour": "Caduca d'aquí a {} hora", + "shared_link_expires_hours": "Caduca d'aquí a {} hores", + "shared_link_expires_minute": "Caduca d'aquí a {} minut", "shared_link_expires_minutes": "Expires in {} minutes", "shared_link_expires_never": "Expires ∞", - "shared_link_expires_second": "Expires in {} second", + "shared_link_expires_second": "Caduca d'aquí a {} segon", "shared_link_expires_seconds": "Expires in {} seconds", - "shared_link_info_chip_download": "Baixa", + "shared_link_individual_shared": "Individual compartit", + "shared_link_info_chip_download": "Download", "shared_link_info_chip_metadata": "EXIF", "shared_link_info_chip_upload": "Puja", "shared_link_manage_links": "Manage Shared links", - "share_done": "Fet", + "shared_link_public_album": "Àlbum públic", + "shared_links": "Enllaços compartits", + "share_done": "Done", + "shared_with_me": "Compartit amb mi", "share_invite": "Convida a l'àlbum", - "sharing_page_album": "Àlbums compartits", + "sharing_page_album": "Shared albums", "sharing_page_description": "Create shared albums to share photos and videos with people in your network.", "sharing_page_empty_list": "EMPTY LIST", "sharing_silver_appbar_create_shared_album": "Crea àlbum compartit", "sharing_silver_appbar_shared_links": "Shared links", "sharing_silver_appbar_share_partner": "Comparteix amb un company", - "tab_controller_nav_library": "Bibilioteca", - "tab_controller_nav_photos": "Fotos", + "start_date": "Data inicial", + "sync": "Sincronitzar", + "sync_albums": "Sincronitzar àlbums", + "sync_albums_manual_subtitle": "Sincronitza tots els vídeos i fotos penjats amb els àlbums de còpia de seguretat seleccionats", + "sync_upload_album_setting_subtitle": "Creeu i pugeu les seves fotos i vídeos als àlbums seleccionats a Immich", + "tab_controller_nav_library": "Library", + "tab_controller_nav_photos": "Fotografies", "tab_controller_nav_search": "Cerca", "tab_controller_nav_sharing": "Compartint", "theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles", "theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})", - "theme_setting_dark_mode_switch": "Modes fosc", + "theme_setting_colorful_interface_subtitle": "Apliqueu color primari a les superfícies de fons.", + "theme_setting_colorful_interface_title": "Interfície colorida", + "theme_setting_dark_mode_switch": "Dark mode", "theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer", "theme_setting_image_viewer_quality_title": "Image viewer quality", + "theme_setting_primary_color_subtitle": "Trieu un color per a les accions i els accents principals.", + "theme_setting_primary_color_title": "Color primari", + "theme_setting_system_primary_color_title": "Utilitza color de sistema", "theme_setting_system_theme_switch": "Automatic (Follow system setting)", "theme_setting_theme_subtitle": "Choose the app's theme setting", - "theme_setting_theme_title": "Tema", + "theme_setting_theme_title": "Theme", "theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load", "theme_setting_three_stage_loading_title": "Enable three-stage loading", "translated_text_options": "Options", - "trash_page_delete": "Elimina", - "trash_page_delete_all": "Elimina-ho tot", - "trash_page_empty_trash_btn": "Buida la paperera", + "trash": "Paperera", + "trash_emptied": "Paperera buidada", + "trash_page_delete": "Delete", + "trash_page_delete_all": "Delete All", + "trash_page_empty_trash_btn": "Empty trash", "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", "trash_page_empty_trash_dialog_ok": "Ok", "trash_page_info": "Trashed items will be permanently deleted after {} days", "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Recupera", - "trash_page_restore_all": "Recupera-ho tot", + "trash_page_restore": "Restore", + "trash_page_restore_all": "Restore All", "trash_page_select_assets_btn": "Select assets", "trash_page_select_btn": "Select", "trash_page_title": "Trash ({})", - "upload_dialog_cancel": "Cancel·la", + "upload": "Puja", + "upload_dialog_cancel": "Cancel", "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", "upload_dialog_ok": "Upload", "upload_dialog_title": "Upload Asset", + "uploading": "Pujant", + "upload_to_immich": "Puja a Immich ({})", + "use_current_connection": "utilitzar la connexió actual", + "validate_endpoint_error": "Per favor introdueix un URL vàlid", "version_announcement_overlay_ack": "Acknowledge", "version_announcement_overlay_release_notes": "release notes", "version_announcement_overlay_text_1": "Hi friend, there is a new release of", "version_announcement_overlay_text_2": "please take your time to visit the ", "version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.", "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89", + "videos": "Vídeos", "viewer_remove_from_stack": "Remove from Stack", "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" -} + "viewer_unstack": "Un-Stack", + "wifi_name": "Nom WiFi", + "your_wifi_name": "El teu nom WiFi" +} \ No newline at end of file diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 38fdb8ac5e..bd2397ce3b 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -133,7 +133,7 @@ "backup_info_card_assets": "assets", "backup_manual_cancelled": "Cancelled", "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after some time", + "backup_manual_in_progress": "Upload already in progress. Try after sometime", "backup_manual_success": "Success", "backup_manual_title": "Upload status", "backup_options_page_title": "Backup options", @@ -252,6 +252,7 @@ "edit_date_time_dialog_timezone": "Timezone", "edit_image_title": "Edit", "edit_location_dialog_title": "Location", + "empty_folder": "This folder is empty", "end_date": "End date", "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", @@ -263,6 +264,7 @@ "exif_bottom_sheet_location_add": "Add a location", "exif_bottom_sheet_people": "PEOPLE", "exif_bottom_sheet_person_add_person": "Add name", + "exif_bottom_sheet_person_age": "Age {}", "experimental_settings_new_asset_list_subtitle": "Work in progress", "experimental_settings_new_asset_list_title": "Enable experimental photo grid", "experimental_settings_subtitle": "Use at your own risk!", @@ -275,6 +277,11 @@ "favorites_page_title": "Favorites", "filename_search": "File name or extension", "filter": "Filter", + "folders": "Folders", + "folder": "Folder", + "failed_to_load_folder": "Failed to load folder", + "failed_to_load_assets": "Failed to load assets", + "folder_not_found": "Folder not found", "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", "grant_permission": "Grant permission", "haptic_feedback_switch": "Enable haptic feedback", diff --git a/mobile/assets/i18n/ga.json b/mobile/assets/i18n/ga.json index 38fdb8ac5e..fd628d4692 100644 --- a/mobile/assets/i18n/ga.json +++ b/mobile/assets/i18n/ga.json @@ -133,7 +133,7 @@ "backup_info_card_assets": "assets", "backup_manual_cancelled": "Cancelled", "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after some time", + "backup_manual_in_progress": "Upload already in progress. Try after sometime", "backup_manual_success": "Success", "backup_manual_title": "Upload status", "backup_options_page_title": "Backup options", @@ -678,4 +678,4 @@ "viewer_unstack": "Un-Stack", "wifi_name": "WiFi Name", "your_wifi_name": "Your WiFi name" -} +} \ No newline at end of file diff --git a/mobile/assets/i18n/hi-IN.json b/mobile/assets/i18n/hi-IN.json index 0d58fbb42e..5b64f8c674 100644 --- a/mobile/assets/i18n/hi-IN.json +++ b/mobile/assets/i18n/hi-IN.json @@ -133,7 +133,7 @@ "backup_info_card_assets": "assets", "backup_manual_cancelled": "Cancelled", "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after some time", + "backup_manual_in_progress": "Upload already in progress. Try after sometime", "backup_manual_success": "Success", "backup_manual_title": "Upload status", "backup_options_page_title": "Backup options", @@ -678,4 +678,4 @@ "viewer_unstack": "स्टैक रद्द करें", "wifi_name": "WiFi Name", "your_wifi_name": "Your WiFi name" -} +} \ No newline at end of file diff --git a/mobile/assets/i18n/lt-LT.json b/mobile/assets/i18n/lt-LT.json index 38fdb8ac5e..fd628d4692 100644 --- a/mobile/assets/i18n/lt-LT.json +++ b/mobile/assets/i18n/lt-LT.json @@ -133,7 +133,7 @@ "backup_info_card_assets": "assets", "backup_manual_cancelled": "Cancelled", "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after some time", + "backup_manual_in_progress": "Upload already in progress. Try after sometime", "backup_manual_success": "Success", "backup_manual_title": "Upload status", "backup_options_page_title": "Backup options", @@ -678,4 +678,4 @@ "viewer_unstack": "Un-Stack", "wifi_name": "WiFi Name", "your_wifi_name": "Your WiFi name" -} +} \ No newline at end of file diff --git a/mobile/assets/i18n/mn-MN.json b/mobile/assets/i18n/mn-MN.json index 9fb9c5498f..33c9ed0440 100644 --- a/mobile/assets/i18n/mn-MN.json +++ b/mobile/assets/i18n/mn-MN.json @@ -133,7 +133,7 @@ "backup_info_card_assets": "assets", "backup_manual_cancelled": "Cancelled", "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after some time", + "backup_manual_in_progress": "Upload already in progress. Try after sometime", "backup_manual_success": "Success", "backup_manual_title": "Upload status", "backup_options_page_title": "Backup options", @@ -678,4 +678,4 @@ "viewer_unstack": "Un-Stack", "wifi_name": "WiFi Name", "your_wifi_name": "Your WiFi name" -} +} \ No newline at end of file diff --git a/mobile/assets/i18n/sr-Cyrl.json b/mobile/assets/i18n/sr-Cyrl.json index 38fdb8ac5e..fd628d4692 100644 --- a/mobile/assets/i18n/sr-Cyrl.json +++ b/mobile/assets/i18n/sr-Cyrl.json @@ -133,7 +133,7 @@ "backup_info_card_assets": "assets", "backup_manual_cancelled": "Cancelled", "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after some time", + "backup_manual_in_progress": "Upload already in progress. Try after sometime", "backup_manual_success": "Success", "backup_manual_title": "Upload status", "backup_options_page_title": "Backup options", @@ -678,4 +678,4 @@ "viewer_unstack": "Un-Stack", "wifi_name": "WiFi Name", "your_wifi_name": "Your WiFi name" -} +} \ No newline at end of file diff --git a/mobile/assets/i18n/sr-Latn.json b/mobile/assets/i18n/sr-Latn.json index 8d67c34792..e0f8edc97b 100644 --- a/mobile/assets/i18n/sr-Latn.json +++ b/mobile/assets/i18n/sr-Latn.json @@ -133,7 +133,7 @@ "backup_info_card_assets": "zapisi", "backup_manual_cancelled": "Cancelled", "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after some time", + "backup_manual_in_progress": "Upload already in progress. Try after sometime", "backup_manual_success": "Success", "backup_manual_title": "Upload status", "backup_options_page_title": "Backup options", @@ -678,4 +678,4 @@ "viewer_unstack": "Un-Stack", "wifi_name": "WiFi Name", "your_wifi_name": "Your WiFi name" -} +} \ No newline at end of file diff --git a/mobile/assets/i18n/sv-FI.json b/mobile/assets/i18n/sv-FI.json index 38fdb8ac5e..fd628d4692 100644 --- a/mobile/assets/i18n/sv-FI.json +++ b/mobile/assets/i18n/sv-FI.json @@ -133,7 +133,7 @@ "backup_info_card_assets": "assets", "backup_manual_cancelled": "Cancelled", "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after some time", + "backup_manual_in_progress": "Upload already in progress. Try after sometime", "backup_manual_success": "Success", "backup_manual_title": "Upload status", "backup_options_page_title": "Backup options", @@ -678,4 +678,4 @@ "viewer_unstack": "Un-Stack", "wifi_name": "WiFi Name", "your_wifi_name": "Your WiFi name" -} +} \ No newline at end of file diff --git a/mobile/assets/i18n/uk-UA.json b/mobile/assets/i18n/uk-UA.json index d4432b88c9..2b619099da 100644 --- a/mobile/assets/i18n/uk-UA.json +++ b/mobile/assets/i18n/uk-UA.json @@ -7,10 +7,10 @@ "action_common_select": "Вибрати", "action_common_update": "Оновити", "add_a_name": "Додати ім'я", - "add_endpoint": "Add endpoint", + "add_endpoint": "Додати кінцеву точку", "add_to_album_bottom_sheet_added": "Додано до {album}", "add_to_album_bottom_sheet_already_exists": "Вже є в {album}", - "advanced_settings_log_level_title": "Log level: {}", + "advanced_settings_log_level_title": "Рівень логування: {}", "advanced_settings_prefer_remote_subtitle": "Деякі пристрої вельми повільно завантажують мініатюри із елементів на пристрої. Активуйте для завантаження віддалених мініатюр натомість.", "advanced_settings_prefer_remote_title": "Перевага віддаленим зображенням", "advanced_settings_proxy_headers_subtitle": "Визначте заголовки проксі-сервера, які Immich має надсилати з кожним мережевим запитом.", @@ -66,12 +66,12 @@ "assets_restored_successfully": "{} елемент(и) успішно відновлено", "assets_trashed": "{} елемент(и) поміщено до кошика", "assets_trashed_from_server": "{} елемент(и) поміщено до кошика на сервері Immich", - "asset_viewer_settings_subtitle": "Manage your gallery viewer settings", + "asset_viewer_settings_subtitle": "Керуйте налаштуваннями переглядача галереї", "asset_viewer_settings_title": "Переглядач зображень", - "automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere", - "automatic_endpoint_switching_title": "Automatic URL switching", - "background_location_permission": "Background location permission", - "background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name", + "automatic_endpoint_switching_subtitle": "Підключатися локально через зазначену Wi-Fi мережу, коли це можливо, і використовувати альтернативні з'єднання в інших випадках", + "automatic_endpoint_switching_title": "Автоматичне перемикання URL", + "background_location_permission": "Дозвіл до місцезнаходження у фоні", + "background_location_permission_content": "Щоб перемикати мережі у фоновому режимі, Immich має *завжди* мати доступ до точної геолокації, щоб зчитувати назву Wi-Fi мережі", "backup_album_selection_page_albums_device": "Альбоми на пристрої ({})", "backup_album_selection_page_albums_tap": "Торкніться, щоб включити,\nторкніться двічі, щоб виключити", "backup_album_selection_page_assets_scatter": "Елементи можуть належати до кількох альбомів водночас. Таким чином, альбоми можуть бути включені або вилучені під час резервного копіювання.", @@ -119,7 +119,7 @@ "backup_controller_page_remainder_sub": "Решта знімків та відео для резервного копіювання з вибраних", "backup_controller_page_select": "Вибрати", "backup_controller_page_server_storage": "Сховище сервера", - "backup_controller_page_start_backup": "Почати Резервне Копіювання", + "backup_controller_page_start_backup": "Почати резервне копіювання", "backup_controller_page_status_off": "Автоматичне резервне копіювання в активному режимі вимкнено", "backup_controller_page_status_on": "Автоматичне резервне копіювання в активному режимі ввімкнено", "backup_controller_page_storage_format": "{} із {} спожито", @@ -137,7 +137,7 @@ "backup_manual_success": "Успіх", "backup_manual_title": "Стан завантаження", "backup_options_page_title": "Резервне копіювання", - "backup_setting_subtitle": "Manage background and foreground upload settings", + "backup_setting_subtitle": "Управління налаштуваннями завантаження у фоновому та активному режимі", "cache_settings_album_thumbnails": "Мініатюри сторінок бібліотеки ({} елементи)", "cache_settings_clear_cache_button": "Очистити кеш", "cache_settings_clear_cache_button_title": "Очищає кеш програми. Це суттєво знизить продуктивність програми, доки кеш не буде перебудовано.", @@ -156,17 +156,17 @@ "cache_settings_tile_subtitle": "Керування поведінкою локального сховища", "cache_settings_tile_title": "Локальне сховище", "cache_settings_title": "Налаштування кешування", - "cancel": "Cancel", - "canceled": "Canceled", - "change_display_order": "Change display order", + "cancel": "Скасувати", + "canceled": "Скасовано", + "change_display_order": "Змінити порядок відображення", "change_password_form_confirm_password": "Підтвердити пароль", "change_password_form_description": "Привіт {name},\n\nВи або або вперше входите у систему, або було зроблено запит на зміну вашого пароля. \nВведіть ваш новий пароль.", "change_password_form_new_password": "Новий пароль", "change_password_form_password_mismatch": "Паролі не співпадають", "change_password_form_reenter_new_password": "Повторіть новий пароль", - "check_corrupt_asset_backup": "Check for corrupt asset backups", - "check_corrupt_asset_backup_button": "Perform check", - "check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.", + "check_corrupt_asset_backup": "Перевірити на пошкоджені резервні копії активів", + "check_corrupt_asset_backup_button": "Виконати перевірку", + "check_corrupt_asset_backup_description": "Запустіть цю перевірку лише через Wi-Fi та після того, як всі активи будуть завантажені на сервер. Процес може зайняти кілька хвилин.", "client_cert_dialog_msg_confirm": "OK", "client_cert_enter_password": "Введіть пароль", "client_cert_import": "Імпорт", @@ -181,7 +181,7 @@ "common_create_new_album": "Створити новий альбом", "common_server_error": "Будь ласка, перевірте з'єднання, переконайтеся, що сервер доступний і версія програми/сервера сумісна.", "common_shared": "Спільні", - "completed": "Completed", + "completed": "Завершено", "contextual_search": "Схід сонця на пляжі", "control_bottom_app_bar_add_to_album": "Додати у альбом", "control_bottom_app_bar_album_info": "{} елементи", @@ -199,7 +199,7 @@ "control_bottom_app_bar_share": "Поділитися", "control_bottom_app_bar_share_to": "Поділитися", "control_bottom_app_bar_stack": "Стек", - "control_bottom_app_bar_trash_from_immich": "Перемістити до кошика", + "control_bottom_app_bar_trash_from_immich": "До кошика", "control_bottom_app_bar_unarchive": "Розархівувати", "control_bottom_app_bar_unfavorite": "Видалити з улюблених", "control_bottom_app_bar_upload": "Завантажити", @@ -213,7 +213,7 @@ "crop": "Кадрувати", "curated_location_page_title": "Місця", "curated_object_page_title": "Речі", - "current_server_address": "Current server address", + "current_server_address": "Поточна адреса сервера", "daily_title_text_date": "E, MMM dd", "daily_title_text_date_year": "E, MMM dd, yyyy", "date_format": "E, LLL d, y • h:mm a", @@ -231,7 +231,7 @@ "delete_shared_link_dialog_title": "Видалити спільне посилання", "description_input_hint_text": "Додати опис...", "description_input_submit_error": "Помилка оновлення опису, перевірте логи для подробиць", - "description_search": "Hiking day in Sapa", + "description_search": "День походу в Сапі", "download_canceled": "Завантаження скасовано", "download_complete": "Завантаження закінчено", "download_enqueue": "Завантаження поставлено в чергу", @@ -248,14 +248,14 @@ "download_sucess_android": "Медіафайли завантажено в DCIM/Immich", "download_waiting_to_retry": "Очікування повторної спроби", "edit_date_time_dialog_date_time": "Дата і час", - "edit_date_time_dialog_search_timezone": "Search timezone...", + "edit_date_time_dialog_search_timezone": "Пошук часової зони...", "edit_date_time_dialog_timezone": "Часовий пояс", "edit_image_title": "Редагувати", "edit_location_dialog_title": "Місцезнаходження", - "end_date": "End date", - "enqueued": "Enqueued", - "enter_wifi_name": "Enter WiFi name", - "error_change_sort_album": "Failed to change album sort order", + "end_date": "Дата завершення", + "enqueued": "У черзі", + "enter_wifi_name": "Введіть назву WiFi", + "error_change_sort_album": "Не вдалося змінити порядок сортування альбому", "error_saving_image": "Помилка: {}", "exif_bottom_sheet_description": "Додати опис...", "exif_bottom_sheet_details": "ПОДРОБИЦІ", @@ -267,16 +267,16 @@ "experimental_settings_new_asset_list_title": "Експериментальний макет знімків", "experimental_settings_subtitle": "На власний ризик!", "experimental_settings_title": "Експериментальні", - "external_network": "External network", - "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", - "failed": "Failed", + "external_network": "Зовнішня мережа", + "external_network_sheet_info": "Коли ви не підключені до переважної мережі WiFi, додаток підключатиметься до сервера через першу з наведених нижче URL-адрес, яку він зможе досягти, починаючи зверху вниз", + "failed": "Не вдалося", "favorites": "Вибране", "favorites_page_no_favorites": "Немає улюблених елементів", "favorites_page_title": "Улюблені", "filename_search": "Ім'я або розширення файлу", "filter": "Фільтр", - "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", - "grant_permission": "Grant permission", + "get_wifiname_error": "Не вдалося отримати назву Wi-Fi. Переконайтеся, що ви надали необхідні дозволи та підключені до Wi-Fi мережі", + "grant_permission": "Надати дозвіл", "haptic_feedback_switch": "Увімкнути тактильну віддачу", "haptic_feedback_title": "Тактильна віддача", "header_settings_add_header_tip": "Додати заголовок", @@ -322,10 +322,10 @@ "library_page_sort_most_oldest_photo": "Найдавніші фото", "library_page_sort_most_recent_photo": "Найновіші фото", "library_page_sort_title": "Назва альбому", - "local_network": "Local network", - "local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network", - "location_permission": "Location permission", - "location_permission_content": "In order to use the auto-switching feature, Immich needs precise location permission so it can read the current WiFi network's name", + "local_network": "Локальна мережа", + "local_network_sheet_info": "Додаток підключатиметься до сервера через цей URL, коли використовується вказана Wi-Fi мережа", + "location_permission": "Дозвіл до місцезнаходження", + "location_permission_content": "Щоб перемикати мережі у фоновому режимі, Immich має *завжди* мати доступ до точної геолокації, щоб зчитувати назву Wi-Fi мережі", "location_picker_choose_on_map": "Обрати на мапі", "location_picker_latitude": "Широта", "location_picker_latitude_error": "Вкажіть дійсну широту", @@ -395,8 +395,8 @@ "multiselect_grid_edit_date_time_err_read_only": "Неможливо редагувати дату елементів лише для читання, пропущено", "multiselect_grid_edit_gps_err_read_only": "Неможливо редагувати місцезнаходження елементів лише для читання, пропущено", "my_albums": "Мої альбоми", - "networking_settings": "Networking", - "networking_subtitle": "Manage the server endpoint settings", + "networking_settings": "Мережеві налаштування", + "networking_subtitle": "Керування налаштуваннями кінцевої точки сервера", "no_assets_to_show": "Елементи відсутні", "no_name": "Без імені", "notification_permission_dialog_cancel": "Скасувати", @@ -405,7 +405,7 @@ "notification_permission_list_tile_content": "Надати дозвіл для сповіщень.", "notification_permission_list_tile_enable_button": "Увімкнути Сповіщення", "notification_permission_list_tile_title": "Дозвіл на Сповіщення", - "not_selected": "Not selected", + "not_selected": "Не вибрано", "on_this_device": "На цьому пристрої", "partner_list_user_photos": "Фотографії {user}", "partner_list_view_all": "Переглянути усі", @@ -419,7 +419,7 @@ "partner_page_stop_sharing_title": "Припинити надання ваших знімків?", "partner_page_title": "Партнер", "partners": "\nПартнери", - "paused": "Paused", + "paused": "Призупинено", "people": "Люди", "permission_onboarding_back": "Назад", "permission_onboarding_continue_anyway": "Все одно продовжити", @@ -432,7 +432,7 @@ "permission_onboarding_permission_limited": "Обмежений доступ. Аби дозволити Immich резервне копіювання та керування вашою галереєю, надайте доступ до знімків та відео у Налаштуваннях", "permission_onboarding_request": "Immich потребує доступу до ваших знімків та відео.", "places": "Місця", - "preferences_settings_subtitle": "Manage the app's preferences", + "preferences_settings_subtitle": "Керування налаштуваннями додатку", "preferences_settings_title": "Параметри", "profile_drawer_app_logs": "Журнал", "profile_drawer_client_out_of_date_major": "Мобільний додаток застарів. Будь ласка, оновіть до останньої мажорної версії.", @@ -447,7 +447,7 @@ "profile_drawer_trash": "Кошик", "recently_added": "Нещодавно додані", "recently_added_page_title": "Нещодавні", - "save": "Save", + "save": "Зберегти", "save_to_gallery": "Зберегти в галерею", "scaffold_body_error_occurred": "Виникла помилка", "search_albums": "Пошук альбому", @@ -457,17 +457,17 @@ "search_filter_camera_make": "Виробник", "search_filter_camera_model": "Модель", "search_filter_camera_title": "Виберіть тип камери", - "search_filter_contextual": "Search by context", + "search_filter_contextual": "Пошук за контекстом", "search_filter_date": "Дата", "search_filter_date_interval": "{start} до {end}", "search_filter_date_title": "Виберіть діапазон дат", - "search_filter_description": "Search by description", + "search_filter_description": "Пошук за описом", "search_filter_display_option_archive": "Архів", "search_filter_display_option_favorite": "Улюблені", "search_filter_display_option_not_in_album": "Не в альбомі", "search_filter_display_options": "Параметри відображення", "search_filter_display_options_title": "Параметри відображення", - "search_filter_filename": "Search by file name", + "search_filter_filename": "Пошук за назвою файлу", "search_filter_location": "Місцезнаходження", "search_filter_location_city": "Місто", "search_filter_location_country": "Країна", @@ -479,10 +479,10 @@ "search_filter_media_type_title": "Виберіть тип носія", "search_filter_media_type_video": "Відео", "search_filter_people": "Люди", - "search_filter_people_hint": "Filter people", + "search_filter_people_hint": "Фільтрувати за людьми", "search_filter_people_title": "Виберіть людей", - "search_no_more_result": "No more results", - "search_no_result": "No results found, try a different search term or combination", + "search_no_more_result": "Більше результатів немає", + "search_no_result": "Результатів не знайдено, спробуйте інший запит або комбінацію", "search_page_categories": "Категорії", "search_page_favorites": "Улюблені", "search_page_motion_photos": "Рухомі знімки", @@ -499,7 +499,7 @@ "search_page_places": "Місця", "search_page_recently_added": "Нещодавно додані", "search_page_screenshots": "Знімки екрану", - "search_page_search_photos_videos": "Search for your photos and videos", + "search_page_search_photos_videos": "Шукайте ваші фото та відео", "search_page_selfies": "Селфі", "search_page_things": "Речі", "search_page_videos": "Відео", @@ -512,7 +512,7 @@ "select_additional_user_for_sharing_page_suggestions": "Пропозиції", "select_user_for_sharing_page_err_album": "Не вдалося створити альбом", "select_user_for_sharing_page_share_suggestions": "Пропозиції", - "server_endpoint": "Server Endpoint", + "server_endpoint": "Кінцева точка сервера", "server_info_box_app_version": "Версія додатка", "server_info_box_latest_release": "Остання версія", "server_info_box_server_url": "URL сервера", @@ -524,7 +524,7 @@ "setting_image_viewer_preview_title": "Завантажувати зображення попереднього перегляду", "setting_image_viewer_title": "Зображення", "setting_languages_apply": "Застосувати", - "setting_languages_subtitle": "Change the app's language", + "setting_languages_subtitle": "Змінити мову додатку", "setting_languages_title": "Мова", "setting_notifications_notify_failures_grace_period": "Повідомити про помилки фонового резервного копіювання: {}", "setting_notifications_notify_hours": "{} годин", @@ -542,8 +542,8 @@ "settings_require_restart": "Перезавантажте програму для застосування цього налаштування", "setting_video_viewer_looping_subtitle": "Увімкнути циклічне відтворення відео", "setting_video_viewer_looping_title": "Циклічне відтворення", - "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", - "setting_video_viewer_original_video_title": "Force original video", + "setting_video_viewer_original_video_subtitle": "При трансляції відео з сервера відтворювати оригінал, навіть якщо доступна транскодування. Може призвести до буферизації. Відео, доступні локально, відтворюються в оригінальній якості, незважаючи на це налаштування.", + "setting_video_viewer_original_video_title": "Примусово відтворювати оригінальне відео", "setting_video_viewer_title": "Відео", "share_add": "Додати", "share_add_photos": "Додати знімки", @@ -562,7 +562,7 @@ "shared_album_section_people_owner_label": "Власник", "shared_album_section_people_title": "ЛЮДИ", "share_dialog_preparing": "Підготовка...", - "shared_intent_upload_button_progress_text": "{} / {} Uploaded", + "shared_intent_upload_button_progress_text": "{} / {} Завантажено", "shared_link_app_bar_title": "Спільні посилання", "shared_link_clipboard_copied_massage": "Скопійовано в буфер обміну", "shared_link_clipboard_text": "Посилання: {}\nПароль: {}", @@ -618,7 +618,7 @@ "sharing_silver_appbar_create_shared_album": "Створити спільний альбом", "sharing_silver_appbar_shared_links": "Спільні посилання", "sharing_silver_appbar_share_partner": "Поділитися з партнером", - "start_date": "Start date", + "start_date": "Дата початку", "sync": "Синхронізувати", "sync_albums": "Синхронізувати альбоми", "sync_albums_manual_subtitle": "Синхронізувати всі завантажені фото та відео у вибрані альбоми для резервного копіювання", @@ -657,15 +657,15 @@ "trash_page_select_assets_btn": "Вибрані елементи", "trash_page_select_btn": "Вибрати", "trash_page_title": "Кошик ({})", - "upload": "Upload", + "upload": "Завантажити", "upload_dialog_cancel": "Скасувати", "upload_dialog_info": "Бажаєте створити резервну копію вибраних елементів на сервері?", "upload_dialog_ok": "Завантажити", "upload_dialog_title": "Завантажити Елементи", - "uploading": "Uploading", - "upload_to_immich": "Upload to Immich ({})", - "use_current_connection": "use current connection", - "validate_endpoint_error": "Please enter a valid URL", + "uploading": "Завантаження", + "upload_to_immich": "Завантажити в Immich ({})", + "use_current_connection": "використовувати поточне підключення", + "validate_endpoint_error": "Будь ласка, введіть дійсну URL-адресу", "version_announcement_overlay_ack": "Прийняти", "version_announcement_overlay_release_notes": "примітки до випуску", "version_announcement_overlay_text_1": "Вітаємо, є новий випуск ", @@ -676,6 +676,6 @@ "viewer_remove_from_stack": "Видалити зі стеку", "viewer_stack_use_as_main_asset": "Використовувати як основний елементи", "viewer_unstack": "Розібрати стек", - "wifi_name": "WiFi Name", - "your_wifi_name": "Your WiFi name" + "wifi_name": "Назва WiFi", + "your_wifi_name": "Ваша назва WiFi" } \ No newline at end of file diff --git a/mobile/immich_lint/lib/immich_mobile_immich_lint.dart b/mobile/immich_lint/lib/immich_mobile_immich_lint.dart index ae757d4fa4..e2622fadd1 100644 --- a/mobile/immich_lint/lib/immich_mobile_immich_lint.dart +++ b/mobile/immich_lint/lib/immich_mobile_immich_lint.dart @@ -71,7 +71,9 @@ class ImportRule extends DartLintRule { final path = resolver.path.substring(_rootOffset); if ((_allowed != null && _allowed!.matches(path)) && - (_forbidden == null || !_forbidden!.matches(path))) return; + (_forbidden == null || !_forbidden!.matches(path))) { + return; + } context.registry.addImportDirective((node) { final uri = node.uri.stringValue; diff --git a/mobile/immich_lint/pubspec.lock b/mobile/immich_lint/pubspec.lock index e81bad7da2..6d4630f1fb 100644 --- a/mobile/immich_lint/pubspec.lock +++ b/mobile/immich_lint/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "73.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: "direct main" description: name: analyzer - sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.11.0" analyzer_plugin: dependency: "direct main" description: @@ -34,26 +34,26 @@ packages: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" async: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" checked_yaml: dependency: transitive description: @@ -74,82 +74,82 @@ packages: dependency: transitive description: name: cli_util - sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "0.4.2" collection: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" convert: dependency: transitive description: name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" crypto: dependency: transitive description: name: crypto - sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" custom_lint: dependency: transitive description: name: custom_lint - sha256: "6e1ec47427ca968f22bce734d00028ae7084361999b41673291138945c5baca0" + sha256: "4500e88854e7581ee43586abeaf4443cb22375d6d289241a87b1aadf678d5545" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.6.10" custom_lint_builder: dependency: "direct main" description: name: custom_lint_builder - sha256: ba2f90fff4eff71d202d097eb14b14f87087eaaef742e956208c0eb9d3a40a21 + sha256: "5a95eff100da256fbf086b329c17c8b49058c261cdf56d3a4157d3c31c511d78" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.6.10" custom_lint_core: dependency: transitive description: name: custom_lint_core - sha256: "4ddbbdaa774265de44c97054dcec058a83d9081d071785ece601e348c18c267d" + sha256: "76a4046cc71d976222a078a8fd4a65e198b70545a8d690a75196dd14f08510f6" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.10" dart_style: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "2.3.8" file: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" fixnum: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" freezed_annotation: dependency: transitive description: @@ -162,18 +162,18 @@ packages: dependency: "direct main" description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" hotreloader: dependency: transitive description: name: hotreloader - sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.3.0" json_annotation: dependency: transitive description: @@ -186,74 +186,74 @@ packages: dependency: "direct dev" description: name: lints - sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.1" logging: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" macros: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" package_config: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" pub_semver: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.0" rxdart: dependency: transitive description: @@ -266,10 +266,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" sprintf: dependency: transitive description: @@ -282,89 +282,89 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.4" typed_data: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" uuid: dependency: transitive description: name: uuid - sha256: f33d6bb662f0e4f79dcd7ada2e6170f3b3a2530c28fc41f49a411ddedd576a77 + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff url: "https://pub.dev" source: hosted - version: "4.5.0" + version: "4.5.1" vm_service: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "15.0.0" watcher: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" yaml: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" sdks: - dart: ">=3.5.0 <4.0.0" + dart: ">=3.6.0 <4.0.0" diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 00a63be8d7..4f030b0495 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -3,7 +3,6 @@ PODS: - Flutter - connectivity_plus (0.0.1): - Flutter - - FlutterMacOS - device_info_plus (0.0.1): - Flutter - DKImagePickerController/Core (4.3.9): @@ -43,16 +42,15 @@ PODS: - Flutter (1.0.0) - flutter_local_notifications (0.0.1): - Flutter - - flutter_native_splash (0.0.1): + - flutter_native_splash (2.4.3): - Flutter - flutter_udid (0.0.1): - Flutter - SAMKeychain - - flutter_web_auth (0.6.0): + - flutter_web_auth_2 (3.0.0): - Flutter - fluttertoast (0.0.2): - Flutter - - Toast - geolocator_apple (1.2.0): - Flutter - image_picker_ios (0.0.1): @@ -61,10 +59,10 @@ PODS: - Flutter - isar_flutter_libs (1.0.0): - Flutter - - MapLibre (5.14.0-pre3) + - MapLibre (6.5.0) - maplibre_gl (0.0.1): - Flutter - - MapLibre (= 5.14.0-pre3) + - MapLibre (= 6.5.0) - native_video_player (1.0.0): - Flutter - network_info_plus (0.0.1): @@ -74,17 +72,15 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - path_provider_ios (0.0.1): - - Flutter - permission_handler_apple (9.3.0): - Flutter - photo_manager (2.0.0): - Flutter - FlutterMacOS - SAMKeychain (1.5.3) - - SDWebImage (5.20.0): - - SDWebImage/Core (= 5.20.0) - - SDWebImage/Core (5.20.0) + - SDWebImage (5.21.0): + - SDWebImage/Core (= 5.21.0) + - SDWebImage/Core (5.21.0) - share_handler_ios (0.0.14): - Flutter - share_handler_ios/share_handler_ios_models (= 0.0.14) @@ -98,11 +94,10 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - - sqflite (0.0.3): + - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - SwiftyGif (5.4.5) - - Toast (4.1.1) - url_launcher_ios (0.0.1): - Flutter - wakelock_plus (0.0.1): @@ -110,14 +105,14 @@ PODS: DEPENDENCIES: - background_downloader (from `.symlinks/plugins/background_downloader/ios`) - - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_udid (from `.symlinks/plugins/flutter_udid/ios`) - - flutter_web_auth (from `.symlinks/plugins/flutter_web_auth/ios`) + - flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) @@ -128,14 +123,13 @@ DEPENDENCIES: - network_info_plus (from `.symlinks/plugins/network_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - photo_manager (from `.symlinks/plugins/photo_manager/ios`) - share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`) - share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - - sqflite (from `.symlinks/plugins/sqflite/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) @@ -147,13 +141,12 @@ SPEC REPOS: - SAMKeychain - SDWebImage - SwiftyGif - - Toast EXTERNAL SOURCES: background_downloader: :path: ".symlinks/plugins/background_downloader/ios" connectivity_plus: - :path: ".symlinks/plugins/connectivity_plus/darwin" + :path: ".symlinks/plugins/connectivity_plus/ios" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" file_picker: @@ -166,8 +159,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_native_splash/ios" flutter_udid: :path: ".symlinks/plugins/flutter_udid/ios" - flutter_web_auth: - :path: ".symlinks/plugins/flutter_web_auth/ios" + flutter_web_auth_2: + :path: ".symlinks/plugins/flutter_web_auth_2/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" geolocator_apple: @@ -188,8 +181,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" - path_provider_ios: - :path: ".symlinks/plugins/path_provider_ios/ios" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" photo_manager: @@ -202,50 +193,48 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" - sqflite: - :path: ".symlinks/plugins/sqflite/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" wakelock_plus: :path: ".symlinks/plugins/wakelock_plus/ios" SPEC CHECKSUMS: - background_downloader: 9f788ffc5de45acf87d6380e91ca0841066c18cf - connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db + background_downloader: 3ca0e156ad83a9fc1c8300f5f7c38e94e2d0bf51 + connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 + file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 - flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 - flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04 - flutter_web_auth: acc15a8fd7bba796a933c724a6dffc3d00f07c27 - fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c - geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450 + flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 + flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab + flutter_web_auth_2: 06d500582775790a0d4c323222fcb6d7990f9603 + fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f + geolocator_apple: 9bcea1918ff7f0062d98345d238ae12718acfbc1 image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 isar_flutter_libs: fdf730ca925d05687f36d7f1d355e482529ed097 - MapLibre: 620fc933c1d6029b33738c905c1490d024e5d4ef - maplibre_gl: a2efec727dd340e4c65e26d2b03b584f14881fd9 + MapLibre: 0ebfa9329d313cec8bf0a5ba5a336a1dc903785e + maplibre_gl: be7b98f1c3ed75bf77f321eec04df359d0ff6f62 native_video_player: d12af78a1a4a8cf09775a5177d5b392def6fd23c network_info_plus: 6613d9d7cdeb0e6f366ed4dbe4b3c51c52d567a9 package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c - SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 + SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 share_handler_ios: 6dd3a4ac5ca0d955274aec712ba0ecdcaf583e7c share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871 share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 + wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56 PODFILE CHECKSUM: 03b7eead4ee77b9e778179eeb0f3b5513617451c diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 1bf67ac5f9..1d77f6cc4a 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -541,7 +541,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 196; + CURRENT_PROJECT_VERSION = 197; CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; @@ -685,7 +685,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 196; + CURRENT_PROJECT_VERSION = 197; CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; @@ -715,7 +715,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 196; + CURRENT_PROJECT_VERSION = 197; CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; @@ -748,7 +748,7 @@ CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 196; + CURRENT_PROJECT_VERSION = 197; CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -791,7 +791,7 @@ CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 196; + CURRENT_PROJECT_VERSION = 197; CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -831,7 +831,7 @@ CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 196; + CURRENT_PROJECT_VERSION = 197; CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; diff --git a/mobile/ios/Runner/AppDelegate.swift b/mobile/ios/Runner/AppDelegate.swift index 6c749cdb9f..fd62618205 100644 --- a/mobile/ios/Runner/AppDelegate.swift +++ b/mobile/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import BackgroundTasks import Flutter import network_info_plus -import path_provider_ios +import path_provider_foundation import permission_handler_apple import photo_manager import shared_preferences_foundation @@ -24,8 +24,8 @@ import UIKit BackgroundServicePlugin.register(with: self.registrar(forPlugin: "BackgroundServicePlugin")!) BackgroundServicePlugin.setPluginRegistrantCallback { registry in - if !registry.hasPlugin("org.cocoapods.path-provider-ios") { - FLTPathProviderPlugin.register(with: registry.registrar(forPlugin: "org.cocoapods.path-provider-ios")!) + if !registry.hasPlugin("org.cocoapods.path-provider-foundation") { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "org.cocoapods.path-provider-foundation")!) } if !registry.hasPlugin("org.cocoapods.photo-manager") { diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist index 035b0ff642..0e97c377cf 100644 --- a/mobile/ios/Runner/Info.plist +++ b/mobile/ios/Runner/Info.plist @@ -78,7 +78,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.128.0 + 1.129.0 CFBundleSignature ???? CFBundleURLTypes @@ -93,7 +93,7 @@ CFBundleVersion - 196 + 197 FLTEnableImpeller ITSAppUsesNonExemptEncryption diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile index fa3cac0d22..f9a99e76ba 100644 --- a/mobile/ios/fastlane/Fastfile +++ b/mobile/ios/fastlane/Fastfile @@ -19,7 +19,7 @@ platform :ios do desc "iOS Release" lane :release do increment_version_number( - version_number: "1.128.0" + version_number: "1.129.0" ) increment_build_number( build_number: latest_testflight_build_number + 1, diff --git a/mobile/lib/constants/locales.dart b/mobile/lib/constants/locales.dart index e4e01f5b53..fa2717fcb7 100644 --- a/mobile/lib/constants/locales.dart +++ b/mobile/lib/constants/locales.dart @@ -5,7 +5,7 @@ const Map locales = { 'English (en_US)': Locale('en', 'US'), // Additional locales 'Arabic (ar_JO)': Locale('ar', 'JO'), - 'Catalan (ca_CA)': Locale('ca', 'CA'), + 'Catalan (ca)': Locale('ca'), 'Chinese (zh_CN)': Locale('zh', 'CN'), 'Chinese Simplified (zh_Hans)': Locale('zh', 'Hans'), 'Chinese TW (zh_TW)': Locale('zh', 'TW'), diff --git a/mobile/lib/domain/interfaces/exif.interface.dart b/mobile/lib/domain/interfaces/exif.interface.dart new file mode 100644 index 0000000000..a5de6167e9 --- /dev/null +++ b/mobile/lib/domain/interfaces/exif.interface.dart @@ -0,0 +1,14 @@ +import 'package:immich_mobile/domain/interfaces/db.interface.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; + +abstract interface class IExifInfoRepository implements IDatabaseRepository { + Future get(int assetId); + + Future update(ExifInfo exifInfo); + + Future> updateAll(List exifInfos); + + Future delete(int assetId); + + Future deleteAll(); +} diff --git a/mobile/lib/domain/interfaces/log.interface.dart b/mobile/lib/domain/interfaces/log.interface.dart index f1cbc977dd..27e91c5488 100644 --- a/mobile/lib/domain/interfaces/log.interface.dart +++ b/mobile/lib/domain/interfaces/log.interface.dart @@ -1,8 +1,9 @@ import 'dart:async'; +import 'package:immich_mobile/domain/interfaces/db.interface.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; -abstract interface class ILogRepository { +abstract interface class ILogRepository implements IDatabaseRepository { Future insert(LogMessage log); Future insertAll(Iterable logs); diff --git a/mobile/lib/domain/interfaces/sync_api.interface.dart b/mobile/lib/domain/interfaces/sync_api.interface.dart new file mode 100644 index 0000000000..fb8f1aa46e --- /dev/null +++ b/mobile/lib/domain/interfaces/sync_api.interface.dart @@ -0,0 +1,7 @@ +import 'package:immich_mobile/domain/models/sync/sync_event.model.dart'; + +abstract interface class ISyncApiRepository { + Future ack(String data); + + Stream> watchUserSyncEvent(); +} diff --git a/mobile/lib/domain/interfaces/user.interface.dart b/mobile/lib/domain/interfaces/user.interface.dart new file mode 100644 index 0000000000..03f3ebb63e --- /dev/null +++ b/mobile/lib/domain/interfaces/user.interface.dart @@ -0,0 +1,24 @@ +import 'package:immich_mobile/domain/interfaces/db.interface.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; + +abstract interface class IUserRepository implements IDatabaseRepository { + Future insert(UserDto user); + + Future get(int id); + + Future getByUserId(String id); + + Future> getByUserIds(List ids); + + Future> getAll({SortUserBy? sortBy}); + + Future updateAll(List users); + + Future update(UserDto user); + + Future delete(List ids); + + Future deleteAll(); +} + +enum SortUserBy { id } diff --git a/mobile/lib/domain/interfaces/user_api.repository.dart b/mobile/lib/domain/interfaces/user_api.repository.dart new file mode 100644 index 0000000000..9889654a52 --- /dev/null +++ b/mobile/lib/domain/interfaces/user_api.repository.dart @@ -0,0 +1,15 @@ +import 'dart:typed_data'; + +import 'package:immich_mobile/domain/models/user.model.dart'; + +abstract interface class IUserApiRepository { + Future getMyUser(); + + Future> getAll(); + + /// Saves the [data] in the server and uses it as the current users profile image + Future createProfileImage({ + required String name, + required Uint8List data, + }); +} diff --git a/mobile/lib/domain/models/exif.model.dart b/mobile/lib/domain/models/exif.model.dart new file mode 100644 index 0000000000..e95653ca4e --- /dev/null +++ b/mobile/lib/domain/models/exif.model.dart @@ -0,0 +1,177 @@ +class ExifInfo { + final int? assetId; + final int? fileSize; + final String? description; + final bool isFlipped; + final String? orientation; + final String? timeZone; + final DateTime? dateTimeOriginal; + + // GPS + final double? latitude; + final double? longitude; + final String? city; + final String? state; + final String? country; + + // Camera related + final String? make; + final String? model; + final String? lens; + final double? f; + final double? mm; + final int? iso; + final double? exposureSeconds; + + bool get hasCoordinates => + latitude != null && longitude != null && latitude != 0 && longitude != 0; + + String get exposureTime { + if (exposureSeconds == null) { + return ""; + } + if (exposureSeconds! < 1) { + return "1/${(1.0 / exposureSeconds!).round()} s"; + } + return "${exposureSeconds!.toStringAsFixed(1)} s"; + } + + String get fNumber => f == null ? "" : f!.toStringAsFixed(1); + + String get focalLength => mm == null ? "" : mm!.toStringAsFixed(1); + + const ExifInfo({ + this.assetId, + this.fileSize, + this.description, + this.orientation, + this.timeZone, + this.dateTimeOriginal, + this.isFlipped = false, + this.latitude, + this.longitude, + this.city, + this.state, + this.country, + this.make, + this.model, + this.lens, + this.f, + this.mm, + this.iso, + this.exposureSeconds, + }); + + @override + bool operator ==(covariant ExifInfo other) { + if (identical(this, other)) return true; + + return other.fileSize == fileSize && + other.description == description && + other.orientation == orientation && + other.timeZone == timeZone && + other.dateTimeOriginal == dateTimeOriginal && + other.latitude == latitude && + other.longitude == longitude && + other.city == city && + other.state == state && + other.country == country && + other.make == make && + other.model == model && + other.lens == lens && + other.f == f && + other.mm == mm && + other.iso == iso && + other.exposureSeconds == exposureSeconds && + other.assetId == assetId; + } + + @override + int get hashCode { + return fileSize.hashCode ^ + description.hashCode ^ + orientation.hashCode ^ + timeZone.hashCode ^ + dateTimeOriginal.hashCode ^ + latitude.hashCode ^ + longitude.hashCode ^ + city.hashCode ^ + state.hashCode ^ + country.hashCode ^ + make.hashCode ^ + model.hashCode ^ + lens.hashCode ^ + f.hashCode ^ + mm.hashCode ^ + iso.hashCode ^ + exposureSeconds.hashCode ^ + assetId.hashCode; + } + + @override + String toString() { + return '''{ +fileSize: ${fileSize ?? 'NA'}, +description: ${description ?? 'NA'}, +orientation: ${orientation ?? 'NA'}, +timeZone: ${timeZone ?? 'NA'}, +dateTimeOriginal: ${dateTimeOriginal ?? 'NA'}, +latitude: ${latitude ?? 'NA'}, +longitude: ${longitude ?? 'NA'}, +city: ${city ?? 'NA'}, +state: ${state ?? 'NA'}, +country: ${country ?? ''}, +make: ${make ?? 'NA'}, +model: ${model ?? 'NA'}, +lens: ${lens ?? 'NA'}, +f: ${f ?? 'NA'}, +mm: ${mm ?? ''}, +iso: ${iso ?? 'NA'}, +exposureSeconds: ${exposureSeconds ?? 'NA'}, +}'''; + } + + ExifInfo copyWith({ + int? assetId, + int? fileSize, + String? description, + String? orientation, + String? timeZone, + DateTime? dateTimeOriginal, + double? latitude, + double? longitude, + String? city, + String? state, + String? country, + bool? isFlipped, + String? make, + String? model, + String? lens, + double? f, + double? mm, + int? iso, + double? exposureSeconds, + }) { + return ExifInfo( + assetId: assetId ?? this.assetId, + fileSize: fileSize ?? this.fileSize, + description: description ?? this.description, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + isFlipped: isFlipped ?? this.isFlipped, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + f: f ?? this.f, + mm: mm ?? this.mm, + iso: iso ?? this.iso, + exposureSeconds: exposureSeconds ?? this.exposureSeconds, + ); + } +} diff --git a/mobile/lib/domain/models/store.model.dart b/mobile/lib/domain/models/store.model.dart index 06b946b3f6..8a99d68b8f 100644 --- a/mobile/lib/domain/models/store.model.dart +++ b/mobile/lib/domain/models/store.model.dart @@ -1,11 +1,11 @@ -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; /// Key for each possible value in the `Store`. /// Defines the data type for each value enum StoreKey { version._(0), assetETag._(1), - currentUser._(2), + currentUser._(2), deviceIdHash._(3), deviceId._(4), backupFailedSince._(5), diff --git a/mobile/lib/domain/models/sync/sync_event.model.dart b/mobile/lib/domain/models/sync/sync_event.model.dart new file mode 100644 index 0000000000..f4642d59cf --- /dev/null +++ b/mobile/lib/domain/models/sync/sync_event.model.dart @@ -0,0 +1,14 @@ +class SyncEvent { + // dynamic + final dynamic data; + + final String ack; + + SyncEvent({ + required this.data, + required this.ack, + }); + + @override + String toString() => 'SyncEvent(data: $data, ack: $ack)'; +} diff --git a/mobile/lib/domain/models/user.model.dart b/mobile/lib/domain/models/user.model.dart new file mode 100644 index 0000000000..ceb65f313a --- /dev/null +++ b/mobile/lib/domain/models/user.model.dart @@ -0,0 +1,157 @@ +import 'dart:ui'; + +import 'package:immich_mobile/utils/hash.dart'; + +enum AvatarColor { + // do not change this order or reuse indices for other purposes, adding is OK + primary, + pink, + red, + yellow, + blue, + green, + purple, + orange, + gray, + amber; + + Color toColor({bool isDarkTheme = false}) => switch (this) { + AvatarColor.primary => + isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF), + AvatarColor.pink => const Color.fromARGB(255, 244, 114, 182), + AvatarColor.red => const Color.fromARGB(255, 239, 68, 68), + AvatarColor.yellow => const Color.fromARGB(255, 234, 179, 8), + AvatarColor.blue => const Color.fromARGB(255, 59, 130, 246), + AvatarColor.green => const Color.fromARGB(255, 22, 163, 74), + AvatarColor.purple => const Color.fromARGB(255, 147, 51, 234), + AvatarColor.orange => const Color.fromARGB(255, 234, 88, 12), + AvatarColor.gray => const Color.fromARGB(255, 75, 85, 99), + AvatarColor.amber => const Color.fromARGB(255, 217, 119, 6), + }; +} + +// TODO: Rename to User once Isar is removed +class UserDto { + final String uid; + final String email; + final String name; + final bool isAdmin; + final DateTime updatedAt; + + final String? profileImagePath; + final AvatarColor avatarColor; + + final bool memoryEnabled; + final bool inTimeline; + + final bool isPartnerSharedBy; + final bool isPartnerSharedWith; + + final int quotaUsageInBytes; + final int quotaSizeInBytes; + + int get id => fastHash(uid); + bool get hasQuota => quotaSizeInBytes > 0; + + const UserDto({ + required this.uid, + required this.email, + required this.name, + required this.isAdmin, + required this.updatedAt, + this.profileImagePath, + this.avatarColor = AvatarColor.primary, + this.memoryEnabled = true, + this.inTimeline = false, + this.isPartnerSharedBy = false, + this.isPartnerSharedWith = false, + this.quotaUsageInBytes = 0, + this.quotaSizeInBytes = 0, + }); + + @override + String toString() { + return '''User: { +id: $id, +uid: $uid, +email: $email, +name: $name, +isAdmin: $isAdmin, +updatedAt: $updatedAt, +profileImagePath: ${profileImagePath ?? ''}, +avatarColor: $avatarColor, +memoryEnabled: $memoryEnabled, +inTimeline: $inTimeline, +isPartnerSharedBy: $isPartnerSharedBy, +isPartnerSharedWith: $isPartnerSharedWith, +quotaUsageInBytes: $quotaUsageInBytes, +quotaSizeInBytes: $quotaSizeInBytes, +}'''; + } + + UserDto copyWith({ + String? uid, + String? email, + String? name, + bool? isAdmin, + DateTime? updatedAt, + String? profileImagePath, + AvatarColor? avatarColor, + bool? memoryEnabled, + bool? inTimeline, + bool? isPartnerSharedBy, + bool? isPartnerSharedWith, + int? quotaUsageInBytes, + int? quotaSizeInBytes, + }) => + UserDto( + uid: uid ?? this.uid, + email: email ?? this.email, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + updatedAt: updatedAt ?? this.updatedAt, + profileImagePath: profileImagePath ?? this.profileImagePath, + avatarColor: avatarColor ?? this.avatarColor, + memoryEnabled: memoryEnabled ?? this.memoryEnabled, + inTimeline: inTimeline ?? this.inTimeline, + isPartnerSharedBy: isPartnerSharedBy ?? this.isPartnerSharedBy, + isPartnerSharedWith: isPartnerSharedWith ?? this.isPartnerSharedWith, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + ); + + @override + bool operator ==(covariant UserDto other) { + if (identical(this, other)) return true; + + return other.uid == uid && + other.updatedAt.isAtSameMomentAs(updatedAt) && + other.avatarColor == avatarColor && + other.email == email && + other.name == name && + other.isPartnerSharedBy == isPartnerSharedBy && + other.isPartnerSharedWith == isPartnerSharedWith && + other.profileImagePath == profileImagePath && + other.isAdmin == isAdmin && + other.memoryEnabled == memoryEnabled && + other.inTimeline == inTimeline && + other.quotaUsageInBytes == quotaUsageInBytes && + other.quotaSizeInBytes == quotaSizeInBytes; + } + + @override + int get hashCode => + uid.hashCode ^ + name.hashCode ^ + email.hashCode ^ + updatedAt.hashCode ^ + isAdmin.hashCode ^ + profileImagePath.hashCode ^ + avatarColor.hashCode ^ + memoryEnabled.hashCode ^ + inTimeline.hashCode ^ + isPartnerSharedBy.hashCode ^ + isPartnerSharedWith.hashCode ^ + quotaUsageInBytes.hashCode ^ + quotaSizeInBytes.hashCode; +} diff --git a/mobile/lib/domain/services/log.service.dart b/mobile/lib/domain/services/log.service.dart index 59de5c2e94..2136912a67 100644 --- a/mobile/lib/domain/services/log.service.dart +++ b/mobile/lib/domain/services/log.service.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/interfaces/log.interface.dart'; import 'package:immich_mobile/domain/interfaces/store.interface.dart'; @@ -91,12 +92,13 @@ class LogService { } /// Flush pending log messages to persistent storage - Future flush() async { + void flush() { if (_flushTimer == null) { return; } _flushTimer!.cancel(); - await _flushBufferToDatabase(); + // TODO: Rename enable this after moving to sqlite - #16504 + // await _flushBufferToDatabase(); } Future dispose() { @@ -106,6 +108,10 @@ class LogService { } void _writeLogToDatabase(LogRecord r) { + if (kDebugMode) { + debugPrint('[${r.level.name}] [${r.time}] ${r.message}'); + } + final record = LogMessage( message: r.message, level: r.level.toLogLevel(), diff --git a/mobile/lib/domain/services/store.service.dart b/mobile/lib/domain/services/store.service.dart index 70b9f31c00..73426cbf4e 100644 --- a/mobile/lib/domain/services/store.service.dart +++ b/mobile/lib/domain/services/store.service.dart @@ -74,7 +74,7 @@ class StoreService { return value; } - /// Asynchronously stores the value in the DB and synchronously in the cache + /// Asynchronously stores the value in the Store Future put, T>(U key, T value) async { if (_cache[key.id] == value) return; await _storeRepository.insert(key, value); @@ -84,7 +84,7 @@ class StoreService { /// Watches a specific key for changes Stream watch(StoreKey key) => _storeRepository.watch(key); - /// Removes the value asynchronously from the DB and synchronously from the cache + /// Removes the value asynchronously from the Store Future delete(StoreKey key) async { await _storeRepository.delete(key); _cache.remove(key.id); diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart new file mode 100644 index 0000000000..72e29b3677 --- /dev/null +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -0,0 +1,49 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart'; +import 'package:openapi/api.dart'; + +class SyncStreamService { + final ISyncApiRepository _syncApiRepository; + + SyncStreamService(this._syncApiRepository); + + StreamSubscription? _userSyncSubscription; + + void syncUsers() { + _userSyncSubscription = + _syncApiRepository.watchUserSyncEvent().listen((events) async { + for (final event in events) { + if (event.data is SyncUserV1) { + final data = event.data as SyncUserV1; + debugPrint("User Update: $data"); + + // final user = await _userRepository.get(data.id); + + // if (user == null) { + // continue; + // } + + // user.name = data.name; + // user.email = data.email; + // user.updatedAt = DateTime.now(); + + // await _userRepository.update(user); + // await _syncApiRepository.ack(event.ack); + } + + if (event.data is SyncUserDeleteV1) { + final data = event.data as SyncUserDeleteV1; + + debugPrint("User delete: $data"); + // await _syncApiRepository.ack(event.ack); + } + } + }); + } + + Future dispose() async { + await _userSyncSubscription?.cancel(); + } +} diff --git a/mobile/lib/domain/services/user.service.dart b/mobile/lib/domain/services/user.service.dart new file mode 100644 index 0000000000..48a6f5777d --- /dev/null +++ b/mobile/lib/domain/services/user.service.dart @@ -0,0 +1,64 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:immich_mobile/domain/interfaces/user.interface.dart'; +import 'package:immich_mobile/domain/interfaces/user_api.repository.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:logging/logging.dart'; + +class UserService { + final Logger _log = Logger("UserService"); + final IUserRepository _userRepository; + final IUserApiRepository _userApiRepository; + final StoreService _storeService; + + UserService({ + required IUserRepository userRepository, + required IUserApiRepository userApiRepository, + required StoreService storeService, + }) : _userRepository = userRepository, + _userApiRepository = userApiRepository, + _storeService = storeService; + + UserDto getMyUser() { + return _storeService.get(StoreKey.currentUser); + } + + UserDto? tryGetMyUser() { + return _storeService.tryGet(StoreKey.currentUser); + } + + Stream watchMyUser() { + return _storeService.watch(StoreKey.currentUser); + } + + Future refreshMyUser() async { + final user = await _userApiRepository.getMyUser(); + if (user == null) return null; + await _storeService.put(StoreKey.currentUser, user); + await _userRepository.update(user); + return user; + } + + Future createProfileImage(String name, Uint8List image) async { + try { + return await _userApiRepository.createProfileImage( + name: name, + data: image, + ); + } catch (e) { + _log.warning("Failed to upload profile image", e); + return null; + } + } + + Future> getAll() async { + return await _userRepository.getAll(); + } + + Future deleteAll() { + return _userRepository.deleteAll(); + } +} diff --git a/mobile/lib/entities/album.entity.dart b/mobile/lib/entities/album.entity.dart index 8caff2255f..8b466da1db 100644 --- a/mobile/lib/entities/album.entity.dart +++ b/mobile/lib/entities/album.entity.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/utils/datetime_comparison.dart'; import 'package:isar/isar.dart'; // ignore: implementation_imports diff --git a/mobile/lib/entities/asset.entity.dart b/mobile/lib/entities/asset.entity.dart index f6b10a3ab5..048068ad3d 100644 --- a/mobile/lib/entities/asset.entity.dart +++ b/mobile/lib/entities/asset.entity.dart @@ -1,13 +1,16 @@ import 'dart:convert'; import 'dart:io'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/extensions/string_extensions.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart' + as entity; +import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; import 'package:immich_mobile/utils/hash.dart'; import 'package:isar/isar.dart'; import 'package:openapi/api.dart'; -import 'package:photo_manager/photo_manager.dart' show AssetEntity; -import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:path/path.dart' as p; +import 'package:photo_manager/photo_manager.dart' show AssetEntity; part 'asset.entity.g.dart'; @@ -27,8 +30,9 @@ class Asset { width = remote.exifInfo?.exifImageWidth?.toInt(), livePhotoVideoId = remote.livePhotoVideoId, ownerId = fastHash(remote.ownerId), - exifInfo = - remote.exifInfo != null ? ExifInfo.fromDto(remote.exifInfo!) : null, + exifInfo = remote.exifInfo == null + ? null + : ExifDtoConverter.fromDto(remote.exifInfo!), isFavorite = remote.isFavorite, isArchived = remote.isArchived, isTrashed = remote.isTrashed, @@ -359,14 +363,14 @@ class Asset { localId: localId, width: a.width ?? width, height: a.height ?? height, - exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo, + exifInfo: a.exifInfo?.copyWith(assetId: id) ?? exifInfo, ); } else if (isRemote) { return _copyWith( localId: localId ?? a.localId, width: width ?? a.width, height: height ?? a.height, - exifInfo: exifInfo ?? a.exifInfo?.copyWith(id: id), + exifInfo: exifInfo ?? a.exifInfo?.copyWith(assetId: id), ); } else { // TODO: Revisit this and remove all bool field assignments @@ -407,7 +411,7 @@ class Asset { isArchived: a.isArchived, isTrashed: a.isTrashed, isOffline: a.isOffline, - exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo, + exifInfo: a.exifInfo?.copyWith(assetId: id) ?? exifInfo, thumbhash: a.thumbhash, ); } else { @@ -416,7 +420,8 @@ class Asset { localId: localId ?? a.localId, width: width ?? a.width, height: height ?? a.height, - exifInfo: exifInfo ?? a.exifInfo?.copyWith(id: id), + exifInfo: exifInfo ?? + a.exifInfo?.copyWith(assetId: id), // updated to use assetId ); } } @@ -476,8 +481,8 @@ class Asset { Future put(Isar db) async { await db.assets.put(this); if (exifInfo != null) { - exifInfo!.id = id; - await db.exifInfos.put(exifInfo!); + await db.exifInfos + .put(entity.ExifInfo.fromDto(exifInfo!.copyWith(assetId: id))); } } diff --git a/mobile/lib/entities/exif_info.entity.dart b/mobile/lib/entities/exif_info.entity.dart deleted file mode 100644 index c46f3dddc1..0000000000 --- a/mobile/lib/entities/exif_info.entity.dart +++ /dev/null @@ -1,241 +0,0 @@ -import 'package:isar/isar.dart'; -import 'package:openapi/api.dart'; - -part 'exif_info.entity.g.dart'; - -/// Exif information 1:1 relation with Asset -@Collection(inheritance: false) -class ExifInfo { - Id? id; - int? fileSize; - DateTime? dateTimeOriginal; - String? timeZone; - String? make; - String? model; - String? lens; - float? f; - float? mm; - short? iso; - float? exposureSeconds; - float? lat; - float? long; - String? city; - String? state; - String? country; - String? description; - String? orientation; - - @ignore - bool get hasCoordinates => - latitude != null && longitude != null && latitude != 0 && longitude != 0; - - @ignore - String get exposureTime { - if (exposureSeconds == null) { - return ""; - } else if (exposureSeconds! < 1) { - return "1/${(1.0 / exposureSeconds!).round()} s"; - } else { - return "${exposureSeconds!.toStringAsFixed(1)} s"; - } - } - - @ignore - String get fNumber => f != null ? f!.toStringAsFixed(1) : ""; - - @ignore - String get focalLength => mm != null ? mm!.toStringAsFixed(1) : ""; - - @ignore - bool? _isFlipped; - - @ignore - @pragma('vm:prefer-inline') - bool get isFlipped => _isFlipped ??= _isOrientationFlipped(orientation); - - @ignore - double? get latitude => lat; - - @ignore - double? get longitude => long; - - ExifInfo.fromDto(ExifResponseDto dto) - : fileSize = dto.fileSizeInByte, - dateTimeOriginal = dto.dateTimeOriginal, - timeZone = dto.timeZone, - make = dto.make, - model = dto.model, - lens = dto.lensModel, - f = dto.fNumber?.toDouble(), - mm = dto.focalLength?.toDouble(), - iso = dto.iso?.toInt(), - exposureSeconds = _exposureTimeToSeconds(dto.exposureTime), - lat = dto.latitude?.toDouble(), - long = dto.longitude?.toDouble(), - city = dto.city, - state = dto.state, - country = dto.country, - description = dto.description, - orientation = dto.orientation; - - ExifInfo({ - this.id, - this.fileSize, - this.dateTimeOriginal, - this.timeZone, - this.make, - this.model, - this.lens, - this.f, - this.mm, - this.iso, - this.exposureSeconds, - this.lat, - this.long, - this.city, - this.state, - this.country, - this.description, - this.orientation, - }); - - ExifInfo copyWith({ - Id? id, - int? fileSize, - DateTime? dateTimeOriginal, - String? timeZone, - String? make, - String? model, - String? lens, - float? f, - float? mm, - short? iso, - float? exposureSeconds, - float? lat, - float? long, - String? city, - String? state, - String? country, - String? description, - String? orientation, - }) => - ExifInfo( - id: id ?? this.id, - fileSize: fileSize ?? this.fileSize, - dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, - timeZone: timeZone ?? this.timeZone, - make: make ?? this.make, - model: model ?? this.model, - lens: lens ?? this.lens, - f: f ?? this.f, - mm: mm ?? this.mm, - iso: iso ?? this.iso, - exposureSeconds: exposureSeconds ?? this.exposureSeconds, - lat: lat ?? this.lat, - long: long ?? this.long, - city: city ?? this.city, - state: state ?? this.state, - country: country ?? this.country, - description: description ?? this.description, - orientation: orientation ?? this.orientation, - ); - - @override - bool operator ==(other) { - if (other is! ExifInfo) return false; - return id == other.id && - fileSize == other.fileSize && - dateTimeOriginal == other.dateTimeOriginal && - timeZone == other.timeZone && - make == other.make && - model == other.model && - lens == other.lens && - f == other.f && - mm == other.mm && - iso == other.iso && - exposureSeconds == other.exposureSeconds && - lat == other.lat && - long == other.long && - city == other.city && - state == other.state && - country == other.country && - description == other.description && - orientation == other.orientation; - } - - @override - @ignore - int get hashCode => - id.hashCode ^ - fileSize.hashCode ^ - dateTimeOriginal.hashCode ^ - timeZone.hashCode ^ - make.hashCode ^ - model.hashCode ^ - lens.hashCode ^ - f.hashCode ^ - mm.hashCode ^ - iso.hashCode ^ - exposureSeconds.hashCode ^ - lat.hashCode ^ - long.hashCode ^ - city.hashCode ^ - state.hashCode ^ - country.hashCode ^ - description.hashCode ^ - orientation.hashCode; - - @override - String toString() { - return """ -{ - id: $id, - fileSize: $fileSize, - dateTimeOriginal: $dateTimeOriginal, - timeZone: $timeZone, - make: $make, - model: $model, - lens: $lens, - f: $f, - mm: $mm, - iso: $iso, - exposureSeconds: $exposureSeconds, - lat: $lat, - long: $long, - city: $city, - state: $state, - country: $country, - description: $description, - orientation: $orientation -}"""; - } -} - -bool _isOrientationFlipped(String? orientation) { - final value = orientation != null ? int.tryParse(orientation) : null; - if (value == null) { - return false; - } - final isRotated90CW = value == 5 || value == 6 || value == 90; - final isRotated270CW = value == 7 || value == 8 || value == -90; - return isRotated90CW || isRotated270CW; -} - -double? _exposureTimeToSeconds(String? s) { - if (s == null) { - return null; - } - double? value = double.tryParse(s); - if (value != null) { - return value; - } - final parts = s.split("/"); - if (parts.length == 2) { - final numerator = double.tryParse(parts[0]); - final denominator = double.tryParse(parts[1]); - if (numerator != null && denominator != null) { - return numerator / denominator; - } - } - return null; -} diff --git a/mobile/lib/entities/user.entity.dart b/mobile/lib/entities/user.entity.dart deleted file mode 100644 index 8fa6e83874..0000000000 --- a/mobile/lib/entities/user.entity.dart +++ /dev/null @@ -1,181 +0,0 @@ -import 'dart:ui'; - -import 'package:immich_mobile/entities/album.entity.dart'; -import 'package:immich_mobile/utils/hash.dart'; -import 'package:isar/isar.dart'; -import 'package:openapi/api.dart'; - -part 'user.entity.g.dart'; - -@Collection(inheritance: false) -class User { - User({ - required this.id, - required this.updatedAt, - required this.email, - required this.name, - required this.isAdmin, - this.isPartnerSharedBy = false, - this.isPartnerSharedWith = false, - this.profileImagePath = '', - this.avatarColor = AvatarColorEnum.primary, - this.memoryEnabled = true, - this.inTimeline = false, - this.quotaUsageInBytes = 0, - this.quotaSizeInBytes = 0, - }); - - Id get isarId => fastHash(id); - - User.fromUserDto( - UserAdminResponseDto dto, - UserPreferencesResponseDto? preferences, - ) : id = dto.id, - updatedAt = dto.updatedAt, - email = dto.email, - name = dto.name, - isPartnerSharedBy = false, - isPartnerSharedWith = false, - profileImagePath = dto.profileImagePath, - isAdmin = dto.isAdmin, - memoryEnabled = preferences?.memories.enabled ?? false, - avatarColor = dto.avatarColor.toAvatarColor(), - inTimeline = false, - quotaUsageInBytes = dto.quotaUsageInBytes ?? 0, - quotaSizeInBytes = dto.quotaSizeInBytes ?? 0; - - User.fromPartnerDto(PartnerResponseDto dto) - : id = dto.id, - updatedAt = DateTime.now(), - email = dto.email, - name = dto.name, - isPartnerSharedBy = false, - isPartnerSharedWith = false, - profileImagePath = dto.profileImagePath, - isAdmin = false, - memoryEnabled = false, - avatarColor = dto.avatarColor.toAvatarColor(), - inTimeline = dto.inTimeline ?? false, - quotaUsageInBytes = 0, - quotaSizeInBytes = 0; - - /// Base user dto used where the complete user object is not required - User.fromSimpleUserDto(UserResponseDto dto) - : id = dto.id, - email = dto.email, - name = dto.name, - profileImagePath = dto.profileImagePath, - avatarColor = dto.avatarColor.toAvatarColor(), - // Fill the remaining fields with placeholders - isAdmin = false, - inTimeline = false, - memoryEnabled = false, - isPartnerSharedBy = false, - isPartnerSharedWith = false, - updatedAt = DateTime.now(), - quotaUsageInBytes = 0, - quotaSizeInBytes = 0; - - @Index(unique: true, replace: false, type: IndexType.hash) - String id; - DateTime updatedAt; - String email; - String name; - bool isPartnerSharedBy; - bool isPartnerSharedWith; - bool isAdmin; - String profileImagePath; - @Enumerated(EnumType.ordinal) - AvatarColorEnum avatarColor; - bool memoryEnabled; - bool inTimeline; - int quotaUsageInBytes; - int quotaSizeInBytes; - - bool get hasQuota => quotaSizeInBytes > 0; - @Backlink(to: 'owner') - final IsarLinks albums = IsarLinks(); - @Backlink(to: 'sharedUsers') - final IsarLinks sharedAlbums = IsarLinks(); - - @override - bool operator ==(other) { - if (other is! User) return false; - return id == other.id && - updatedAt.isAtSameMomentAs(other.updatedAt) && - avatarColor == other.avatarColor && - email == other.email && - name == other.name && - isPartnerSharedBy == other.isPartnerSharedBy && - isPartnerSharedWith == other.isPartnerSharedWith && - profileImagePath == other.profileImagePath && - isAdmin == other.isAdmin && - memoryEnabled == other.memoryEnabled && - inTimeline == other.inTimeline && - quotaUsageInBytes == other.quotaUsageInBytes && - quotaSizeInBytes == other.quotaSizeInBytes; - } - - @override - @ignore - int get hashCode => - id.hashCode ^ - updatedAt.hashCode ^ - email.hashCode ^ - name.hashCode ^ - isPartnerSharedBy.hashCode ^ - isPartnerSharedWith.hashCode ^ - profileImagePath.hashCode ^ - avatarColor.hashCode ^ - isAdmin.hashCode ^ - memoryEnabled.hashCode ^ - inTimeline.hashCode ^ - quotaUsageInBytes.hashCode ^ - quotaSizeInBytes.hashCode; -} - -enum AvatarColorEnum { - // do not change this order or reuse indices for other purposes, adding is OK - primary, - pink, - red, - yellow, - blue, - green, - purple, - orange, - gray, - amber, -} - -extension AvatarColorEnumHelper on UserAvatarColor { - AvatarColorEnum toAvatarColor() => switch (this) { - UserAvatarColor.primary => AvatarColorEnum.primary, - UserAvatarColor.pink => AvatarColorEnum.pink, - UserAvatarColor.red => AvatarColorEnum.red, - UserAvatarColor.yellow => AvatarColorEnum.yellow, - UserAvatarColor.blue => AvatarColorEnum.blue, - UserAvatarColor.green => AvatarColorEnum.green, - UserAvatarColor.purple => AvatarColorEnum.purple, - UserAvatarColor.orange => AvatarColorEnum.orange, - UserAvatarColor.gray => AvatarColorEnum.gray, - UserAvatarColor.amber => AvatarColorEnum.amber, - _ => AvatarColorEnum.primary, - }; -} - -extension AvatarColorToColorHelper on AvatarColorEnum { - Color toColor([bool isDarkTheme = false]) => switch (this) { - AvatarColorEnum.primary => - isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF), - AvatarColorEnum.pink => const Color.fromARGB(255, 244, 114, 182), - AvatarColorEnum.red => const Color.fromARGB(255, 239, 68, 68), - AvatarColorEnum.yellow => const Color.fromARGB(255, 234, 179, 8), - AvatarColorEnum.blue => const Color.fromARGB(255, 59, 130, 246), - AvatarColorEnum.green => const Color.fromARGB(255, 22, 163, 74), - AvatarColorEnum.purple => const Color.fromARGB(255, 147, 51, 234), - AvatarColorEnum.orange => const Color.fromARGB(255, 234, 88, 12), - AvatarColorEnum.gray => const Color.fromARGB(255, 75, 85, 99), - AvatarColorEnum.amber => const Color.fromARGB(255, 217, 119, 6), - }; -} diff --git a/mobile/lib/extensions/collection_extensions.dart b/mobile/lib/extensions/collection_extensions.dart index d27c9e9500..e02582588b 100644 --- a/mobile/lib/extensions/collection_extensions.dart +++ b/mobile/lib/extensions/collection_extensions.dart @@ -1,8 +1,8 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; extension ListExtension on List { List uniqueConsecutive({ @@ -58,11 +58,11 @@ extension AssetListExtension on Iterable { /// Returns the assets that are owned by the user passed to the [owner] param /// If [owner] is null, an empty list is returned Iterable ownedOnly( - User? owner, { + UserDto? owner, { void Function()? errorCallback, }) { if (owner == null) return []; - final userId = owner.isarId; + final userId = owner.id; final bool onlyOwned = every((e) => e.ownerId == userId); if (!onlyOwned) { if (errorCallback != null) errorCallback(); diff --git a/mobile/lib/extensions/maplibrecontroller_extensions.dart b/mobile/lib/extensions/maplibrecontroller_extensions.dart index 83074be137..42d5e2c1d0 100644 --- a/mobile/lib/extensions/maplibrecontroller_extensions.dart +++ b/mobile/lib/extensions/maplibrecontroller_extensions.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'dart:math'; import 'package:flutter/services.dart'; @@ -6,7 +7,7 @@ import 'package:immich_mobile/models/map/map_marker.model.dart'; import 'package:immich_mobile/utils/map_utils.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; -extension MapMarkers on MaplibreMapController { +extension MapMarkers on MapLibreMapController { static var _completer = Completer()..complete(); Future addGeoJSONSourceForMarkers(List markers) async { @@ -40,11 +41,29 @@ extension MapMarkers on MaplibreMapController { await addGeoJSONSourceForMarkers(markers); - await addHeatmapLayer( - MapUtils.defaultSourceId, - MapUtils.defaultHeatMapLayerId, - MapUtils.defaultHeatMapLayerProperties, - ); + if (Platform.isAndroid) { + await addCircleLayer( + MapUtils.defaultSourceId, + MapUtils.defaultHeatMapLayerId, + const CircleLayerProperties( + circleRadius: 10, + circleColor: "rgba(150,86,34,0.7)", + circleBlur: 1.0, + circleOpacity: 0.7, + circleStrokeWidth: 0.1, + circleStrokeColor: "rgba(203,46,19,0.5)", + circleStrokeOpacity: 0.7, + ), + ); + } + + if (Platform.isIOS) { + await addHeatmapLayer( + MapUtils.defaultSourceId, + MapUtils.defaultHeatMapLayerId, + MapUtils.defaultHeatMapLayerProperties, + ); + } _completer.complete(); } diff --git a/mobile/lib/extensions/theme_extensions.dart b/mobile/lib/extensions/theme_extensions.dart index 3e17e2b991..b81e4476e0 100644 --- a/mobile/lib/extensions/theme_extensions.dart +++ b/mobile/lib/extensions/theme_extensions.dart @@ -10,14 +10,14 @@ extension ImmichColorSchemeExtensions on ColorScheme { extension ColorExtensions on Color { Color lighten({double amount = 0.1}) { return Color.alphaBlend( - Colors.white.withOpacity(amount), + Colors.white.withValues(alpha: amount), this, ); } Color darken({double amount = 0.1}) { return Color.alphaBlend( - Colors.black.withOpacity(amount), + Colors.black.withValues(alpha: amount), this, ); } diff --git a/mobile/lib/infrastructure/entities/exif.entity.dart b/mobile/lib/infrastructure/entities/exif.entity.dart new file mode 100644 index 0000000000..5a93bc9768 --- /dev/null +++ b/mobile/lib/infrastructure/entities/exif.entity.dart @@ -0,0 +1,92 @@ +import 'package:immich_mobile/domain/models/exif.model.dart' as domain; +import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; +import 'package:isar/isar.dart'; + +part 'exif.entity.g.dart'; + +/// Exif information 1:1 relation with Asset +@Collection(inheritance: false) +class ExifInfo { + final Id? id; + final int? fileSize; + final DateTime? dateTimeOriginal; + final String? timeZone; + final String? make; + final String? model; + final String? lens; + final float? f; + final float? mm; + final short? iso; + final float? exposureSeconds; + final float? lat; + final float? long; + final String? city; + final String? state; + final String? country; + final String? description; + final String? orientation; + + const ExifInfo({ + this.id, + this.fileSize, + this.dateTimeOriginal, + this.timeZone, + this.make, + this.model, + this.lens, + this.f, + this.mm, + this.iso, + this.exposureSeconds, + this.lat, + this.long, + this.city, + this.state, + this.country, + this.description, + this.orientation, + }); + + static ExifInfo fromDto(domain.ExifInfo dto) => ExifInfo( + id: dto.assetId, + fileSize: dto.fileSize, + dateTimeOriginal: dto.dateTimeOriginal, + timeZone: dto.timeZone, + make: dto.make, + model: dto.model, + lens: dto.lens, + f: dto.f, + mm: dto.mm, + iso: dto.iso?.toInt(), + exposureSeconds: dto.exposureSeconds, + lat: dto.latitude, + long: dto.longitude, + city: dto.city, + state: dto.state, + country: dto.country, + description: dto.description, + orientation: dto.orientation, + ); + + domain.ExifInfo toDto() => domain.ExifInfo( + assetId: id, + fileSize: fileSize, + description: description, + orientation: orientation, + timeZone: timeZone, + dateTimeOriginal: dateTimeOriginal, + isFlipped: ExifDtoConverter.isOrientationFlipped(orientation), + latitude: lat, + longitude: long, + city: city, + state: state, + country: country, + make: make, + model: model, + lens: lens, + f: f, + mm: mm, + iso: iso?.toInt(), + exposureSeconds: exposureSeconds, + ); +} diff --git a/mobile/lib/entities/exif_info.entity.g.dart b/mobile/lib/infrastructure/entities/exif.entity.g.dart similarity index 99% rename from mobile/lib/entities/exif_info.entity.g.dart rename to mobile/lib/infrastructure/entities/exif.entity.g.dart index 0b744e5f20..989338abff 100644 --- a/mobile/lib/entities/exif_info.entity.g.dart +++ b/mobile/lib/infrastructure/entities/exif.entity.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'exif_info.entity.dart'; +part of 'exif.entity.dart'; // ************************************************************************** // IsarCollectionGenerator @@ -288,9 +288,7 @@ List> _exifInfoGetLinks(ExifInfo object) { return []; } -void _exifInfoAttach(IsarCollection col, Id id, ExifInfo object) { - object.id = id; -} +void _exifInfoAttach(IsarCollection col, Id id, ExifInfo object) {} extension ExifInfoQueryWhereSort on QueryBuilder { QueryBuilder anyId() { diff --git a/mobile/lib/infrastructure/entities/user.entity.dart b/mobile/lib/infrastructure/entities/user.entity.dart new file mode 100644 index 0000000000..834559b936 --- /dev/null +++ b/mobile/lib/infrastructure/entities/user.entity.dart @@ -0,0 +1,73 @@ +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/utils/hash.dart'; +import 'package:isar/isar.dart'; + +part 'user.entity.g.dart'; + +@Collection(inheritance: false) +class User { + Id get isarId => fastHash(id); + @Index(unique: true, replace: false, type: IndexType.hash) + final String id; + final DateTime updatedAt; + final String email; + final String name; + final bool isPartnerSharedBy; + final bool isPartnerSharedWith; + final bool isAdmin; + final String profileImagePath; + @Enumerated(EnumType.ordinal) + final AvatarColor avatarColor; + final bool memoryEnabled; + final bool inTimeline; + final int quotaUsageInBytes; + final int quotaSizeInBytes; + + const User({ + required this.id, + required this.updatedAt, + required this.email, + required this.name, + required this.isAdmin, + this.isPartnerSharedBy = false, + this.isPartnerSharedWith = false, + this.profileImagePath = '', + this.avatarColor = AvatarColor.primary, + this.memoryEnabled = true, + this.inTimeline = false, + this.quotaUsageInBytes = 0, + this.quotaSizeInBytes = 0, + }); + + static User fromDto(UserDto dto) => User( + id: dto.uid, + updatedAt: dto.updatedAt, + email: dto.email, + name: dto.name, + isAdmin: dto.isAdmin, + isPartnerSharedBy: dto.isPartnerSharedBy, + isPartnerSharedWith: dto.isPartnerSharedWith, + profileImagePath: dto.profileImagePath ?? "", + avatarColor: dto.avatarColor, + memoryEnabled: dto.memoryEnabled, + inTimeline: dto.inTimeline, + quotaUsageInBytes: dto.quotaUsageInBytes, + quotaSizeInBytes: dto.quotaSizeInBytes, + ); + + UserDto toDto() => UserDto( + uid: id, + email: email, + name: name, + isAdmin: isAdmin, + updatedAt: updatedAt, + profileImagePath: profileImagePath.isEmpty ? null : profileImagePath, + avatarColor: avatarColor, + memoryEnabled: memoryEnabled, + inTimeline: inTimeline, + isPartnerSharedBy: isPartnerSharedBy, + isPartnerSharedWith: isPartnerSharedWith, + quotaUsageInBytes: quotaUsageInBytes, + quotaSizeInBytes: quotaSizeInBytes, + ); +} diff --git a/mobile/lib/entities/user.entity.g.dart b/mobile/lib/infrastructure/entities/user.entity.g.dart similarity index 86% rename from mobile/lib/entities/user.entity.g.dart rename to mobile/lib/infrastructure/entities/user.entity.g.dart index a7aaee44bf..37a793b2c3 100644 --- a/mobile/lib/entities/user.entity.g.dart +++ b/mobile/lib/infrastructure/entities/user.entity.g.dart @@ -28,63 +28,58 @@ const UserSchema = CollectionSchema( name: r'email', type: IsarType.string, ), - r'hasQuota': PropertySchema( - id: 2, - name: r'hasQuota', - type: IsarType.bool, - ), r'id': PropertySchema( - id: 3, + id: 2, name: r'id', type: IsarType.string, ), r'inTimeline': PropertySchema( - id: 4, + id: 3, name: r'inTimeline', type: IsarType.bool, ), r'isAdmin': PropertySchema( - id: 5, + id: 4, name: r'isAdmin', type: IsarType.bool, ), r'isPartnerSharedBy': PropertySchema( - id: 6, + id: 5, name: r'isPartnerSharedBy', type: IsarType.bool, ), r'isPartnerSharedWith': PropertySchema( - id: 7, + id: 6, name: r'isPartnerSharedWith', type: IsarType.bool, ), r'memoryEnabled': PropertySchema( - id: 8, + id: 7, name: r'memoryEnabled', type: IsarType.bool, ), r'name': PropertySchema( - id: 9, + id: 8, name: r'name', type: IsarType.string, ), r'profileImagePath': PropertySchema( - id: 10, + id: 9, name: r'profileImagePath', type: IsarType.string, ), r'quotaSizeInBytes': PropertySchema( - id: 11, + id: 10, name: r'quotaSizeInBytes', type: IsarType.long, ), r'quotaUsageInBytes': PropertySchema( - id: 12, + id: 11, name: r'quotaUsageInBytes', type: IsarType.long, ), r'updatedAt': PropertySchema( - id: 13, + id: 12, name: r'updatedAt', type: IsarType.dateTime, ) @@ -109,22 +104,7 @@ const UserSchema = CollectionSchema( ], ) }, - links: { - r'albums': LinkSchema( - id: -8764917375410137318, - name: r'albums', - target: r'Album', - single: false, - linkName: r'owner', - ), - r'sharedAlbums': LinkSchema( - id: -7037628715076287024, - name: r'sharedAlbums', - target: r'Album', - single: false, - linkName: r'sharedUsers', - ) - }, + links: {}, embeddedSchemas: {}, getId: _userGetId, getLinks: _userGetLinks, @@ -153,18 +133,17 @@ void _userSerialize( ) { writer.writeByte(offsets[0], object.avatarColor.index); writer.writeString(offsets[1], object.email); - writer.writeBool(offsets[2], object.hasQuota); - writer.writeString(offsets[3], object.id); - writer.writeBool(offsets[4], object.inTimeline); - writer.writeBool(offsets[5], object.isAdmin); - writer.writeBool(offsets[6], object.isPartnerSharedBy); - writer.writeBool(offsets[7], object.isPartnerSharedWith); - writer.writeBool(offsets[8], object.memoryEnabled); - writer.writeString(offsets[9], object.name); - writer.writeString(offsets[10], object.profileImagePath); - writer.writeLong(offsets[11], object.quotaSizeInBytes); - writer.writeLong(offsets[12], object.quotaUsageInBytes); - writer.writeDateTime(offsets[13], object.updatedAt); + writer.writeString(offsets[2], object.id); + writer.writeBool(offsets[3], object.inTimeline); + writer.writeBool(offsets[4], object.isAdmin); + writer.writeBool(offsets[5], object.isPartnerSharedBy); + writer.writeBool(offsets[6], object.isPartnerSharedWith); + writer.writeBool(offsets[7], object.memoryEnabled); + writer.writeString(offsets[8], object.name); + writer.writeString(offsets[9], object.profileImagePath); + writer.writeLong(offsets[10], object.quotaSizeInBytes); + writer.writeLong(offsets[11], object.quotaUsageInBytes); + writer.writeDateTime(offsets[12], object.updatedAt); } User _userDeserialize( @@ -176,19 +155,19 @@ User _userDeserialize( final object = User( avatarColor: _UseravatarColorValueEnumMap[reader.readByteOrNull(offsets[0])] ?? - AvatarColorEnum.primary, + AvatarColor.primary, email: reader.readString(offsets[1]), - id: reader.readString(offsets[3]), - inTimeline: reader.readBoolOrNull(offsets[4]) ?? false, - isAdmin: reader.readBool(offsets[5]), - isPartnerSharedBy: reader.readBoolOrNull(offsets[6]) ?? false, - isPartnerSharedWith: reader.readBoolOrNull(offsets[7]) ?? false, - memoryEnabled: reader.readBoolOrNull(offsets[8]) ?? true, - name: reader.readString(offsets[9]), - profileImagePath: reader.readStringOrNull(offsets[10]) ?? '', - quotaSizeInBytes: reader.readLongOrNull(offsets[11]) ?? 0, - quotaUsageInBytes: reader.readLongOrNull(offsets[12]) ?? 0, - updatedAt: reader.readDateTime(offsets[13]), + id: reader.readString(offsets[2]), + inTimeline: reader.readBoolOrNull(offsets[3]) ?? false, + isAdmin: reader.readBool(offsets[4]), + isPartnerSharedBy: reader.readBoolOrNull(offsets[5]) ?? false, + isPartnerSharedWith: reader.readBoolOrNull(offsets[6]) ?? false, + memoryEnabled: reader.readBoolOrNull(offsets[7]) ?? true, + name: reader.readString(offsets[8]), + profileImagePath: reader.readStringOrNull(offsets[9]) ?? '', + quotaSizeInBytes: reader.readLongOrNull(offsets[10]) ?? 0, + quotaUsageInBytes: reader.readLongOrNull(offsets[11]) ?? 0, + updatedAt: reader.readDateTime(offsets[12]), ); return object; } @@ -202,32 +181,30 @@ P _userDeserializeProp

( switch (propertyId) { case 0: return (_UseravatarColorValueEnumMap[reader.readByteOrNull(offset)] ?? - AvatarColorEnum.primary) as P; + AvatarColor.primary) as P; case 1: return (reader.readString(offset)) as P; case 2: - return (reader.readBool(offset)) as P; - case 3: return (reader.readString(offset)) as P; - case 4: + case 3: return (reader.readBoolOrNull(offset) ?? false) as P; - case 5: + case 4: return (reader.readBool(offset)) as P; + case 5: + return (reader.readBoolOrNull(offset) ?? false) as P; case 6: return (reader.readBoolOrNull(offset) ?? false) as P; case 7: - return (reader.readBoolOrNull(offset) ?? false) as P; - case 8: return (reader.readBoolOrNull(offset) ?? true) as P; - case 9: + case 8: return (reader.readString(offset)) as P; - case 10: + case 9: return (reader.readStringOrNull(offset) ?? '') as P; + case 10: + return (reader.readLongOrNull(offset) ?? 0) as P; case 11: return (reader.readLongOrNull(offset) ?? 0) as P; case 12: - return (reader.readLongOrNull(offset) ?? 0) as P; - case 13: return (reader.readDateTime(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -247,16 +224,16 @@ const _UseravatarColorEnumValueMap = { 'amber': 9, }; const _UseravatarColorValueEnumMap = { - 0: AvatarColorEnum.primary, - 1: AvatarColorEnum.pink, - 2: AvatarColorEnum.red, - 3: AvatarColorEnum.yellow, - 4: AvatarColorEnum.blue, - 5: AvatarColorEnum.green, - 6: AvatarColorEnum.purple, - 7: AvatarColorEnum.orange, - 8: AvatarColorEnum.gray, - 9: AvatarColorEnum.amber, + 0: AvatarColor.primary, + 1: AvatarColor.pink, + 2: AvatarColor.red, + 3: AvatarColor.yellow, + 4: AvatarColor.blue, + 5: AvatarColor.green, + 6: AvatarColor.purple, + 7: AvatarColor.orange, + 8: AvatarColor.gray, + 9: AvatarColor.amber, }; Id _userGetId(User object) { @@ -264,14 +241,10 @@ Id _userGetId(User object) { } List> _userGetLinks(User object) { - return [object.albums, object.sharedAlbums]; + return []; } -void _userAttach(IsarCollection col, Id id, User object) { - object.albums.attach(col, col.isar.collection(), r'albums', id); - object.sharedAlbums - .attach(col, col.isar.collection(), r'sharedAlbums', id); -} +void _userAttach(IsarCollection col, Id id, User object) {} extension UserByIndex on IsarCollection { Future getById(String id) { @@ -447,7 +420,7 @@ extension UserQueryWhere on QueryBuilder { extension UserQueryFilter on QueryBuilder { QueryBuilder avatarColorEqualTo( - AvatarColorEnum value) { + AvatarColor value) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.equalTo( property: r'avatarColor', @@ -457,7 +430,7 @@ extension UserQueryFilter on QueryBuilder { } QueryBuilder avatarColorGreaterThan( - AvatarColorEnum value, { + AvatarColor value, { bool include = false, }) { return QueryBuilder.apply(this, (query) { @@ -470,7 +443,7 @@ extension UserQueryFilter on QueryBuilder { } QueryBuilder avatarColorLessThan( - AvatarColorEnum value, { + AvatarColor value, { bool include = false, }) { return QueryBuilder.apply(this, (query) { @@ -483,8 +456,8 @@ extension UserQueryFilter on QueryBuilder { } QueryBuilder avatarColorBetween( - AvatarColorEnum lower, - AvatarColorEnum upper, { + AvatarColor lower, + AvatarColor upper, { bool includeLower = true, bool includeUpper = true, }) { @@ -627,15 +600,6 @@ extension UserQueryFilter on QueryBuilder { }); } - QueryBuilder hasQuotaEqualTo(bool value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'hasQuota', - value: value, - )); - }); - } - QueryBuilder idEqualTo( String value, { bool caseSensitive = true, @@ -1285,118 +1249,7 @@ extension UserQueryFilter on QueryBuilder { extension UserQueryObject on QueryBuilder {} -extension UserQueryLinks on QueryBuilder { - QueryBuilder albums(FilterQuery q) { - return QueryBuilder.apply(this, (query) { - return query.link(q, r'albums'); - }); - } - - QueryBuilder albumsLengthEqualTo( - int length) { - return QueryBuilder.apply(this, (query) { - return query.linkLength(r'albums', length, true, length, true); - }); - } - - QueryBuilder albumsIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.linkLength(r'albums', 0, true, 0, true); - }); - } - - QueryBuilder albumsIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.linkLength(r'albums', 0, false, 999999, true); - }); - } - - QueryBuilder albumsLengthLessThan( - int length, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.linkLength(r'albums', 0, true, length, include); - }); - } - - QueryBuilder albumsLengthGreaterThan( - int length, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.linkLength(r'albums', length, include, 999999, true); - }); - } - - QueryBuilder albumsLengthBetween( - int lower, - int upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.linkLength( - r'albums', lower, includeLower, upper, includeUpper); - }); - } - - QueryBuilder sharedAlbums( - FilterQuery q) { - return QueryBuilder.apply(this, (query) { - return query.link(q, r'sharedAlbums'); - }); - } - - QueryBuilder sharedAlbumsLengthEqualTo( - int length) { - return QueryBuilder.apply(this, (query) { - return query.linkLength(r'sharedAlbums', length, true, length, true); - }); - } - - QueryBuilder sharedAlbumsIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.linkLength(r'sharedAlbums', 0, true, 0, true); - }); - } - - QueryBuilder sharedAlbumsIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.linkLength(r'sharedAlbums', 0, false, 999999, true); - }); - } - - QueryBuilder sharedAlbumsLengthLessThan( - int length, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.linkLength(r'sharedAlbums', 0, true, length, include); - }); - } - - QueryBuilder sharedAlbumsLengthGreaterThan( - int length, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.linkLength(r'sharedAlbums', length, include, 999999, true); - }); - } - - QueryBuilder sharedAlbumsLengthBetween( - int lower, - int upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.linkLength( - r'sharedAlbums', lower, includeLower, upper, includeUpper); - }); - } -} +extension UserQueryLinks on QueryBuilder {} extension UserQuerySortBy on QueryBuilder { QueryBuilder sortByAvatarColor() { @@ -1423,18 +1276,6 @@ extension UserQuerySortBy on QueryBuilder { }); } - QueryBuilder sortByHasQuota() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'hasQuota', Sort.asc); - }); - } - - QueryBuilder sortByHasQuotaDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'hasQuota', Sort.desc); - }); - } - QueryBuilder sortById() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'id', Sort.asc); @@ -1593,18 +1434,6 @@ extension UserQuerySortThenBy on QueryBuilder { }); } - QueryBuilder thenByHasQuota() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'hasQuota', Sort.asc); - }); - } - - QueryBuilder thenByHasQuotaDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'hasQuota', Sort.desc); - }); - } - QueryBuilder thenById() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'id', Sort.asc); @@ -1764,12 +1593,6 @@ extension UserQueryWhereDistinct on QueryBuilder { }); } - QueryBuilder distinctByHasQuota() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'hasQuota'); - }); - } - QueryBuilder distinctById( {bool caseSensitive = true}) { return QueryBuilder.apply(this, (query) { @@ -1848,7 +1671,7 @@ extension UserQueryProperty on QueryBuilder { }); } - QueryBuilder avatarColorProperty() { + QueryBuilder avatarColorProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'avatarColor'); }); @@ -1860,12 +1683,6 @@ extension UserQueryProperty on QueryBuilder { }); } - QueryBuilder hasQuotaProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'hasQuota'); - }); - } - QueryBuilder idProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'id'); diff --git a/mobile/lib/infrastructure/repositories/api.repository.dart b/mobile/lib/infrastructure/repositories/api.repository.dart new file mode 100644 index 0000000000..56c64c5512 --- /dev/null +++ b/mobile/lib/infrastructure/repositories/api.repository.dart @@ -0,0 +1,11 @@ +import 'package:immich_mobile/constants/errors.dart'; + +class ApiRepository { + const ApiRepository(); + + Future checkNull(Future future) async { + final response = await future; + if (response == null) throw NoResponseDtoError(); + return response; + } +} diff --git a/mobile/lib/infrastructure/repositories/exif.repository.dart b/mobile/lib/infrastructure/repositories/exif.repository.dart new file mode 100644 index 0000000000..2b4276dd57 --- /dev/null +++ b/mobile/lib/infrastructure/repositories/exif.repository.dart @@ -0,0 +1,50 @@ +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart' + as entity; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:isar/isar.dart'; + +class IsarExifRepository extends IsarDatabaseRepository + implements IExifInfoRepository { + final Isar _db; + + const IsarExifRepository(this._db) : super(_db); + + @override + Future delete(int assetId) async { + await transaction(() async { + await _db.exifInfos.delete(assetId); + }); + } + + @override + Future deleteAll() async { + await transaction(() async { + await _db.exifInfos.clear(); + }); + } + + @override + Future get(int assetId) async { + return (await _db.exifInfos.get(assetId))?.toDto(); + } + + @override + Future update(ExifInfo exifInfo) { + return transaction(() async { + await _db.exifInfos.put(entity.ExifInfo.fromDto(exifInfo)); + return exifInfo; + }); + } + + @override + Future> updateAll(List exifInfos) { + return transaction(() async { + await _db.exifInfos.putAll( + exifInfos.map(entity.ExifInfo.fromDto).toList(), + ); + return exifInfos; + }); + } +} diff --git a/mobile/lib/infrastructure/repositories/store.repository.dart b/mobile/lib/infrastructure/repositories/store.repository.dart index 5cf6838ee1..1e5a5335d5 100644 --- a/mobile/lib/infrastructure/repositories/store.repository.dart +++ b/mobile/lib/infrastructure/repositories/store.repository.dart @@ -1,9 +1,9 @@ import 'package:immich_mobile/domain/interfaces/store.interface.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/repositories/user.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:isar/isar.dart'; class IsarStoreRepository extends IsarDatabaseRepository @@ -78,7 +78,7 @@ class IsarStoreRepository extends IsarDatabaseRepository const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!), - const (User) => await UserRepository(_db).getByDbId(entity.intValue!), + const (UserDto) => await IsarUserRepository(_db).get(entity.intValue!), _ => null, } as T?; @@ -88,8 +88,8 @@ class IsarStoreRepository extends IsarDatabaseRepository const (String) => (null, value as String), const (bool) => ((value as bool) ? 1 : 0, null), const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null), - const (User) => ( - (await UserRepository(_db).update(value as User)).isarId, + const (UserDto) => ( + (await IsarUserRepository(_db).update(value as UserDto)).id, null, ), _ => throw UnsupportedError( diff --git a/mobile/lib/infrastructure/repositories/sync_api.repository.dart b/mobile/lib/infrastructure/repositories/sync_api.repository.dart new file mode 100644 index 0000000000..88a6838c44 --- /dev/null +++ b/mobile/lib/infrastructure/repositories/sync_api.repository.dart @@ -0,0 +1,112 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart'; +import 'package:immich_mobile/domain/models/sync/sync_event.model.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:openapi/api.dart'; +import 'package:http/http.dart' as http; + +class SyncApiRepository implements ISyncApiRepository { + final ApiService _api; + const SyncApiRepository(this._api); + + @override + Stream> watchUserSyncEvent() { + return _getSyncStream( + SyncStreamDto(types: [SyncRequestType.usersV1]), + ); + } + + @override + Future ack(String data) { + return _api.syncApi.sendSyncAck(SyncAckSetDto(acks: [data])); + } + + Stream> _getSyncStream( + SyncStreamDto dto, { + int batchSize = 5000, + }) async* { + final client = http.Client(); + final endpoint = "${_api.apiClient.basePath}/sync/stream"; + + final headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/jsonlines+json', + }; + + final queryParams = []; + final headerParams = {}; + await _api.applyToParams(queryParams, headerParams); + headers.addAll(headerParams); + + final request = http.Request('POST', Uri.parse(endpoint)); + request.headers.addAll(headers); + request.body = jsonEncode(dto.toJson()); + + String previousChunk = ''; + List lines = []; + + try { + final response = await client.send(request); + + if (response.statusCode != 200) { + final errorBody = await response.stream.bytesToString(); + throw ApiException( + response.statusCode, + 'Failed to get sync stream: $errorBody', + ); + } + + await for (final chunk in response.stream.transform(utf8.decoder)) { + previousChunk += chunk; + final parts = previousChunk.split('\n'); + previousChunk = parts.removeLast(); + lines.addAll(parts); + + if (lines.length < batchSize) { + continue; + } + + yield await compute(_parseSyncResponse, lines); + lines.clear(); + } + } finally { + if (lines.isNotEmpty) { + yield await compute(_parseSyncResponse, lines); + } + client.close(); + } + } +} + +const _kResponseMap = { + SyncEntityType.userV1: SyncUserV1.fromJson, + SyncEntityType.userDeleteV1: SyncUserDeleteV1.fromJson, +}; + +// Need to be outside of the class to be able to use compute +List _parseSyncResponse(List lines) { + final List data = []; + + for (var line in lines) { + try { + final jsonData = jsonDecode(line); + final type = SyncEntityType.fromJson(jsonData['type'])!; + final dataJson = jsonData['data']; + final ack = jsonData['ack']; + final converter = _kResponseMap[type]; + if (converter == null) { + debugPrint("[_parseSyncReponse] Unknown type $type"); + continue; + } + + data.add(SyncEvent(data: converter(dataJson), ack: ack)); + } catch (error, stack) { + debugPrint("[_parseSyncReponse] Error parsing json $error $stack"); + } + } + + return data; +} diff --git a/mobile/lib/infrastructure/repositories/user.repository.dart b/mobile/lib/infrastructure/repositories/user.repository.dart new file mode 100644 index 0000000000..2cb8023b2a --- /dev/null +++ b/mobile/lib/infrastructure/repositories/user.repository.dart @@ -0,0 +1,80 @@ +import 'package:immich_mobile/domain/interfaces/user.interface.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart' + as entity; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:isar/isar.dart'; + +class IsarUserRepository extends IsarDatabaseRepository + implements IUserRepository { + final Isar _db; + const IsarUserRepository(super.db) : _db = db; + + @override + Future delete(List ids) async { + await transaction(() async { + await _db.users.deleteAll(ids); + }); + } + + @override + Future deleteAll() async { + await transaction(() async { + await _db.users.clear(); + }); + } + + @override + Future get(int id) async { + return (await _db.users.get(id))?.toDto(); + } + + @override + Future> getAll({SortUserBy? sortBy}) async { + return (await _db.users + .where() + .optional( + sortBy != null, + (query) => switch (sortBy!) { + SortUserBy.id => query.sortById(), + }, + ) + .findAll()) + .map((u) => u.toDto()) + .toList(); + } + + @override + Future getByUserId(String id) async { + return (await _db.users.getById(id))?.toDto(); + } + + @override + Future> getByUserIds(List ids) async { + return (await _db.users.getAllById(ids)).map((u) => u?.toDto()).toList(); + } + + @override + Future insert(UserDto user) async { + await transaction(() async { + await _db.users.put(entity.User.fromDto(user)); + }); + return true; + } + + @override + Future update(UserDto user) async { + await transaction(() async { + await _db.users.put(entity.User.fromDto(user)); + }); + return user; + } + + @override + Future updateAll(List users) async { + await transaction(() async { + await _db.users.putAll(users.map(entity.User.fromDto).toList()); + }); + return true; + } +} diff --git a/mobile/lib/infrastructure/repositories/user_api.repository.dart b/mobile/lib/infrastructure/repositories/user_api.repository.dart new file mode 100644 index 0000000000..2623e8c264 --- /dev/null +++ b/mobile/lib/infrastructure/repositories/user_api.repository.dart @@ -0,0 +1,41 @@ +import 'dart:typed_data'; + +import 'package:http/http.dart'; +import 'package:immich_mobile/domain/interfaces/user_api.repository.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/infrastructure/repositories/api.repository.dart'; +import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; +import 'package:openapi/api.dart'; + +class UserApiRepository extends ApiRepository implements IUserApiRepository { + final UsersApi _api; + const UserApiRepository(this._api); + + @override + Future getMyUser() async { + final (adminDto, preferenceDto) = + await (_api.getMyUser(), _api.getMyPreferences()).wait; + if (adminDto == null) return null; + + return UserConverter.fromAdminDto(adminDto, preferenceDto); + } + + @override + Future createProfileImage({ + required String name, + required Uint8List data, + }) async { + final res = await checkNull( + _api.createProfileImage( + MultipartFile.fromBytes('file', data, filename: name), + ), + ); + return res.profileImagePath; + } + + @override + Future> getAll() async { + final dto = await checkNull(_api.searchUsers()); + return dto.map(UserConverter.fromSimpleUserDto).toList(); + } +} diff --git a/mobile/lib/infrastructure/utils/exif.converter.dart b/mobile/lib/infrastructure/utils/exif.converter.dart new file mode 100644 index 0000000000..0f6e2b0295 --- /dev/null +++ b/mobile/lib/infrastructure/utils/exif.converter.dart @@ -0,0 +1,56 @@ +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:openapi/api.dart'; + +abstract final class ExifDtoConverter { + static ExifInfo fromDto(ExifResponseDto dto) { + return ExifInfo( + fileSize: dto.fileSizeInByte, + description: dto.description, + orientation: dto.orientation, + timeZone: dto.timeZone, + dateTimeOriginal: dto.dateTimeOriginal, + isFlipped: isOrientationFlipped(dto.orientation), + latitude: dto.latitude?.toDouble(), + longitude: dto.longitude?.toDouble(), + city: dto.city, + state: dto.state, + country: dto.country, + make: dto.make, + model: dto.model, + lens: dto.lensModel, + f: dto.fNumber?.toDouble(), + mm: dto.focalLength?.toDouble(), + iso: dto.iso?.toInt(), + exposureSeconds: _exposureTimeToSeconds(dto.exposureTime), + ); + } + + static bool isOrientationFlipped(String? orientation) { + final value = orientation == null ? null : int.tryParse(orientation); + if (value == null) { + return false; + } + final isRotated90CW = value == 5 || value == 6 || value == 90; + final isRotated270CW = value == 7 || value == 8 || value == -90; + return isRotated90CW || isRotated270CW; + } + + static double? _exposureTimeToSeconds(String? s) { + if (s == null) { + return null; + } + double? value = double.tryParse(s); + if (value != null) { + return value; + } + final parts = s.split("/"); + if (parts.length == 2) { + final numerator = double.tryParse(parts.firstOrNull ?? "-"); + final denominator = double.tryParse(parts.lastOrNull ?? "-"); + if (numerator != null && denominator != null) { + return numerator / denominator; + } + } + return null; + } +} diff --git a/mobile/lib/infrastructure/utils/user.converter.dart b/mobile/lib/infrastructure/utils/user.converter.dart new file mode 100644 index 0000000000..11f9ddec33 --- /dev/null +++ b/mobile/lib/infrastructure/utils/user.converter.dart @@ -0,0 +1,66 @@ +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:openapi/api.dart'; + +abstract final class UserConverter { + /// Base user dto used where the complete user object is not required + static UserDto fromSimpleUserDto(UserResponseDto dto) => UserDto( + uid: dto.id, + email: dto.email, + name: dto.name, + isAdmin: false, + updatedAt: DateTime.now(), + profileImagePath: dto.profileImagePath, + avatarColor: dto.avatarColor.toAvatarColor(), + ); + + static UserDto fromAdminDto( + UserAdminResponseDto adminDto, [ + UserPreferencesResponseDto? preferenceDto, + ]) => + UserDto( + uid: adminDto.id, + email: adminDto.email, + name: adminDto.name, + isAdmin: adminDto.isAdmin, + updatedAt: adminDto.updatedAt, + profileImagePath: adminDto.profileImagePath, + avatarColor: adminDto.avatarColor.toAvatarColor(), + memoryEnabled: preferenceDto?.memories.enabled ?? true, + inTimeline: false, + isPartnerSharedBy: false, + isPartnerSharedWith: false, + quotaUsageInBytes: adminDto.quotaUsageInBytes ?? 0, + quotaSizeInBytes: adminDto.quotaSizeInBytes ?? 0, + ); + + static UserDto fromPartnerDto(PartnerResponseDto dto) => UserDto( + uid: dto.id, + email: dto.email, + name: dto.name, + isAdmin: false, + updatedAt: DateTime.now(), + profileImagePath: dto.profileImagePath, + avatarColor: dto.avatarColor.toAvatarColor(), + memoryEnabled: false, + inTimeline: dto.inTimeline ?? false, + isPartnerSharedBy: false, + isPartnerSharedWith: false, + quotaUsageInBytes: 0, + quotaSizeInBytes: 0, + ); +} + +extension on UserAvatarColor { + AvatarColor toAvatarColor() => switch (this) { + UserAvatarColor.red => AvatarColor.red, + UserAvatarColor.green => AvatarColor.green, + UserAvatarColor.blue => AvatarColor.blue, + UserAvatarColor.purple => AvatarColor.purple, + UserAvatarColor.orange => AvatarColor.orange, + UserAvatarColor.pink => AvatarColor.pink, + UserAvatarColor.amber => AvatarColor.amber, + UserAvatarColor.yellow => AvatarColor.yellow, + UserAvatarColor.gray => AvatarColor.gray, + UserAvatarColor.primary || _ => AvatarColor.primary, + }; +} diff --git a/mobile/lib/interfaces/album.interface.dart b/mobile/lib/interfaces/album.interface.dart index 3a83a8feb7..c1696eda80 100644 --- a/mobile/lib/interfaces/album.interface.dart +++ b/mobile/lib/interfaces/album.interface.dart @@ -1,6 +1,6 @@ +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/interfaces/database.interface.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; @@ -31,9 +31,9 @@ abstract interface class IAlbumRepository implements IDatabaseRepository { Future count({bool? local}); - Future addUsers(Album album, List users); + Future addUsers(Album album, List users); - Future removeUsers(Album album, List users); + Future removeUsers(Album album, List users); Future addAssets(Album album, List assets); diff --git a/mobile/lib/interfaces/exif_info.interface.dart b/mobile/lib/interfaces/exif_info.interface.dart deleted file mode 100644 index ce379c926c..0000000000 --- a/mobile/lib/interfaces/exif_info.interface.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:immich_mobile/entities/exif_info.entity.dart'; -import 'package:immich_mobile/interfaces/database.interface.dart'; - -abstract interface class IExifInfoRepository implements IDatabaseRepository { - Future get(int id); - - Future update(ExifInfo exifInfo); - - Future> updateAll(List exifInfos); - - Future delete(int id); - - Future clearTable(); -} diff --git a/mobile/lib/interfaces/folder_api.interface.dart b/mobile/lib/interfaces/folder_api.interface.dart new file mode 100644 index 0000000000..68c1652e21 --- /dev/null +++ b/mobile/lib/interfaces/folder_api.interface.dart @@ -0,0 +1,6 @@ +import 'package:immich_mobile/entities/asset.entity.dart'; + +abstract interface class IFolderApiRepository { + Future> getAllUniquePaths(); + Future> getAssetsForPath(String? path); +} diff --git a/mobile/lib/interfaces/partner.interface.dart b/mobile/lib/interfaces/partner.interface.dart index 995e07c392..8e5fcb7a97 100644 --- a/mobile/lib/interfaces/partner.interface.dart +++ b/mobile/lib/interfaces/partner.interface.dart @@ -1,8 +1,8 @@ -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; abstract class IPartnerRepository { - Future> getSharedWith(); - Future> getSharedBy(); - Stream> watchSharedWith(); - Stream> watchSharedBy(); + Future> getSharedWith(); + Future> getSharedBy(); + Stream> watchSharedWith(); + Stream> watchSharedBy(); } diff --git a/mobile/lib/interfaces/partner_api.interface.dart b/mobile/lib/interfaces/partner_api.interface.dart index bca1baf66d..01149f473c 100644 --- a/mobile/lib/interfaces/partner_api.interface.dart +++ b/mobile/lib/interfaces/partner_api.interface.dart @@ -1,9 +1,9 @@ -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; abstract interface class IPartnerApiRepository { - Future> getAll(Direction direction); - Future create(String id); - Future update(String id, {required bool inTimeline}); + Future> getAll(Direction direction); + Future create(String id); + Future update(String id, {required bool inTimeline}); Future delete(String id); } diff --git a/mobile/lib/interfaces/user.interface.dart b/mobile/lib/interfaces/user.interface.dart deleted file mode 100644 index d099e0e50b..0000000000 --- a/mobile/lib/interfaces/user.interface.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:immich_mobile/entities/user.entity.dart'; -import 'package:immich_mobile/interfaces/database.interface.dart'; - -abstract interface class IUserRepository implements IDatabaseRepository { - Future get(String id); - - Future getByDbId(int id); - - Future> getByIds(List ids); - - Future> getAll({bool self = true, UserSort? sortBy}); - - /// Returns all users whose assets can be accessed (self+partners) - Future> getAllAccessible(); - - Future> upsertAll(List users); - - Future update(User user); - - Future deleteById(List ids); - - Future me(); - - Future clearTable(); -} - -enum UserSort { id } diff --git a/mobile/lib/interfaces/user_api.interface.dart b/mobile/lib/interfaces/user_api.interface.dart deleted file mode 100644 index 67ac3c0883..0000000000 --- a/mobile/lib/interfaces/user_api.interface.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'dart:typed_data'; - -import 'package:immich_mobile/entities/user.entity.dart'; - -abstract interface class IUserApiRepository { - Future> getAll(); - Future<({String profileImagePath})> createProfileImage({ - required String name, - required Uint8List data, - }); -} diff --git a/mobile/lib/mixins/error_logger.mixin.dart b/mobile/lib/mixins/error_logger.mixin.dart index 466028c338..482420a978 100644 --- a/mobile/lib/mixins/error_logger.mixin.dart +++ b/mobile/lib/mixins/error_logger.mixin.dart @@ -6,6 +6,7 @@ typedef AsyncFuture = Future>; mixin ErrorLoggerMixin { abstract final Logger logger; + // ignore: unintended_html_in_doc_comment /// Returns an AsyncValue if the future is successfully executed /// Else, logs the error to the overridden logger and returns an AsyncError<> AsyncFuture guardError( diff --git a/mobile/lib/models/activities/activity.model.dart b/mobile/lib/models/activities/activity.model.dart index 4702753f41..17f70d5d62 100644 --- a/mobile/lib/models/activities/activity.model.dart +++ b/mobile/lib/models/activities/activity.model.dart @@ -1,4 +1,4 @@ -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; enum ActivityType { comment, like } @@ -8,7 +8,7 @@ class Activity { final String? comment; final DateTime createdAt; final ActivityType type; - final User user; + final UserDto user; const Activity({ required this.id, @@ -25,7 +25,7 @@ class Activity { String? comment, DateTime? createdAt, ActivityType? type, - User? user, + UserDto? user, }) { return Activity( id: id ?? this.id, diff --git a/mobile/lib/models/folder/recursive_folder.model.dart b/mobile/lib/models/folder/recursive_folder.model.dart new file mode 100644 index 0000000000..5b54a2e1bf --- /dev/null +++ b/mobile/lib/models/folder/recursive_folder.model.dart @@ -0,0 +1,11 @@ +import 'package:immich_mobile/models/folder/root_folder.model.dart'; + +class RecursiveFolder extends RootFolder { + final String name; + + RecursiveFolder({ + required this.name, + required super.path, + required super.subfolders, + }); +} diff --git a/mobile/lib/models/folder/root_folder.model.dart b/mobile/lib/models/folder/root_folder.model.dart new file mode 100644 index 0000000000..8f72a539c0 --- /dev/null +++ b/mobile/lib/models/folder/root_folder.model.dart @@ -0,0 +1,11 @@ +import 'package:immich_mobile/models/folder/recursive_folder.model.dart'; + +class RootFolder { + final List subfolders; + final String path; + + RootFolder({ + required this.subfolders, + required this.path, + }); +} diff --git a/mobile/lib/models/search/search_curated_content.model.dart b/mobile/lib/models/search/search_curated_content.model.dart index af660bad9d..a3d74941b3 100644 --- a/mobile/lib/models/search/search_curated_content.model.dart +++ b/mobile/lib/models/search/search_curated_content.model.dart @@ -8,20 +8,26 @@ class SearchCuratedContent { /// The label to show associated with this curated object final String label; + /// The subtitle to show below the label + final String? subtitle; + /// The id to lookup the asset from the server final String id; SearchCuratedContent({ required this.label, required this.id, + this.subtitle, }); SearchCuratedContent copyWith({ String? label, + String? subtitle, String? id, }) { return SearchCuratedContent( label: label ?? this.label, + subtitle: subtitle ?? this.subtitle, id: id ?? this.id, ); } @@ -29,6 +35,7 @@ class SearchCuratedContent { Map toMap() { return { 'label': label, + 'subtitle': subtitle, 'id': id, }; } @@ -36,6 +43,7 @@ class SearchCuratedContent { factory SearchCuratedContent.fromMap(Map map) { return SearchCuratedContent( label: map['label'] as String, + subtitle: map['subtitle'] as String?, id: map['id'] as String, ); } @@ -46,13 +54,14 @@ class SearchCuratedContent { SearchCuratedContent.fromMap(json.decode(source) as Map); @override - String toString() => 'CuratedContent(label: $label, id: $id)'; + String toString() => + 'CuratedContent(label: $label, subtitle: $subtitle, id: $id)'; @override bool operator ==(covariant SearchCuratedContent other) { if (identical(this, other)) return true; - return other.label == label && other.id == id; + return other.label == label && other.subtitle == subtitle && other.id == id; } @override diff --git a/mobile/lib/pages/album/album_additional_shared_user_selection.page.dart b/mobile/lib/pages/album/album_additional_shared_user_selection.page.dart index 02026b828d..2dc41b396d 100644 --- a/mobile/lib/pages/album/album_additional_shared_user_selection.page.dart +++ b/mobile/lib/pages/album/album_additional_shared_user_selection.page.dart @@ -3,11 +3,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/album/suggested_shared_users.provider.dart'; -import 'package:immich_mobile/entities/album.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; @RoutePage() @@ -21,15 +21,15 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final AsyncValue> suggestedShareUsers = + final AsyncValue> suggestedShareUsers = ref.watch(otherUsersProvider); - final sharedUsersList = useState>({}); + final sharedUsersList = useState>({}); addNewUsersHandler() { - context.maybePop(sharedUsersList.value.map((e) => e.id).toList()); + context.maybePop(sharedUsersList.value.map((e) => e.uid).toList()); } - buildTileIcon(User user) { + buildTileIcon(UserDto user) { if (sharedUsersList.value.contains(user)) { return CircleAvatar( backgroundColor: context.primaryColor, @@ -45,7 +45,7 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget { } } - buildUserList(List users) { + buildUserList(List users) { List usersChip = []; for (var user in sharedUsersList.value) { @@ -53,7 +53,7 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Chip( - backgroundColor: context.primaryColor.withOpacity(0.15), + backgroundColor: context.primaryColor.withValues(alpha: 0.15), label: Text( user.name, style: const TextStyle( @@ -151,7 +151,7 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget { onData: (users) { for (var sharedUsers in album.sharedUsers) { users.removeWhere( - (u) => u.id == sharedUsers.id || u.id == album.ownerId, + (u) => u.uid == sharedUsers.id || u.uid == album.ownerId, ); } diff --git a/mobile/lib/pages/album/album_options.page.dart b/mobile/lib/pages/album/album_options.page.dart index 0e9bfeb2ce..a765be50b3 100644 --- a/mobile/lib/pages/album/album_options.page.dart +++ b/mobile/lib/pages/album/album_options.page.dart @@ -4,14 +4,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart' + as entity; import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/album/current_album.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; -import 'package:immich_mobile/utils/immich_loading_overlay.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/utils/immich_loading_overlay.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; @@ -26,7 +28,8 @@ class AlbumOptionsPage extends HookConsumerWidget { return const SizedBox(); } - final sharedUsers = useState(album.sharedUsers.toList()); + final sharedUsers = + useState(album.sharedUsers.map((u) => u.toDto()).toList()); final owner = album.owner.value; final userId = ref.watch(authProvider).userId; final activityEnabled = useState(album.activityEnabled); @@ -64,13 +67,13 @@ class AlbumOptionsPage extends HookConsumerWidget { isProcessing.value = false; } - void removeUserFromAlbum(User user) async { + void removeUserFromAlbum(UserDto user) async { isProcessing.value = true; try { await ref.read(albumProvider.notifier).removeUser(album, user); - album.sharedUsers.remove(user); - sharedUsers.value = album.sharedUsers.toList(); + album.sharedUsers.remove(entity.User.fromDto(user)); + sharedUsers.value = album.sharedUsers.map((u) => u.toDto()).toList(); } catch (error) { showErrorMessage(); } @@ -79,10 +82,10 @@ class AlbumOptionsPage extends HookConsumerWidget { isProcessing.value = false; } - void handleUserClick(User user) { + void handleUserClick(UserDto user) { var actions = []; - if (user.id == userId) { + if (user.uid == userId) { actions = [ ListTile( leading: const Icon(Icons.exit_to_app_rounded), @@ -123,8 +126,9 @@ class AlbumOptionsPage extends HookConsumerWidget { buildOwnerInfo() { return ListTile( - leading: - owner != null ? UserCircleAvatar(user: owner) : const SizedBox(), + leading: owner != null + ? UserCircleAvatar(user: owner.toDto()) + : const SizedBox(), title: Text( album.owner.value?.name ?? "", style: const TextStyle( @@ -166,10 +170,10 @@ class AlbumOptionsPage extends HookConsumerWidget { color: context.colorScheme.onSurfaceSecondary, ), ), - trailing: userId == user.id || isOwner + trailing: userId == user.uid || isOwner ? const Icon(Icons.more_horiz_rounded) : const SizedBox(), - onTap: userId == user.id || isOwner + onTap: userId == user.uid || isOwner ? () => handleUserClick(user) : null, ); diff --git a/mobile/lib/pages/album/album_shared_user_icons.dart b/mobile/lib/pages/album/album_shared_user_icons.dart index f417f9fb38..47ea476028 100644 --- a/mobile/lib/pages/album/album_shared_user_icons.dart +++ b/mobile/lib/pages/album/album_shared_user_icons.dart @@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/providers/album/current_album.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; @@ -12,7 +12,7 @@ class AlbumSharedUserIcons extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final sharedUsers = useRef>(const []); + final sharedUsers = useRef>(const []); sharedUsers.value = ref.watch( currentAlbumProvider.select((album) { if (album == null) { @@ -23,7 +23,7 @@ class AlbumSharedUserIcons extends HookConsumerWidget { return sharedUsers.value; } - return album.sharedUsers.toList(growable: false); + return album.sharedUsers.map((u) => u.toDto()).toList(growable: false); }), ); diff --git a/mobile/lib/pages/album/album_shared_user_selection.page.dart b/mobile/lib/pages/album/album_shared_user_selection.page.dart index ed8a45194d..1c797aa449 100644 --- a/mobile/lib/pages/album/album_shared_user_selection.page.dart +++ b/mobile/lib/pages/album/album_shared_user_selection.page.dart @@ -3,14 +3,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/album/album_title.provider.dart'; import 'package:immich_mobile/providers/album/suggested_shared_users.provider.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; @RoutePage() @@ -21,7 +21,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final sharedUsersList = useState>({}); + final sharedUsersList = useState>({}); final suggestedShareUsers = ref.watch(otherUsersProvider); createSharedAlbum() async { @@ -48,7 +48,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget { ); } - buildTileIcon(User user) { + buildTileIcon(UserDto user) { if (sharedUsersList.value.contains(user)) { return CircleAvatar( backgroundColor: context.primaryColor, @@ -64,7 +64,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget { } } - buildUserList(List users) { + buildUserList(List users) { List usersChip = []; for (var user in sharedUsersList.value) { @@ -72,7 +72,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Chip( - backgroundColor: context.primaryColor.withOpacity(0.15), + backgroundColor: context.primaryColor.withValues(alpha: 0.15), label: Text( user.email, style: const TextStyle( diff --git a/mobile/lib/pages/albums/albums.page.dart b/mobile/lib/pages/albums/albums.page.dart index ac6bd2f2fb..acdfbf385f 100644 --- a/mobile/lib/pages/albums/albums.page.dart +++ b/mobile/lib/pages/albums/albums.page.dart @@ -33,7 +33,7 @@ class AlbumsPage extends HookConsumerWidget { final searchController = useTextEditingController(); final debounceTimer = useRef(null); final filterMode = useState(QuickFilterMode.all); - final userId = ref.watch(currentUserProvider)?.id; + final userId = ref.watch(currentUserProvider)?.uid; final searchFocusNode = useFocusNode(); toggleViewMode() { @@ -106,9 +106,9 @@ class AlbumsPage extends HookConsumerWidget { borderRadius: BorderRadius.circular(24), gradient: LinearGradient( colors: [ - context.colorScheme.primary.withOpacity(0.075), - context.colorScheme.primary.withOpacity(0.09), - context.colorScheme.primary.withOpacity(0.075), + context.colorScheme.primary.withValues(alpha: 0.075), + context.colorScheme.primary.withValues(alpha: 0.09), + context.colorScheme.primary.withValues(alpha: 0.075), ], begin: Alignment.topLeft, end: Alignment.bottomRight, diff --git a/mobile/lib/pages/common/activities.page.dart b/mobile/lib/pages/common/activities.page.dart index 9678058111..84a8622fa5 100644 --- a/mobile/lib/pages/common/activities.page.dart +++ b/mobile/lib/pages/common/activities.page.dart @@ -7,12 +7,12 @@ import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/activities/activity.model.dart'; import 'package:immich_mobile/providers/activity.provider.dart'; -import 'package:immich_mobile/widgets/activities/activity_text_field.dart'; -import 'package:immich_mobile/widgets/activities/activity_tile.dart'; -import 'package:immich_mobile/widgets/activities/dismissible_activity.dart'; import 'package:immich_mobile/providers/album/current_album.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/widgets/activities/activity_text_field.dart'; +import 'package:immich_mobile/widgets/activities/activity_tile.dart'; +import 'package:immich_mobile/widgets/activities/dismissible_activity.dart'; @RoutePage() class ActivitiesPage extends HookConsumerWidget { @@ -72,7 +72,7 @@ class ActivitiesPage extends HookConsumerWidget { final activity = data[index]; final canDelete = activity.user.id == user?.id || - album.ownerId == user?.id; + album.ownerId == user?.uid; return Padding( padding: const EdgeInsets.all(5), diff --git a/mobile/lib/pages/common/app_log.page.dart b/mobile/lib/pages/common/app_log.page.dart index 56c32327dd..359a541de0 100644 --- a/mobile/lib/pages/common/app_log.page.dart +++ b/mobile/lib/pages/common/app_log.page.dart @@ -49,9 +49,9 @@ class AppLogPage extends HookConsumerWidget { Color getTileColor(LogLevel level) => switch (level) { LogLevel.info => Colors.transparent, - LogLevel.severe => Colors.redAccent.withOpacity(0.25), - LogLevel.warning => Colors.orangeAccent.withOpacity(0.25), - _ => context.primaryColor.withOpacity(0.1), + LogLevel.severe => Colors.redAccent.withValues(alpha: 0.25), + LogLevel.warning => Colors.orangeAccent.withValues(alpha: 0.25), + _ => context.primaryColor.withValues(alpha: 0.1), }; return Scaffold( diff --git a/mobile/lib/pages/editing/edit.page.dart b/mobile/lib/pages/editing/edit.page.dart index 385140eb59..9f2e6a2266 100644 --- a/mobile/lib/pages/editing/edit.page.dart +++ b/mobile/lib/pages/editing/edit.page.dart @@ -127,7 +127,7 @@ class EditImagePage extends ConsumerWidget { borderRadius: BorderRadius.circular(7), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.2), + color: Colors.black.withValues(alpha: 0.2), spreadRadius: 2, blurRadius: 10, offset: const Offset(0, 3), diff --git a/mobile/lib/pages/library/folder/folder.page.dart b/mobile/lib/pages/library/folder/folder.page.dart new file mode 100644 index 0000000000..af6f295970 --- /dev/null +++ b/mobile/lib/pages/library/folder/folder.page.dart @@ -0,0 +1,320 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/models/folder/recursive_folder.model.dart'; +import 'package:immich_mobile/models/folder/root_folder.model.dart'; +import 'package:immich_mobile/pages/common/large_leading_tile.dart'; +import 'package:immich_mobile/providers/folder.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/bytes_units.dart'; +import 'package:immich_mobile/widgets/asset_grid/thumbnail_image.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +RecursiveFolder? _findFolderInStructure( + RootFolder rootFolder, + RecursiveFolder targetFolder, +) { + for (final folder in rootFolder.subfolders) { + if (targetFolder.path == '/' && + folder.path.isEmpty && + folder.name == targetFolder.name) { + return folder; + } + + if (folder.path == targetFolder.path && folder.name == targetFolder.name) { + return folder; + } + + if (folder.subfolders.isNotEmpty) { + final found = _findFolderInStructure(folder, targetFolder); + if (found != null) return found; + } + } + return null; +} + +@RoutePage() +class FolderPage extends HookConsumerWidget { + final RecursiveFolder? folder; + + const FolderPage({super.key, this.folder}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final folderState = ref.watch(folderStructureProvider); + final currentFolder = useState(folder); + final sortOrder = useState(SortOrder.asc); + + useEffect( + () { + if (folder == null) { + ref + .read(folderStructureProvider.notifier) + .fetchFolders(sortOrder.value); + } + return null; + }, + [], + ); + + // Update current folder when root structure changes + useEffect( + () { + if (folder != null && folderState.hasValue) { + final updatedFolder = + _findFolderInStructure(folderState.value!, folder!); + if (updatedFolder != null) { + currentFolder.value = updatedFolder; + } + } + return null; + }, + [folderState], + ); + + void onToggleSortOrder() { + final newOrder = + sortOrder.value == SortOrder.asc ? SortOrder.desc : SortOrder.asc; + + ref.read(folderStructureProvider.notifier).fetchFolders(newOrder); + + sortOrder.value = newOrder; + } + + return Scaffold( + appBar: AppBar( + title: Text(currentFolder.value?.name ?? tr("folders")), + elevation: 0, + centerTitle: false, + actions: [ + IconButton( + icon: const Icon(Icons.swap_vert), + onPressed: onToggleSortOrder, + ), + ], + ), + body: folderState.when( + data: (rootFolder) { + if (folder == null) { + return FolderContent( + folder: rootFolder, + root: rootFolder, + sortOrder: sortOrder.value, + ); + } else { + return FolderContent( + folder: currentFolder.value!, + root: rootFolder, + sortOrder: sortOrder.value, + ); + } + }, + loading: () => const Center( + child: CircularProgressIndicator(), + ), + error: (error, stack) { + ImmichToast.show( + context: context, + msg: "failed_to_load_folder".tr(), + toastType: ToastType.error, + ); + return Center(child: const Text("failed_to_load_folder").tr()); + }, + ), + ); + } +} + +class FolderContent extends HookConsumerWidget { + final RootFolder? folder; + final RootFolder root; + final SortOrder sortOrder; + + const FolderContent({ + super.key, + this.folder, + required this.root, + this.sortOrder = SortOrder.asc, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final folderRenderlist = ref.watch(folderRenderListProvider(folder!)); + + // Initial asset fetch + useEffect( + () { + if (folder == null) return; + ref + .read(folderRenderListProvider(folder!).notifier) + .fetchAssets(sortOrder); + return null; + }, + [folder], + ); + + if (folder == null) { + return Center(child: const Text("folder_not_found").tr()); + } + + getSubtitle(int subFolderCount) { + if (subFolderCount > 0) { + return "$subFolderCount ${tr("folders")}".toLowerCase(); + } + + if (subFolderCount == 1) { + return "1 ${tr("folder")}".toLowerCase(); + } + + return ""; + } + + return Column( + children: [ + FolderPath(currentFolder: folder!, root: root), + Expanded( + child: folderRenderlist.when( + data: (list) { + if (folder!.subfolders.isEmpty && list.isEmpty) { + return Center(child: const Text("empty_folder").tr()); + } + + return ListView( + children: [ + if (folder!.subfolders.isNotEmpty) + ...folder!.subfolders.map( + (subfolder) => LargeLeadingTile( + leading: Icon( + Icons.folder, + color: context.primaryColor, + size: 48, + ), + title: Text( + subfolder.name, + softWrap: false, + overflow: TextOverflow.ellipsis, + style: context.textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + subtitle: subfolder.subfolders.isNotEmpty + ? Text( + getSubtitle(subfolder.subfolders.length), + style: context.textTheme.bodyMedium?.copyWith( + color: context.colorScheme.onSurfaceSecondary, + ), + ) + : null, + onTap: () => + context.pushRoute(FolderRoute(folder: subfolder)), + ), + ), + if (!list.isEmpty && + list.allAssets != null && + list.allAssets!.isNotEmpty) + ...list.allAssets!.map( + (asset) => LargeLeadingTile( + onTap: () => context.pushRoute( + GalleryViewerRoute( + renderList: list, + initialIndex: list.allAssets!.indexOf(asset), + ), + ), + leading: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(15), + ), + child: SizedBox( + width: 80, + height: 80, + child: ThumbnailImage( + asset: asset, + showStorageIndicator: false, + ), + ), + ), + title: Text( + asset.fileName, + maxLines: 2, + softWrap: false, + overflow: TextOverflow.ellipsis, + style: context.textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + subtitle: Text( + "${asset.exifInfo?.fileSize != null ? formatBytes(asset.exifInfo?.fileSize ?? 0) : ""} • ${DateFormat.yMMMd().format(asset.fileCreatedAt)}", + style: context.textTheme.bodyMedium?.copyWith( + color: context.colorScheme.onSurfaceSecondary, + ), + ), + ), + ), + ], + ); + }, + loading: () => const Center( + child: CircularProgressIndicator(), + ), + error: (error, stack) { + ImmichToast.show( + context: context, + msg: "failed_to_load_assets".tr(), + toastType: ToastType.error, + ); + return Center(child: const Text("failed_to_load_assets").tr()); + }, + ), + ), + ], + ); + } +} + +class FolderPath extends StatelessWidget { + final RootFolder currentFolder; + final RootFolder root; + + const FolderPath({ + super.key, + required this.currentFolder, + required this.root, + }); + + @override + Widget build(BuildContext context) { + if (currentFolder.path.isEmpty || currentFolder.path == '/') { + return const SizedBox.shrink(); + } + + return Container( + width: double.infinity, + alignment: Alignment.centerLeft, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + currentFolder.path, + style: TextStyle( + fontFamily: 'Inconsolata', + fontWeight: FontWeight.bold, + fontSize: 14, + color: context.colorScheme.onSurface.withAlpha(175), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/pages/library/library.page.dart b/mobile/lib/pages/library/library.page.dart index 92fe8cec17..86c411395b 100644 --- a/mobile/lib/pages/library/library.page.dart +++ b/mobile/lib/pages/library/library.page.dart @@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/album/album.provider.dart'; @@ -128,6 +128,19 @@ class QuickAccessButtons extends ConsumerWidget { bottomRight: Radius.circular(partners.isEmpty ? 20 : 0), ), ), + leading: const Icon( + Icons.folder_outlined, + size: 26, + ), + title: Text( + 'folders'.tr(), + style: context.textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w500, + ), + ), + onTap: () => context.pushRoute(FolderRoute()), + ), + ListTile( leading: const Icon( Icons.group_outlined, size: 26, @@ -150,7 +163,7 @@ class QuickAccessButtons extends ConsumerWidget { class PartnerList extends ConsumerWidget { const PartnerList({super.key, required this.partners}); - final List partners; + final List partners; @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/mobile/lib/pages/library/partner/partner.page.dart b/mobile/lib/pages/library/partner/partner.page.dart index 1e9e801210..faea069c50 100644 --- a/mobile/lib/pages/library/partner/partner.page.dart +++ b/mobile/lib/pages/library/partner/partner.page.dart @@ -2,10 +2,10 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/partner.provider.dart'; import 'package:immich_mobile/services/partner.service.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/user_avatar.dart'; @@ -16,7 +16,7 @@ class PartnerPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final List partners = ref.watch(partnerSharedByProvider); + final List partners = ref.watch(partnerSharedByProvider); final availableUsers = ref.watch(partnerAvailableProvider); addNewUsersHandler() async { @@ -29,13 +29,13 @@ class PartnerPage extends HookConsumerWidget { return; } - final selectedUser = await showDialog( + final selectedUser = await showDialog( context: context, builder: (context) { return SimpleDialog( title: const Text("partner_page_select_partner").tr(), children: [ - for (User u in users) + for (UserDto u in users) SimpleDialogOption( onPressed: () => context.pop(u), child: Row( @@ -67,7 +67,7 @@ class PartnerPage extends HookConsumerWidget { } } - onDeleteUser(User u) { + onDeleteUser(UserDto u) { return showDialog( context: context, builder: (BuildContext context) { @@ -80,7 +80,7 @@ class PartnerPage extends HookConsumerWidget { ); } - buildUserList(List users) { + buildUserList(List users) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/mobile/lib/pages/library/partner/partner_detail.page.dart b/mobile/lib/pages/library/partner/partner_detail.page.dart index f018726fe2..618d31affa 100644 --- a/mobile/lib/pages/library/partner/partner_detail.page.dart +++ b/mobile/lib/pages/library/partner/partner_detail.page.dart @@ -2,11 +2,11 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/multiselect.provider.dart'; import 'package:immich_mobile/providers/partner.provider.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; -import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/timeline.provider.dart'; import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -15,7 +15,7 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart'; class PartnerDetailPage extends HookConsumerWidget { const PartnerDetailPage({super.key, required this.partner}); - final User partner; + final UserDto partner; @override Widget build(BuildContext context, WidgetRef ref) { @@ -111,7 +111,7 @@ class PartnerDetailPage extends HookConsumerWidget { ), ), ), - renderListProvider: singleUserTimelineProvider(partner.isarId), + renderListProvider: singleUserTimelineProvider(partner.id), onRefresh: () => ref.read(assetProvider.notifier).getAllAsset(), deleteEnabled: false, favoriteEnabled: false, diff --git a/mobile/lib/pages/library/people/people_collection.page.dart b/mobile/lib/pages/library/people/people_collection.page.dart index 6c62d70058..c859e96ff2 100644 --- a/mobile/lib/pages/library/people/people_collection.page.dart +++ b/mobile/lib/pages/library/people/people_collection.page.dart @@ -1,8 +1,10 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/providers/search/people.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/services/api.service.dart'; @@ -16,6 +18,8 @@ class PeopleCollectionPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final people = ref.watch(getAllPeopleProvider); final headers = ApiService.getRequestHeaders(); + final formFocus = useFocusNode(); + final ValueNotifier search = useState(null); showNameEditModel( String personId, @@ -36,10 +40,70 @@ class PeopleCollectionPage extends HookConsumerWidget { return Scaffold( appBar: AppBar( - title: Text('people'.tr()), + automaticallyImplyLeading: search.value == null, + title: search.value != null + ? TextField( + focusNode: formFocus, + onTapOutside: (_) => formFocus.unfocus(), + onChanged: (value) => search.value = value, + decoration: InputDecoration( + contentPadding: const EdgeInsets.only(left: 24), + filled: true, + fillColor: context.primaryColor.withValues(alpha: 0.1), + hintStyle: context.textTheme.bodyLarge?.copyWith( + color: context.themeData.colorScheme.onSurfaceSecondary, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(25), + borderSide: BorderSide( + color: context.colorScheme.surfaceContainerHighest, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(25), + borderSide: BorderSide( + color: context.colorScheme.surfaceContainerHighest, + ), + ), + disabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(25), + borderSide: BorderSide( + color: context.colorScheme.surfaceContainerHighest, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(25), + borderSide: BorderSide( + color: context.colorScheme.primary.withAlpha(150), + ), + ), + prefixIcon: Icon( + Icons.search_rounded, + color: context.colorScheme.primary, + ), + hintText: 'search_filter_people_hint'.tr(), + ), + autofocus: true, + ) + : Text('people'.tr()), + actions: [ + IconButton( + icon: Icon(search.value != null ? Icons.close : Icons.search), + onPressed: () { + search.value = search.value == null ? '' : null; + }, + ), + ], ), body: people.when( data: (people) { + if (search.value != null) { + people = people.where((person) { + return person.name + .toLowerCase() + .contains(search.value!.toLowerCase()); + }).toList(); + } return GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: isTablet ? 6 : 3, diff --git a/mobile/lib/pages/library/shared_link/shared_link.page.dart b/mobile/lib/pages/library/shared_link/shared_link.page.dart index 7ad9943a89..f3afdb4a37 100644 --- a/mobile/lib/pages/library/shared_link/shared_link.page.dart +++ b/mobile/lib/pages/library/shared_link/shared_link.page.dart @@ -58,7 +58,8 @@ class SharedLinkPage extends HookConsumerWidget { child: Icon( Icons.link_off, size: 100, - color: context.themeData.iconTheme.color?.withOpacity(0.5), + color: + context.themeData.iconTheme.color?.withValues(alpha: 0.5), ), ), ), diff --git a/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart b/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart index 82819c94bd..4b311ba554 100644 --- a/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart +++ b/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart @@ -120,7 +120,7 @@ class SharedLinkEditPage extends HookConsumerWidget { fontSize: 14, ), disabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.grey.withOpacity(0.5)), + borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5)), ), ), onTapOutside: (_) => descriptionFocusNode.unfocus(), @@ -146,7 +146,7 @@ class SharedLinkEditPage extends HookConsumerWidget { fontSize: 14, ), disabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.grey.withOpacity(0.5)), + borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5)), ), ), ); diff --git a/mobile/lib/pages/photos/memory.page.dart b/mobile/lib/pages/photos/memory.page.dart index b082b7484f..211472f27a 100644 --- a/mobile/lib/pages/photos/memory.page.dart +++ b/mobile/lib/pages/photos/memory.page.dart @@ -1,5 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -61,6 +62,37 @@ class MemoryPage extends HookConsumerWidget { ); } + void toPreviousMemory() { + if (currentMemoryIndex.value > 0) { + // Move to the previous memory page + memoryPageController.previousPage( + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn, + ); + + // Wait for the next frame to ensure the page is built + SchedulerBinding.instance.addPostFrameCallback((_) { + final previousIndex = currentMemoryIndex.value - 1; + final previousMemoryController = + memoryAssetPageControllers[previousIndex]; + + // Ensure the controller is attached + if (previousMemoryController.hasClients) { + previousMemoryController + .jumpToPage(memories[previousIndex].assets.length - 1); + } else { + // Wait for the next frame until it is attached + SchedulerBinding.instance.addPostFrameCallback((_) { + if (previousMemoryController.hasClients) { + previousMemoryController + .jumpToPage(memories[previousIndex].assets.length - 1); + } + }); + } + }); + } + } + toNextAsset(int currentAssetIndex) { if (currentAssetIndex + 1 < currentMemory.value.assets.length) { // Go to the next asset @@ -77,6 +109,22 @@ class MemoryPage extends HookConsumerWidget { } } + toPreviousAsset(int currentAssetIndex) { + if (currentAssetIndex > 0) { + // Go to the previous asset + PageController controller = + memoryAssetPageControllers[currentMemoryIndex.value]; + + controller.previousPage( + curve: Curves.easeInOut, + duration: const Duration(milliseconds: 500), + ); + } else { + // Go to the previous memory since we are at the end of our assets + toPreviousMemory(); + } + } + updateProgressText() { assetProgress.value = "${currentAssetPage.value + 1}|${currentMemory.value.assets.length}"; @@ -141,17 +189,17 @@ class MemoryPage extends HookConsumerWidget { currentAssetPage.value = otherIndex; updateProgressText(); + // Wait for page change animation to finish + await Future.delayed(const Duration(milliseconds: 400)); + // And then precache the next asset + await precacheAsset(otherIndex + 1); + final asset = currentMemory.value.assets[otherIndex]; currentAsset.value = asset; ref.read(currentAssetProvider.notifier).set(asset); if (asset.isVideo || asset.isMotionPhoto) { ref.read(videoPlaybackValueProvider.notifier).reset(); } - - // Wait for page change animation to finish - await Future.delayed(const Duration(milliseconds: 400)); - // And then precache the next asset - await precacheAsset(otherIndex + 1); } /* Notification listener is used instead of OnPageChanged callback since OnPageChanged is called @@ -248,19 +296,42 @@ class MemoryPage extends HookConsumerWidget { itemCount: memories[mIndex].assets.length, itemBuilder: (context, index) { final asset = memories[mIndex].assets[index]; - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () { - toNextAsset(index); - }, - child: Container( - color: Colors.black, - child: MemoryCard( - asset: asset, - title: memories[mIndex].title, - showTitle: index == 0, + return Stack( + children: [ + Container( + color: Colors.black, + child: MemoryCard( + asset: asset, + title: memories[mIndex].title, + showTitle: index == 0, + ), ), - ), + Positioned.fill( + child: Row( + children: [ + // Left side of the screen + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + toPreviousAsset(index); + }, + ), + ), + + // Right side of the screen + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + toNextAsset(index); + }, + ), + ), + ], + ), + ), + ], ); }, ), @@ -279,7 +350,7 @@ class MemoryPage extends HookConsumerWidget { ); }, shape: const CircleBorder(), - color: Colors.white.withOpacity(0.2), + color: Colors.white.withValues(alpha: 0.2), elevation: 0, child: const Icon( Icons.close_rounded, diff --git a/mobile/lib/pages/photos/photos.page.dart b/mobile/lib/pages/photos/photos.page.dart index b3bfa366f2..c9211e984d 100644 --- a/mobile/lib/pages/photos/photos.page.dart +++ b/mobile/lib/pages/photos/photos.page.dart @@ -7,16 +7,16 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/album/album.provider.dart'; -import 'package:immich_mobile/providers/multiselect.provider.dart'; -import 'package:immich_mobile/providers/timeline.provider.dart'; -import 'package:immich_mobile/widgets/memories/memory_lane.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/multiselect.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; import 'package:immich_mobile/widgets/common/immich_app_bar.dart'; import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart'; +import 'package:immich_mobile/widgets/memories/memory_lane.dart'; @RoutePage() class PhotosPage extends HookConsumerWidget { @@ -110,7 +110,7 @@ class PhotosPage extends HookConsumerWidget { : const SizedBox(), renderListProvider: timelineUsers.length > 1 ? multiUsersTimelineProvider(timelineUsers) - : singleUserTimelineProvider(currentUser?.isarId), + : singleUserTimelineProvider(currentUser?.id), buildLoadingIndicator: buildLoadingIndicator, onRefresh: refreshAssets, stackEnabled: true, diff --git a/mobile/lib/pages/search/map/map.page.dart b/mobile/lib/pages/search/map/map.page.dart index 52ce13f958..0e64759241 100644 --- a/mobile/lib/pages/search/map/map.page.dart +++ b/mobile/lib/pages/search/map/map.page.dart @@ -11,7 +11,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/extensions/latlngbounds_extension.dart'; import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart'; import 'package:immich_mobile/models/map/map_event.model.dart'; import 'package:immich_mobile/models/map/map_marker.model.dart'; @@ -39,7 +38,7 @@ class MapPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final mapController = useRef(null); + final mapController = useRef(null); final markers = useRef>([]); final markersInBounds = useRef>([]); final bottomSheetStreamController = useStreamController(); @@ -162,7 +161,7 @@ class MapPage extends HookConsumerWidget { } } - void onMapCreated(MaplibreMapController controller) async { + void onMapCreated(MapLibreMapController controller) async { mapController.value = controller; controller.addListener(() { if (controller.isCameraMoving && selectedMarker.value != null) { @@ -389,7 +388,7 @@ class _MapWithMarker extends StatelessWidget { child: Stack( children: [ style.widgetWhen( - onData: (style) => MaplibreMap( + onData: (style) => MapLibreMap( initialCameraPosition: const CameraPosition(target: LatLng(0, 0)), styleString: style, @@ -403,7 +402,7 @@ class _MapWithMarker extends StatelessWidget { tiltGesturesEnabled: false, dragEnabled: false, myLocationEnabled: false, - attributionButtonPosition: AttributionButtonPosition.TopRight, + attributionButtonPosition: AttributionButtonPosition.topRight, rotateGesturesEnabled: false, ), ), diff --git a/mobile/lib/pages/search/map/map_location_picker.page.dart b/mobile/lib/pages/search/map/map_location_picker.page.dart index 487de69a1e..9d526d8080 100644 --- a/mobile/lib/pages/search/map/map_location_picker.page.dart +++ b/mobile/lib/pages/search/map/map_location_picker.page.dart @@ -24,7 +24,7 @@ class MapLocationPickerPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final selectedLatLng = useValueNotifier(initialLatLng); - final controller = useRef(null); + final controller = useRef(null); final marker = useRef(null); Future onStyleLoaded() async { @@ -74,7 +74,7 @@ class MapLocationPickerPage extends HookConsumerWidget { bottomRight: Radius.circular(40), ), ), - child: MaplibreMap( + child: MapLibreMap( initialCameraPosition: CameraPosition(target: initialLatLng, zoom: 12), styleString: style, diff --git a/mobile/lib/pages/search/search.page.dart b/mobile/lib/pages/search/search.page.dart index fcae1fb586..464ac31728 100644 --- a/mobile/lib/pages/search/search.page.dart +++ b/mobile/lib/pages/search/search.page.dart @@ -517,8 +517,6 @@ class SearchPage extends HookConsumerWidget { return Icons.abc_rounded; case TextSearchType.description: return Icons.text_snippet_outlined; - default: - return Icons.search_rounded; } } @@ -634,9 +632,9 @@ class SearchPage extends HookConsumerWidget { borderRadius: BorderRadius.circular(24), gradient: LinearGradient( colors: [ - context.colorScheme.primary.withOpacity(0.075), - context.colorScheme.primary.withOpacity(0.09), - context.colorScheme.primary.withOpacity(0.075), + context.colorScheme.primary.withValues(alpha: 0.075), + context.colorScheme.primary.withValues(alpha: 0.09), + context.colorScheme.primary.withValues(alpha: 0.075), ], begin: Alignment.topLeft, end: Alignment.bottomRight, diff --git a/mobile/lib/providers/activity.provider.dart b/mobile/lib/providers/activity.provider.dart index 8ae218c817..0dcc99320b 100644 --- a/mobile/lib/providers/activity.provider.dart +++ b/mobile/lib/providers/activity.provider.dart @@ -5,6 +5,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'activity.provider.g.dart'; +// ignore: unintended_html_in_doc_comment /// Maintains the current list of all activities for @riverpod class AlbumActivity extends _$AlbumActivity { diff --git a/mobile/lib/providers/activity.provider.g.dart b/mobile/lib/providers/activity.provider.g.dart index 9c20a09793..af574b991a 100644 --- a/mobile/lib/providers/activity.provider.g.dart +++ b/mobile/lib/providers/activity.provider.g.dart @@ -187,6 +187,8 @@ class AlbumActivityProvider extends AutoDisposeAsyncNotifierProviderImpl< } } +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element mixin AlbumActivityRef on AutoDisposeAsyncNotifierProviderRef> { /// The parameter `albumId` of this provider. String get albumId; @@ -206,4 +208,4 @@ class _AlbumActivityProviderElement String? get assetId => (origin as AlbumActivityProvider).assetId; } // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/activity_service.provider.dart b/mobile/lib/providers/activity_service.provider.dart index 6bd139c565..2d63e55354 100644 --- a/mobile/lib/providers/activity_service.provider.dart +++ b/mobile/lib/providers/activity_service.provider.dart @@ -1,3 +1,4 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/repositories/activity_api.repository.dart'; import 'package:immich_mobile/services/activity.service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -5,5 +6,5 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'activity_service.provider.g.dart'; @riverpod -ActivityService activityService(ActivityServiceRef ref) => +ActivityService activityService(Ref ref) => ActivityService(ref.watch(activityApiRepositoryProvider)); diff --git a/mobile/lib/providers/activity_service.provider.g.dart b/mobile/lib/providers/activity_service.provider.g.dart index d42b2a39e4..2bf160c487 100644 --- a/mobile/lib/providers/activity_service.provider.g.dart +++ b/mobile/lib/providers/activity_service.provider.g.dart @@ -6,7 +6,7 @@ part of 'activity_service.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$activityServiceHash() => r'23a3ee7db71676d2719daa64217a683cc5c7eab0'; +String _$activityServiceHash() => r'ce775779787588defe1e76406e09a9c109470310'; /// See also [activityService]. @ProviderFor(activityService) @@ -20,6 +20,8 @@ final activityServiceProvider = AutoDisposeProvider.internal( allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef ActivityServiceRef = AutoDisposeProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/activity_statistics.provider.dart b/mobile/lib/providers/activity_statistics.provider.dart index b1d2b4b987..c260a7a547 100644 --- a/mobile/lib/providers/activity_statistics.provider.dart +++ b/mobile/lib/providers/activity_statistics.provider.dart @@ -3,6 +3,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'activity_statistics.provider.g.dart'; +// ignore: unintended_html_in_doc_comment /// Maintains the current number of comments by @riverpod class ActivityStatistics extends _$ActivityStatistics { diff --git a/mobile/lib/providers/activity_statistics.provider.g.dart b/mobile/lib/providers/activity_statistics.provider.g.dart index 16a3c0e81b..d2de32c0aa 100644 --- a/mobile/lib/providers/activity_statistics.provider.g.dart +++ b/mobile/lib/providers/activity_statistics.provider.g.dart @@ -186,6 +186,8 @@ class ActivityStatisticsProvider } } +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element mixin ActivityStatisticsRef on AutoDisposeNotifierProviderRef { /// The parameter `albumId` of this provider. String get albumId; @@ -205,4 +207,4 @@ class _ActivityStatisticsProviderElement String? get assetId => (origin as ActivityStatisticsProvider).assetId; } // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/album/album.provider.dart b/mobile/lib/providers/album/album.provider.dart index a2d7db68ec..39f5af7344 100644 --- a/mobile/lib/providers/album/album.provider.dart +++ b/mobile/lib/providers/album/album.provider.dart @@ -2,11 +2,11 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/services/album.service.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/album.entity.dart'; final isRefreshingRemoteAlbumProvider = StateProvider((ref) => false); @@ -88,7 +88,7 @@ class AlbumNotifier extends StateNotifier> { await albumService.addUsers(album, userIds); } - Future removeUser(Album album, User user) async { + Future removeUser(Album album, UserDto user) async { final isRemoved = await albumService.removeUser(album, user); if (isRemoved && album.sharedUsers.isEmpty) { diff --git a/mobile/lib/providers/album/album_sort_by_options.provider.g.dart b/mobile/lib/providers/album/album_sort_by_options.provider.g.dart index 9a05bb6c7d..ba20e7eb66 100644 --- a/mobile/lib/providers/album/album_sort_by_options.provider.g.dart +++ b/mobile/lib/providers/album/album_sort_by_options.provider.g.dart @@ -40,4 +40,4 @@ final albumSortOrderProvider = typedef _$AlbumSortOrder = AutoDisposeNotifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/album/current_album.provider.g.dart b/mobile/lib/providers/album/current_album.provider.g.dart index 50e8854637..60ebe3e333 100644 --- a/mobile/lib/providers/album/current_album.provider.g.dart +++ b/mobile/lib/providers/album/current_album.provider.g.dart @@ -22,4 +22,4 @@ final currentAlbumProvider = typedef _$CurrentAlbum = AutoDisposeNotifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/album/suggested_shared_users.provider.dart b/mobile/lib/providers/album/suggested_shared_users.provider.dart index fe8a1fccce..3c8dcb6733 100644 --- a/mobile/lib/providers/album/suggested_shared_users.provider.dart +++ b/mobile/lib/providers/album/suggested_shared_users.provider.dart @@ -1,9 +1,15 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; -import 'package:immich_mobile/services/user.service.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; -final otherUsersProvider = FutureProvider.autoDispose>((ref) { +final otherUsersProvider = + FutureProvider.autoDispose>((ref) async { UserService userService = ref.watch(userServiceProvider); + final currentUser = ref.watch(currentUserProvider); - return userService.getUsers(); + final allUsers = await userService.getAll(); + allUsers.removeWhere((u) => currentUser?.id == u.id); + return allUsers; }); diff --git a/mobile/lib/providers/api.provider.dart b/mobile/lib/providers/api.provider.dart index 8e48324c92..a994dacf2f 100644 --- a/mobile/lib/providers/api.provider.dart +++ b/mobile/lib/providers/api.provider.dart @@ -1,7 +1,8 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'api.provider.g.dart'; @Riverpod(keepAlive: true) -ApiService apiService(ApiServiceRef ref) => ApiService(); +ApiService apiService(Ref ref) => ApiService(); diff --git a/mobile/lib/providers/api.provider.g.dart b/mobile/lib/providers/api.provider.g.dart index 421d554314..76ccb4ad6d 100644 --- a/mobile/lib/providers/api.provider.g.dart +++ b/mobile/lib/providers/api.provider.g.dart @@ -6,7 +6,7 @@ part of 'api.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$apiServiceHash() => r'5b8beddb448316bdae5e3963ff77601653715729'; +String _$apiServiceHash() => r'93a7e3b4d3004741abc3061c4688239c3a72f9c4'; /// See also [apiService]. @ProviderFor(apiService) @@ -19,6 +19,8 @@ final apiServiceProvider = Provider.internal( allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef ApiServiceRef = ProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index 92c199ab76..ccd073ef07 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -17,6 +17,7 @@ import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/tab.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/services/background.service.dart'; +import 'package:isar/isar.dart'; import 'package:permission_handler/permission_handler.dart'; enum AppLifeCycleEnum { @@ -114,11 +115,13 @@ class AppLifeCycleNotifier extends StateNotifier { _ref.read(websocketProvider.notifier).disconnect(); } - unawaited(LogService.I.flush()); + LogService.I.flush(); } - void handleAppDetached() { + Future handleAppDetached() async { state = AppLifeCycleEnum.detached; + LogService.I.flush(); + await Isar.getInstance()?.close(); // no guarantee this is called at all _ref.read(manualUploadProvider.notifier).cancelBackup(); } diff --git a/mobile/lib/providers/app_settings.provider.dart b/mobile/lib/providers/app_settings.provider.dart index a598be7a1f..81c5c8e201 100644 --- a/mobile/lib/providers/app_settings.provider.dart +++ b/mobile/lib/providers/app_settings.provider.dart @@ -1,8 +1,8 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'app_settings.provider.g.dart'; @Riverpod(keepAlive: true) -AppSettingsService appSettingsService(AppSettingsServiceRef ref) => - AppSettingsService(); +AppSettingsService appSettingsService(Ref ref) => AppSettingsService(); diff --git a/mobile/lib/providers/app_settings.provider.g.dart b/mobile/lib/providers/app_settings.provider.g.dart index a9954382a7..88cab49c1b 100644 --- a/mobile/lib/providers/app_settings.provider.g.dart +++ b/mobile/lib/providers/app_settings.provider.g.dart @@ -7,7 +7,7 @@ part of 'app_settings.provider.dart'; // ************************************************************************** String _$appSettingsServiceHash() => - r'45ea609a91d250290431a7a08a14d16b37c7515d'; + r'3736e0d384ec7b1f896938589656dd6eb1552d60'; /// See also [appSettingsService]. @ProviderFor(appSettingsService) @@ -21,6 +21,8 @@ final appSettingsServiceProvider = Provider.internal( allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef AppSettingsServiceRef = ProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/asset.provider.dart b/mobile/lib/providers/asset.provider.dart index f093d90071..fef0f7b420 100644 --- a/mobile/lib/providers/asset.provider.dart +++ b/mobile/lib/providers/asset.provider.dart @@ -1,15 +1,16 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/memory.provider.dart'; import 'package:immich_mobile/services/album.service.dart'; import 'package:immich_mobile/services/asset.service.dart'; import 'package:immich_mobile/services/etag.service.dart'; import 'package:immich_mobile/services/exif.service.dart'; import 'package:immich_mobile/services/sync.service.dart'; -import 'package:immich_mobile/services/user.service.dart'; import 'package:logging/logging.dart'; final assetProvider = StateNotifierProvider((ref) { @@ -31,7 +32,7 @@ class AssetNotifier extends StateNotifier { final SyncService _syncService; final ETagService _etagService; final ExifService _exifService; - final StateNotifierProviderRef _ref; + final Ref _ref; final log = Logger('AssetNotifier'); bool _getAllAssetInProgress = false; bool _deleteInProgress = false; @@ -59,7 +60,7 @@ class AssetNotifier extends StateNotifier { await clearAllAssets(); log.info("Manual refresh requested, cleared assets and albums from db"); } - final users = await _userService.getUsersFromServer(); + final users = await _syncService.getUsersFromServer(); bool changedUsers = false; if (users != null) { changedUsers = await _syncService.syncUsersFromServer(users); @@ -86,7 +87,7 @@ class AssetNotifier extends StateNotifier { _assetService.clearTable(), _exifService.clearTable(), _albumService.clearTable(), - _userService.clearTable(), + _userService.deleteAll(), _etagService.clearTable(), ]); } diff --git a/mobile/lib/providers/asset_viewer/asset_people.provider.g.dart b/mobile/lib/providers/asset_viewer/asset_people.provider.g.dart index df6ee779cc..ebe8a14186 100644 --- a/mobile/lib/providers/asset_viewer/asset_people.provider.g.dart +++ b/mobile/lib/providers/asset_viewer/asset_people.provider.g.dart @@ -171,6 +171,8 @@ class AssetPeopleNotifierProvider extends AutoDisposeAsyncNotifierProviderImpl< } } +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element mixin AssetPeopleNotifierRef on AutoDisposeAsyncNotifierProviderRef> { /// The parameter `asset` of this provider. @@ -186,4 +188,4 @@ class _AssetPeopleNotifierProviderElement Asset get asset => (origin as AssetPeopleNotifierProvider).asset; } // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/asset_viewer/asset_stack.provider.dart b/mobile/lib/providers/asset_viewer/asset_stack.provider.dart index d7049e4e1e..0edefde526 100644 --- a/mobile/lib/providers/asset_viewer/asset_stack.provider.dart +++ b/mobile/lib/providers/asset_viewer/asset_stack.provider.dart @@ -39,6 +39,6 @@ final assetStackStateProvider = StateNotifierProvider.autoDispose ); @riverpod -int assetStackIndex(AssetStackIndexRef ref, Asset asset) { +int assetStackIndex(Ref ref, Asset asset) { return -1; } diff --git a/mobile/lib/providers/asset_viewer/asset_stack.provider.g.dart b/mobile/lib/providers/asset_viewer/asset_stack.provider.g.dart index 142e46d322..5d4051b285 100644 --- a/mobile/lib/providers/asset_viewer/asset_stack.provider.g.dart +++ b/mobile/lib/providers/asset_viewer/asset_stack.provider.g.dart @@ -6,7 +6,7 @@ part of 'asset_stack.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$assetStackIndexHash() => r'0f2df55e929767c8c698bd432b5e6e351d000a16'; +String _$assetStackIndexHash() => r'38b4b0116e3e4592620b118ae01cf89b77da9cfe'; /// Copied from Dart SDK class _SystemHash { @@ -142,6 +142,8 @@ class AssetStackIndexProvider extends AutoDisposeProvider { } } +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element mixin AssetStackIndexRef on AutoDisposeProviderRef { /// The parameter `asset` of this provider. Asset get asset; @@ -155,4 +157,4 @@ class _AssetStackIndexProviderElement extends AutoDisposeProviderElement Asset get asset => (origin as AssetStackIndexProvider).asset; } // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/asset_viewer/current_asset.provider.g.dart b/mobile/lib/providers/asset_viewer/current_asset.provider.g.dart index 96628dab58..53b02c2ace 100644 --- a/mobile/lib/providers/asset_viewer/current_asset.provider.g.dart +++ b/mobile/lib/providers/asset_viewer/current_asset.provider.g.dart @@ -22,4 +22,4 @@ final currentAssetProvider = typedef _$CurrentAsset = AutoDisposeNotifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/auth.provider.dart b/mobile/lib/providers/auth.provider.dart index e2939e89ce..2a140911b0 100644 --- a/mobile/lib/providers/auth.provider.dart +++ b/mobile/lib/providers/auth.provider.dart @@ -2,8 +2,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_udid/flutter_udid.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/models/auth/auth_state.model.dart'; import 'package:immich_mobile/models/auth/login_response.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; @@ -105,7 +106,7 @@ class AuthNotifier extends StateNotifier { String deviceId = Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid; - User? user = Store.tryGet(StoreKey.currentUser); + UserDto? user = Store.tryGet(StoreKey.currentUser); UserAdminResponseDto? userResponse; UserPreferencesResponseDto? userPreferences; @@ -141,18 +142,18 @@ class AuthNotifier extends StateNotifier { // If the user information is successfully retrieved, update the store // Due to the flow of the code, this will always happen on first login - if (userResponse != null) { + if (userResponse == null) { + _log.severe("Unable to get user information from the server."); + } else { await Store.put(StoreKey.deviceId, deviceId); await Store.put(StoreKey.deviceIdHash, fastHash(deviceId)); await Store.put( StoreKey.currentUser, - User.fromUserDto(userResponse, userPreferences), + UserConverter.fromAdminDto(userResponse, userPreferences), ); await Store.put(StoreKey.accessToken, accessToken); - user = User.fromUserDto(userResponse, userPreferences); - } else { - _log.severe("Unable to get user information from the server."); + user = UserConverter.fromAdminDto(userResponse, userPreferences); } // If the user is null, the login was not successful @@ -163,7 +164,7 @@ class AuthNotifier extends StateNotifier { state = state.copyWith( isAuthenticated: true, - userId: user.id, + userId: user.uid, userEmail: user.email, name: user.name, profileImagePath: user.profileImagePath, diff --git a/mobile/lib/providers/backup/backup_verification.provider.g.dart b/mobile/lib/providers/backup/backup_verification.provider.g.dart index 9b52698847..bae3ec366b 100644 --- a/mobile/lib/providers/backup/backup_verification.provider.g.dart +++ b/mobile/lib/providers/backup/backup_verification.provider.g.dart @@ -24,4 +24,4 @@ final backupVerificationProvider = typedef _$BackupVerification = AutoDisposeNotifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/folder.provider.dart b/mobile/lib/providers/folder.provider.dart new file mode 100644 index 0000000000..810c2cea73 --- /dev/null +++ b/mobile/lib/providers/folder.provider.dart @@ -0,0 +1,62 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/models/folder/root_folder.model.dart'; +import 'package:immich_mobile/services/folder.service.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:logging/logging.dart'; + +class FolderStructureNotifier extends StateNotifier> { + final FolderService _folderService; + final Logger _log = Logger("FolderStructureNotifier"); + + FolderStructureNotifier(this._folderService) : super(const AsyncLoading()); + + Future fetchFolders(SortOrder order) async { + try { + final folders = await _folderService.getFolderStructure(order); + state = AsyncData(folders); + } catch (e, stack) { + _log.severe("Failed to build folder structure", e, stack); + state = AsyncError(e, stack); + } + } +} + +final folderStructureProvider = + StateNotifierProvider>( + (ref) { + return FolderStructureNotifier( + ref.watch(folderServiceProvider), + ); +}); + +class FolderRenderListNotifier extends StateNotifier> { + final FolderService _folderService; + final RootFolder _folder; + final Logger _log = Logger("FolderAssetsNotifier"); + + FolderRenderListNotifier(this._folderService, this._folder) + : super(const AsyncLoading()); + + Future fetchAssets(SortOrder order) async { + try { + final assets = await _folderService.getFolderAssets(_folder, order); + final renderList = + await RenderList.fromAssets(assets, GroupAssetsBy.none); + state = AsyncData(renderList); + } catch (e, stack) { + _log.severe("Failed to fetch folder assets", e, stack); + state = AsyncError(e, stack); + } + } +} + +final folderRenderListProvider = StateNotifierProvider.family< + FolderRenderListNotifier, + AsyncValue, + RootFolder>((ref, folder) { + return FolderRenderListNotifier( + ref.watch(folderServiceProvider), + folder, + ); +}); diff --git a/mobile/lib/providers/immich_logo_provider.dart b/mobile/lib/providers/immich_logo_provider.dart index c5c65fcfe0..a52aba5f9e 100644 --- a/mobile/lib/providers/immich_logo_provider.dart +++ b/mobile/lib/providers/immich_logo_provider.dart @@ -1,12 +1,13 @@ import 'dart:convert'; import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'immich_logo_provider.g.dart'; @riverpod -Future immichLogo(ImmichLogoRef ref) async { +Future immichLogo(Ref ref) async { final json = await rootBundle.loadString('assets/immich-logo.json'); final j = jsonDecode(json); return base64Decode(j['content']); diff --git a/mobile/lib/providers/immich_logo_provider.g.dart b/mobile/lib/providers/immich_logo_provider.g.dart index 1a95814e35..0889e60fda 100644 --- a/mobile/lib/providers/immich_logo_provider.g.dart +++ b/mobile/lib/providers/immich_logo_provider.g.dart @@ -6,7 +6,7 @@ part of 'immich_logo_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$immichLogoHash() => r'040cc44fae3339e0f40a091fb3b2f2abe9f83acd'; +String _$immichLogoHash() => r'6f23d217c44279537b7edee1ca80ebf47f69a4d0'; /// See also [immichLogo]. @ProviderFor(immichLogo) @@ -19,6 +19,8 @@ final immichLogoProvider = AutoDisposeFutureProvider.internal( allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef ImmichLogoRef = AutoDisposeFutureProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/infrastructure/db.provider.dart b/mobile/lib/providers/infrastructure/db.provider.dart index 447039478e..84010b3b96 100644 --- a/mobile/lib/providers/infrastructure/db.provider.dart +++ b/mobile/lib/providers/infrastructure/db.provider.dart @@ -1,7 +1,8 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:isar/isar.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'db.provider.g.dart'; @Riverpod(keepAlive: true) -Isar isar(IsarRef ref) => throw UnimplementedError('isar'); +Isar isar(Ref ref) => throw UnimplementedError('isar'); diff --git a/mobile/lib/providers/infrastructure/db.provider.g.dart b/mobile/lib/providers/infrastructure/db.provider.g.dart index 1bfe7b0ad5..33b330192f 100644 --- a/mobile/lib/providers/infrastructure/db.provider.g.dart +++ b/mobile/lib/providers/infrastructure/db.provider.g.dart @@ -6,7 +6,7 @@ part of 'db.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$isarHash() => r'f0e886fa20e56dd1dc0082fcc723016289bd03cb'; +String _$isarHash() => r'69d3a06aa7e69a4381478e03f7956eb07d7f7feb'; /// See also [isar]. @ProviderFor(isar) @@ -19,6 +19,8 @@ final isarProvider = Provider.internal( allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef IsarRef = ProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/infrastructure/exif.provider.dart b/mobile/lib/providers/infrastructure/exif.provider.dart new file mode 100644 index 0000000000..ecb67dd2fe --- /dev/null +++ b/mobile/lib/providers/infrastructure/exif.provider.dart @@ -0,0 +1,11 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; +import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'exif.provider.g.dart'; + +@Riverpod(keepAlive: true) +IExifInfoRepository exifRepository(Ref ref) => + IsarExifRepository(ref.watch(isarProvider)); diff --git a/mobile/lib/providers/infrastructure/exif.provider.g.dart b/mobile/lib/providers/infrastructure/exif.provider.g.dart new file mode 100644 index 0000000000..053abf18cc --- /dev/null +++ b/mobile/lib/providers/infrastructure/exif.provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'exif.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exifRepositoryHash() => r'f0abe778ed61fbb257001fdf2ac6e17814011fee'; + +/// See also [exifRepository]. +@ProviderFor(exifRepository) +final exifRepositoryProvider = Provider.internal( + exifRepository, + name: r'exifRepositoryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$exifRepositoryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef ExifRepositoryRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/infrastructure/store.provider.dart b/mobile/lib/providers/infrastructure/store.provider.dart index cb7024ad51..c7f0c04a4f 100644 --- a/mobile/lib/providers/infrastructure/store.provider.dart +++ b/mobile/lib/providers/infrastructure/store.provider.dart @@ -1,10 +1,15 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/interfaces/store.interface.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'store.provider.g.dart'; -@riverpod -IStoreRepository storeRepository(StoreRepositoryRef ref) => +@Riverpod(keepAlive: true) +IStoreRepository storeRepository(Ref ref) => IsarStoreRepository(ref.watch(isarProvider)); + +@Riverpod(keepAlive: true) +StoreService storeService(Ref _) => StoreService.I; diff --git a/mobile/lib/providers/infrastructure/store.provider.g.dart b/mobile/lib/providers/infrastructure/store.provider.g.dart index f53d677384..ffdcd291b6 100644 --- a/mobile/lib/providers/infrastructure/store.provider.g.dart +++ b/mobile/lib/providers/infrastructure/store.provider.g.dart @@ -6,11 +6,11 @@ part of 'store.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$storeRepositoryHash() => r'2f1c3e2e2db5082a40eb30a183a6c770f5b09d76'; +String _$storeRepositoryHash() => r'99d24875d30c5e86b1c6caa352a0026167114e62'; /// See also [storeRepository]. @ProviderFor(storeRepository) -final storeRepositoryProvider = AutoDisposeProvider.internal( +final storeRepositoryProvider = Provider.internal( storeRepository, name: r'storeRepositoryProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') @@ -20,6 +20,24 @@ final storeRepositoryProvider = AutoDisposeProvider.internal( allTransitiveDependencies: null, ); -typedef StoreRepositoryRef = AutoDisposeProviderRef; +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef StoreRepositoryRef = ProviderRef; +String _$storeServiceHash() => r'250e10497c42df360e9e1f9a618d0b19c1b5b0a0'; + +/// See also [storeService]. +@ProviderFor(storeService) +final storeServiceProvider = Provider.internal( + storeService, + name: r'storeServiceProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$storeServiceHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef StoreServiceRef = ProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/infrastructure/sync_stream.provider.dart b/mobile/lib/providers/infrastructure/sync_stream.provider.dart new file mode 100644 index 0000000000..64f1a6cb05 --- /dev/null +++ b/mobile/lib/providers/infrastructure/sync_stream.provider.dart @@ -0,0 +1,24 @@ +import 'dart:async'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/services/sync_stream.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; + +final syncStreamServiceProvider = Provider( + (ref) { + final instance = SyncStreamService( + ref.watch(syncApiRepositoryProvider), + ); + + ref.onDispose(() => unawaited(instance.dispose())); + + return instance; + }, +); + +final syncApiRepositoryProvider = Provider( + (ref) => SyncApiRepository( + ref.watch(apiServiceProvider), + ), +); diff --git a/mobile/lib/providers/infrastructure/user.provider.dart b/mobile/lib/providers/infrastructure/user.provider.dart new file mode 100644 index 0000000000..e6eaac06b6 --- /dev/null +++ b/mobile/lib/providers/infrastructure/user.provider.dart @@ -0,0 +1,27 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/interfaces/user.interface.dart'; +import 'package:immich_mobile/domain/interfaces/user_api.repository.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'user.provider.g.dart'; + +@Riverpod(keepAlive: true) +IUserRepository userRepository(Ref ref) => + IsarUserRepository(ref.watch(isarProvider)); + +@Riverpod(keepAlive: true) +IUserApiRepository userApiRepository(Ref ref) => + UserApiRepository(ref.watch(apiServiceProvider).usersApi); + +@Riverpod(keepAlive: true) +UserService userService(Ref ref) => UserService( + userRepository: ref.watch(userRepositoryProvider), + userApiRepository: ref.watch(userApiRepositoryProvider), + storeService: ref.watch(storeServiceProvider), + ); diff --git a/mobile/lib/providers/infrastructure/user.provider.g.dart b/mobile/lib/providers/infrastructure/user.provider.g.dart new file mode 100644 index 0000000000..fdb4223ee8 --- /dev/null +++ b/mobile/lib/providers/infrastructure/user.provider.g.dart @@ -0,0 +1,60 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$userRepositoryHash() => r'1a2ac726bcc44397dcaecf449084fefd336696d4'; + +/// See also [userRepository]. +@ProviderFor(userRepository) +final userRepositoryProvider = Provider.internal( + userRepository, + name: r'userRepositoryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$userRepositoryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef UserRepositoryRef = ProviderRef; +String _$userApiRepositoryHash() => r'6b19f2c99fb83162a5ceb91adb8589eaae01bc92'; + +/// See also [userApiRepository]. +@ProviderFor(userApiRepository) +final userApiRepositoryProvider = Provider.internal( + userApiRepository, + name: r'userApiRepositoryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$userApiRepositoryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef UserApiRepositoryRef = ProviderRef; +String _$userServiceHash() => r'4a0873357b7115b4d6bfa8e89b847c0b74ce0d93'; + +/// See also [userService]. +@ProviderFor(userService) +final userServiceProvider = Provider.internal( + userService, + name: r'userServiceProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$userServiceHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef UserServiceRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/map/map_marker.provider.dart b/mobile/lib/providers/map/map_marker.provider.dart index c8e8a77c17..23342b77b3 100644 --- a/mobile/lib/providers/map/map_marker.provider.dart +++ b/mobile/lib/providers/map/map_marker.provider.dart @@ -1,3 +1,4 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/models/map/map_marker.model.dart'; import 'package:immich_mobile/providers/map/map_service.provider.dart'; import 'package:immich_mobile/providers/map/map_state.provider.dart'; @@ -6,7 +7,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'map_marker.provider.g.dart'; @riverpod -Future> mapMarkers(MapMarkersRef ref) async { +Future> mapMarkers(Ref ref) async { final service = ref.read(mapServiceProvider); final mapState = ref.read(mapStateNotifierProvider); DateTime? fileCreatedAfter; diff --git a/mobile/lib/providers/map/map_marker.provider.g.dart b/mobile/lib/providers/map/map_marker.provider.g.dart index ce11b4ebff..76cc44a103 100644 --- a/mobile/lib/providers/map/map_marker.provider.g.dart +++ b/mobile/lib/providers/map/map_marker.provider.g.dart @@ -6,7 +6,7 @@ part of 'map_marker.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$mapMarkersHash() => r'737d52f3d02e6a458b11d730f2fe522c39ee1ebf'; +String _$mapMarkersHash() => r'f33ac4baa3251b3f06423aece89673315966f885'; /// See also [mapMarkers]. @ProviderFor(mapMarkers) @@ -19,6 +19,8 @@ final mapMarkersProvider = AutoDisposeFutureProvider>.internal( allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef MapMarkersRef = AutoDisposeFutureProviderRef>; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/map/map_service.provider.dart b/mobile/lib/providers/map/map_service.provider.dart index 2773f7dcc9..0d998c5173 100644 --- a/mobile/lib/providers/map/map_service.provider.dart +++ b/mobile/lib/providers/map/map_service.provider.dart @@ -1,3 +1,4 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/services/map.service.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -5,5 +6,4 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'map_service.provider.g.dart'; @riverpod -MapSerivce mapService(MapServiceRef ref) => - MapSerivce(ref.watch(apiServiceProvider)); +MapSerivce mapService(Ref ref) => MapSerivce(ref.watch(apiServiceProvider)); diff --git a/mobile/lib/providers/map/map_service.provider.g.dart b/mobile/lib/providers/map/map_service.provider.g.dart index 7b4e68eaee..70e44da621 100644 --- a/mobile/lib/providers/map/map_service.provider.g.dart +++ b/mobile/lib/providers/map/map_service.provider.g.dart @@ -6,7 +6,7 @@ part of 'map_service.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$mapServiceHash() => r'2f68c07ac6cd5c74ec8be3bd2df91f4db673b79e'; +String _$mapServiceHash() => r'7b26bcd231ed5728ac51fe015dddbf8f91491abb'; /// See also [mapService]. @ProviderFor(mapService) @@ -19,6 +19,8 @@ final mapServiceProvider = AutoDisposeProvider.internal( allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef MapServiceRef = AutoDisposeProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/map/map_state.provider.g.dart b/mobile/lib/providers/map/map_state.provider.g.dart index 23a570d1c8..85a237099c 100644 --- a/mobile/lib/providers/map/map_state.provider.g.dart +++ b/mobile/lib/providers/map/map_state.provider.g.dart @@ -23,4 +23,4 @@ final mapStateNotifierProvider = typedef _$MapStateNotifier = Notifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/partner.provider.dart b/mobile/lib/providers/partner.provider.dart index 282e779432..f210c7fe3f 100644 --- a/mobile/lib/providers/partner.provider.dart +++ b/mobile/lib/providers/partner.provider.dart @@ -2,16 +2,16 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/providers/album/suggested_shared_users.provider.dart'; import 'package:immich_mobile/services/partner.service.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; -class PartnerSharedWithNotifier extends StateNotifier> { +class PartnerSharedWithNotifier extends StateNotifier> { final PartnerService _partnerService; - late final StreamSubscription> streamSub; + late final StreamSubscription> streamSub; PartnerSharedWithNotifier(this._partnerService) : super([]) { - Function eq = const ListEquality().equals; + Function eq = const ListEquality().equals; _partnerService.getSharedWith().then((partners) { if (!eq(state, partners)) { state = partners; @@ -25,7 +25,7 @@ class PartnerSharedWithNotifier extends StateNotifier> { }); } - Future updatePartner(User partner, {required bool inTimeline}) { + Future updatePartner(UserDto partner, {required bool inTimeline}) { return _partnerService.updatePartner(partner, inTimeline: inTimeline); } @@ -39,18 +39,18 @@ class PartnerSharedWithNotifier extends StateNotifier> { } final partnerSharedWithProvider = - StateNotifierProvider>((ref) { + StateNotifierProvider>((ref) { return PartnerSharedWithNotifier( ref.watch(partnerServiceProvider), ); }); -class PartnerSharedByNotifier extends StateNotifier> { +class PartnerSharedByNotifier extends StateNotifier> { final PartnerService _partnerService; - late final StreamSubscription> streamSub; + late final StreamSubscription> streamSub; PartnerSharedByNotifier(this._partnerService) : super([]) { - Function eq = const ListEquality().equals; + Function eq = const ListEquality().equals; _partnerService.getSharedBy().then((partners) { if (!eq(state, partners)) { state = partners; @@ -74,15 +74,15 @@ class PartnerSharedByNotifier extends StateNotifier> { } final partnerSharedByProvider = - StateNotifierProvider>((ref) { + StateNotifierProvider>((ref) { return PartnerSharedByNotifier(ref.watch(partnerServiceProvider)); }); final partnerAvailableProvider = - FutureProvider.autoDispose>((ref) async { + FutureProvider.autoDispose>((ref) async { final otherUsers = await ref.watch(otherUsersProvider.future); final currentPartners = ref.watch(partnerSharedByProvider); - final available = Set.of(otherUsers); + final available = Set.of(otherUsers); available.removeAll(currentPartners); return available.toList(); }); diff --git a/mobile/lib/providers/search/paginated_search.provider.dart b/mobile/lib/providers/search/paginated_search.provider.dart index 990bd3f74a..bac5c5e77e 100644 --- a/mobile/lib/providers/search/paginated_search.provider.dart +++ b/mobile/lib/providers/search/paginated_search.provider.dart @@ -45,7 +45,7 @@ class PaginatedSearchNotifier extends StateNotifier { @riverpod Future paginatedSearchRenderList( - PaginatedSearchRenderListRef ref, + Ref ref, ) { final result = ref.watch(paginatedSearchProvider); final timelineService = ref.watch(timelineServiceProvider); diff --git a/mobile/lib/providers/search/paginated_search.provider.g.dart b/mobile/lib/providers/search/paginated_search.provider.g.dart index 5682795ea1..650cf130fc 100644 --- a/mobile/lib/providers/search/paginated_search.provider.g.dart +++ b/mobile/lib/providers/search/paginated_search.provider.g.dart @@ -7,7 +7,7 @@ part of 'paginated_search.provider.dart'; // ************************************************************************** String _$paginatedSearchRenderListHash() => - r'9efb98fd73d4e57e1ccd97a902cd459e3f18f749'; + r'22d715ff7864e5a946be38322ce7813616f899c2'; /// See also [paginatedSearchRenderList]. @ProviderFor(paginatedSearchRenderList) @@ -22,6 +22,8 @@ final paginatedSearchRenderListProvider = allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef PaginatedSearchRenderListRef = AutoDisposeFutureProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/search/people.provider.dart b/mobile/lib/providers/search/people.provider.dart index 7c956f0a37..d1370f498f 100644 --- a/mobile/lib/providers/search/people.provider.dart +++ b/mobile/lib/providers/search/people.provider.dart @@ -1,3 +1,4 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/interfaces/person_api.interface.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/services/person.service.dart'; @@ -9,7 +10,7 @@ part 'people.provider.g.dart'; @riverpod Future> getAllPeople( - GetAllPeopleRef ref, + Ref ref, ) async { final PersonService personService = ref.read(personServiceProvider); @@ -19,7 +20,7 @@ Future> getAllPeople( } @riverpod -Future personAssets(PersonAssetsRef ref, String personId) async { +Future personAssets(Ref ref, String personId) async { final PersonService personService = ref.read(personServiceProvider); final assets = await personService.getPersonAssets(personId); @@ -31,7 +32,7 @@ Future personAssets(PersonAssetsRef ref, String personId) async { @riverpod Future updatePersonName( - UpdatePersonNameRef ref, + Ref ref, String personId, String updatedName, ) async { diff --git a/mobile/lib/providers/search/people.provider.g.dart b/mobile/lib/providers/search/people.provider.g.dart index c5ff6287cd..391edd362c 100644 --- a/mobile/lib/providers/search/people.provider.g.dart +++ b/mobile/lib/providers/search/people.provider.g.dart @@ -6,7 +6,7 @@ part of 'people.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$getAllPeopleHash() => r'3417b7e0c211382d4480a415e352139995d57b6d'; +String _$getAllPeopleHash() => r'226947af3b09ce62224916543958dd1d5e2ba651'; /// See also [getAllPeople]. @ProviderFor(getAllPeople) @@ -19,8 +19,10 @@ final getAllPeopleProvider = AutoDisposeFutureProvider>.internal( allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef GetAllPeopleRef = AutoDisposeFutureProviderRef>; -String _$personAssetsHash() => r'3dfecb67a54d07e4208bcb9581b2625acd2e1832'; +String _$personAssetsHash() => r'c1d35ee0e024bd6915e21bc724be4b458a14bc24'; /// Copied from Dart SDK class _SystemHash { @@ -156,6 +158,8 @@ class PersonAssetsProvider extends AutoDisposeFutureProvider { } } +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element mixin PersonAssetsRef on AutoDisposeFutureProviderRef { /// The parameter `personId` of this provider. String get personId; @@ -169,7 +173,7 @@ class _PersonAssetsProviderElement String get personId => (origin as PersonAssetsProvider).personId; } -String _$updatePersonNameHash() => r'7145aaaf6fc38fdafe3a283ebf3d3f4fd0774cd2'; +String _$updatePersonNameHash() => r'45f7693172de522a227406d8198811434cf2bbbc'; /// See also [updatePersonName]. @ProviderFor(updatePersonName) @@ -296,6 +300,8 @@ class UpdatePersonNameProvider extends AutoDisposeFutureProvider { } } +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element mixin UpdatePersonNameRef on AutoDisposeFutureProviderRef { /// The parameter `personId` of this provider. String get personId; @@ -314,4 +320,4 @@ class _UpdatePersonNameProviderElement String get updatedName => (origin as UpdatePersonNameProvider).updatedName; } // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/search/search_filter.provider.dart b/mobile/lib/providers/search/search_filter.provider.dart index 9086fc861f..2a81060522 100644 --- a/mobile/lib/providers/search/search_filter.provider.dart +++ b/mobile/lib/providers/search/search_filter.provider.dart @@ -1,3 +1,4 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/services/search.service.dart'; import 'package:openapi/api.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -6,7 +7,7 @@ part 'search_filter.provider.g.dart'; @riverpod Future> getSearchSuggestions( - GetSearchSuggestionsRef ref, + Ref ref, SearchSuggestionType type, { String? locationCountry, String? locationState, diff --git a/mobile/lib/providers/search/search_filter.provider.g.dart b/mobile/lib/providers/search/search_filter.provider.g.dart index d5cdaa0312..03f88b0332 100644 --- a/mobile/lib/providers/search/search_filter.provider.g.dart +++ b/mobile/lib/providers/search/search_filter.provider.g.dart @@ -7,7 +7,7 @@ part of 'search_filter.provider.dart'; // ************************************************************************** String _$getSearchSuggestionsHash() => - r'bc1e9a1a060868f14e6eb970d2251dbfe39c6866'; + r'bc30a65e8fcb273cbd07bab876baf67bcc794737'; /// Copied from Dart SDK class _SystemHash { @@ -189,6 +189,8 @@ class GetSearchSuggestionsProvider } } +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element mixin GetSearchSuggestionsRef on AutoDisposeFutureProviderRef> { /// The parameter `type` of this provider. SearchSuggestionType get type; @@ -226,4 +228,4 @@ class _GetSearchSuggestionsProviderElement String? get model => (origin as GetSearchSuggestionsProvider).model; } // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/upload_profile_image.provider.dart b/mobile/lib/providers/upload_profile_image.provider.dart index 83f30e5018..10aa645654 100644 --- a/mobile/lib/providers/upload_profile_image.provider.dart +++ b/mobile/lib/providers/upload_profile_image.provider.dart @@ -3,8 +3,8 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; - -import 'package:immich_mobile/services/user.service.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; enum UploadProfileStatus { idle, @@ -72,7 +72,7 @@ class UploadProfileImageState { class UploadProfileImageNotifier extends StateNotifier { - UploadProfileImageNotifier(this._userSErvice) + UploadProfileImageNotifier(this._userService) : super( UploadProfileImageState( profileImagePath: '', @@ -80,18 +80,21 @@ class UploadProfileImageNotifier ), ); - final UserService _userSErvice; + final UserService _userService; Future upload(XFile file) async { state = state.copyWith(status: UploadProfileStatus.loading); - var res = await _userSErvice.uploadProfileImage(file); + var profileImagePath = await _userService.createProfileImage( + file.name, + await file.readAsBytes(), + ); - if (res != null) { + if (profileImagePath != null) { debugPrint("Successfully upload profile image"); state = state.copyWith( status: UploadProfileStatus.success, - profileImagePath: res.profileImagePath, + profileImagePath: profileImagePath, ); return true; } diff --git a/mobile/lib/providers/user.provider.dart b/mobile/lib/providers/user.provider.dart index 0a1bc0275a..fb574fa99a 100644 --- a/mobile/lib/providers/user.provider.dart +++ b/mobile/lib/providers/user.provider.dart @@ -2,13 +2,14 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/timeline.service.dart'; -class CurrentUserProvider extends StateNotifier { +class CurrentUserProvider extends StateNotifier { CurrentUserProvider(this._apiService) : super(null) { state = Store.tryGet(StoreKey.currentUser); streamSub = @@ -16,7 +17,7 @@ class CurrentUserProvider extends StateNotifier { } final ApiService _apiService; - late final StreamSubscription streamSub; + late final StreamSubscription streamSub; refresh() async { try { @@ -25,7 +26,7 @@ class CurrentUserProvider extends StateNotifier { if (user != null) { await Store.put( StoreKey.currentUser, - User.fromUserDto(user, userPreferences), + UserConverter.fromAdminDto(user, userPreferences), ); } } catch (_) {} @@ -39,7 +40,7 @@ class CurrentUserProvider extends StateNotifier { } final currentUserProvider = - StateNotifierProvider((ref) { + StateNotifierProvider((ref) { return CurrentUserProvider( ref.watch(apiServiceProvider), ); diff --git a/mobile/lib/repositories/activity_api.repository.dart b/mobile/lib/repositories/activity_api.repository.dart index 8da3759709..868415caf9 100644 --- a/mobile/lib/repositories/activity_api.repository.dart +++ b/mobile/lib/repositories/activity_api.repository.dart @@ -1,5 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/interfaces/activity_api.interface.dart'; import 'package:immich_mobile/models/activities/activity.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; @@ -60,7 +60,7 @@ class ActivityApiRepository extends ApiRepository type: dto.type == ReactionType.comment ? ActivityType.comment : ActivityType.like, - user: User.fromSimpleUserDto(dto.user), + user: UserConverter.fromSimpleUserDto(dto.user), assetId: dto.assetId, comment: dto.comment, ); diff --git a/mobile/lib/repositories/album.repository.dart b/mobile/lib/repositories/album.repository.dart index 1d2df89579..a6657f7637 100644 --- a/mobile/lib/repositories/album.repository.dart +++ b/mobile/lib/repositories/album.repository.dart @@ -1,9 +1,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart' + as entity; import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/providers/db.provider.dart'; @@ -43,11 +45,11 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository { } if (owner == true) { query = query.owner( - (q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).isarId), + (q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).id), ); } else if (owner == false) { query = query.owner( - (q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).isarId), + (q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).id), ); } if (remote == true) { @@ -100,8 +102,9 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository { Future get(int id) => db.albums.get(id); @override - Future removeUsers(Album album, List users) => - txn(() => album.sharedUsers.update(unlink: users)); + Future removeUsers(Album album, List users) => txn( + () => album.sharedUsers.update(unlink: users.map(entity.User.fromDto)), + ); @override Future addAssets(Album album, List assets) => @@ -121,8 +124,8 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository { } @override - Future addUsers(Album album, List users) => - txn(() => album.sharedUsers.update(link: users)); + Future addUsers(Album album, List users) => + txn(() => album.sharedUsers.update(link: users.map(entity.User.fromDto))); @override Future deleteAllLocal() => @@ -141,11 +144,11 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository { switch (filterMode) { case QuickFilterMode.sharedWithMe: query = query.owner( - (q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).isarId), + (q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).id), ); case QuickFilterMode.myAlbums: query = query.owner( - (q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).isarId), + (q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).id), ); case QuickFilterMode.all: break; diff --git a/mobile/lib/repositories/album_api.repository.dart b/mobile/lib/repositories/album_api.repository.dart index 2438304158..a7bbe452e6 100644 --- a/mobile/lib/repositories/album_api.repository.dart +++ b/mobile/lib/repositories/album_api.repository.dart @@ -2,7 +2,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart' + as entity; +import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/interfaces/album_api.interface.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/repositories/api.repository.dart'; @@ -164,11 +166,12 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository { sortOrder: dto.order == AssetOrder.asc ? SortOrder.asc : SortOrder.desc, ); album.remoteAssetCount = dto.assetCount; - album.owner.value = User.fromSimpleUserDto(dto.owner); + album.owner.value = + entity.User.fromDto(UserConverter.fromSimpleUserDto(dto.owner)); album.remoteThumbnailAssetId = dto.albumThumbnailAssetId; final users = dto.albumUsers - .map((albumUser) => User.fromSimpleUserDto(albumUser.user)); - album.sharedUsers.addAll(users); + .map((albumUser) => UserConverter.fromSimpleUserDto(albumUser.user)); + album.sharedUsers.addAll(users.map(entity.User.fromDto)); final assets = dto.assets.map(Asset.remote).toList(); album.assets.addAll(assets); return album; diff --git a/mobile/lib/repositories/album_media.repository.dart b/mobile/lib/repositories/album_media.repository.dart index f4f31cf14e..f08322e20a 100644 --- a/mobile/lib/repositories/album_media.repository.dart +++ b/mobile/lib/repositories/album_media.repository.dart @@ -3,6 +3,7 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/interfaces/album_media.interface.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:photo_manager/photo_manager.dart' hide AssetType; @@ -86,7 +87,7 @@ class AlbumMediaRepository implements IAlbumMediaRepository { shared: false, activityEnabled: false, ); - album.owner.value = Store.get(StoreKey.currentUser); + album.owner.value = User.fromDto(Store.get(StoreKey.currentUser)); album.localId = assetPathEntity.id; album.isAll = assetPathEntity.isAll; return album; diff --git a/mobile/lib/repositories/asset.repository.dart b/mobile/lib/repositories/asset.repository.dart index ec7fe77dea..c27660c352 100644 --- a/mobile/lib/repositories/asset.repository.dart +++ b/mobile/lib/repositories/asset.repository.dart @@ -6,8 +6,8 @@ import 'package:immich_mobile/entities/android_device_asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/device_asset.entity.dart'; import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/database.repository.dart'; diff --git a/mobile/lib/repositories/asset_media.repository.dart b/mobile/lib/repositories/asset_media.repository.dart index 4f8272b44f..0149a8d6c6 100644 --- a/mobile/lib/repositories/asset_media.repository.dart +++ b/mobile/lib/repositories/asset_media.repository.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/interfaces/asset_media.interface.dart'; import 'package:photo_manager/photo_manager.dart' hide AssetType; @@ -24,7 +24,7 @@ class AssetMediaRepository implements IAssetMediaRepository { final Asset asset = Asset( checksum: "", localId: local.id, - ownerId: Store.get(StoreKey.currentUser).isarId, + ownerId: Store.get(StoreKey.currentUser).id, fileCreatedAt: local.createDateTime, fileModifiedAt: local.modifiedDateTime, updatedAt: local.modifiedDateTime, @@ -39,7 +39,8 @@ class AssetMediaRepository implements IAssetMediaRepository { asset.fileCreatedAt = asset.fileModifiedAt; } if (local.latitude != null) { - asset.exifInfo = ExifInfo(lat: local.latitude, long: local.longitude); + asset.exifInfo = + ExifInfo(latitude: local.latitude, longitude: local.longitude); } asset.local = local; return asset; diff --git a/mobile/lib/repositories/auth.repository.dart b/mobile/lib/repositories/auth.repository.dart index 491db3c8a8..f9e82e1635 100644 --- a/mobile/lib/repositories/auth.repository.dart +++ b/mobile/lib/repositories/auth.repository.dart @@ -5,9 +5,9 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/interfaces/auth.interface.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/providers/db.provider.dart'; diff --git a/mobile/lib/repositories/exif_info.repository.dart b/mobile/lib/repositories/exif_info.repository.dart deleted file mode 100644 index a70b216df1..0000000000 --- a/mobile/lib/repositories/exif_info.repository.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; -import 'package:immich_mobile/interfaces/exif_info.interface.dart'; -import 'package:immich_mobile/providers/db.provider.dart'; -import 'package:immich_mobile/repositories/database.repository.dart'; - -final exifInfoRepositoryProvider = - Provider((ref) => ExifInfoRepository(ref.watch(dbProvider))); - -class ExifInfoRepository extends DatabaseRepository - implements IExifInfoRepository { - ExifInfoRepository(super.db); - - @override - Future delete(int id) => txn(() => db.exifInfos.delete(id)); - - @override - Future get(int id) => db.exifInfos.get(id); - - @override - Future update(ExifInfo exifInfo) async { - await txn(() => db.exifInfos.put(exifInfo)); - return exifInfo; - } - - @override - Future> updateAll(List exifInfos) async { - await txn(() => db.exifInfos.putAll(exifInfos)); - return exifInfos; - } - - @override - Future clearTable() { - return txn(() => db.exifInfos.clear()); - } -} diff --git a/mobile/lib/repositories/folder_api.repository.dart b/mobile/lib/repositories/folder_api.repository.dart new file mode 100644 index 0000000000..bd7b035157 --- /dev/null +++ b/mobile/lib/repositories/folder_api.repository.dart @@ -0,0 +1,43 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/interfaces/folder_api.interface.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/repositories/api.repository.dart'; +import 'package:logging/logging.dart'; +import 'package:openapi/api.dart'; + +final folderApiRepositoryProvider = Provider( + (ref) => FolderApiRepository( + ref.watch(apiServiceProvider).viewApi, + ), +); + +class FolderApiRepository extends ApiRepository + implements IFolderApiRepository { + final ViewApi _api; + final Logger _log = Logger("FolderApiRepository"); + + FolderApiRepository(this._api); + + @override + Future> getAllUniquePaths() async { + try { + final list = await _api.getUniqueOriginalPaths(); + return list ?? []; + } catch (e, stack) { + _log.severe("Failed to fetch unique original links", e, stack); + return []; + } + } + + @override + Future> getAssetsForPath(String? path) async { + try { + final list = await _api.getAssetsByOriginalPath(path ?? '/'); + return list != null ? list.map(Asset.remote).toList() : []; + } catch (e, stack) { + _log.severe("Failed to fetch Assets by original path", e, stack); + return []; + } + } +} diff --git a/mobile/lib/repositories/partner.repository.dart b/mobile/lib/repositories/partner.repository.dart index cae49fee39..5ea10f98c8 100644 --- a/mobile/lib/repositories/partner.repository.dart +++ b/mobile/lib/repositories/partner.repository.dart @@ -1,5 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart' + as entity; import 'package:immich_mobile/interfaces/partner.interface.dart'; import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/database.repository.dart'; @@ -14,34 +16,40 @@ class PartnerRepository extends DatabaseRepository PartnerRepository(super.db); @override - Future> getSharedBy() { - return db.users - .filter() - .isPartnerSharedByEqualTo(true) - .sortById() - .findAll(); + Future> getSharedBy() async { + return (await db.users + .filter() + .isPartnerSharedByEqualTo(true) + .sortById() + .findAll()) + .map((u) => u.toDto()) + .toList(); } @override - Future> getSharedWith() { - return db.users - .filter() - .isPartnerSharedWithEqualTo(true) - .sortById() - .findAll(); + Future> getSharedWith() async { + return (await db.users + .filter() + .isPartnerSharedWithEqualTo(true) + .sortById() + .findAll()) + .map((u) => u.toDto()) + .toList(); } @override - Stream> watchSharedBy() { - return db.users.filter().isPartnerSharedByEqualTo(true).sortById().watch(); + Stream> watchSharedBy() { + return (db.users.filter().isPartnerSharedByEqualTo(true).sortById().watch()) + .map((users) => users.map((u) => u.toDto()).toList()); } @override - Stream> watchSharedWith() { - return db.users - .filter() - .isPartnerSharedWithEqualTo(true) - .sortById() - .watch(); + Stream> watchSharedWith() { + return (db.users + .filter() + .isPartnerSharedWithEqualTo(true) + .sortById() + .watch()) + .map((users) => users.map((u) => u.toDto()).toList()); } } diff --git a/mobile/lib/repositories/partner_api.repository.dart b/mobile/lib/repositories/partner_api.repository.dart index 1ae16d9d52..367e2a58d7 100644 --- a/mobile/lib/repositories/partner_api.repository.dart +++ b/mobile/lib/repositories/partner_api.repository.dart @@ -1,5 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/interfaces/partner_api.interface.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/repositories/api.repository.dart'; @@ -18,7 +19,7 @@ class PartnerApiRepository extends ApiRepository PartnerApiRepository(this._api); @override - Future> getAll(Direction direction) async { + Future> getAll(Direction direction) async { final response = await checkNull( _api.getPartners( direction == Direction.sharedByMe @@ -26,26 +27,26 @@ class PartnerApiRepository extends ApiRepository : PartnerDirection.with_, ), ); - return response.map(User.fromPartnerDto).toList(); + return response.map(UserConverter.fromPartnerDto).toList(); } @override - Future create(String id) async { + Future create(String id) async { final dto = await checkNull(_api.createPartner(id)); - return User.fromPartnerDto(dto); + return UserConverter.fromPartnerDto(dto); } @override Future delete(String id) => _api.removePartner(id); @override - Future update(String id, {required bool inTimeline}) async { + Future update(String id, {required bool inTimeline}) async { final dto = await checkNull( _api.updatePartner( id, UpdatePartnerDto(inTimeline: inTimeline), ), ); - return User.fromPartnerDto(dto); + return UserConverter.fromPartnerDto(dto); } } diff --git a/mobile/lib/repositories/timeline.repository.dart b/mobile/lib/repositories/timeline.repository.dart index 1b9ee8ad37..1b0471059f 100644 --- a/mobile/lib/repositories/timeline.repository.dart +++ b/mobile/lib/repositories/timeline.repository.dart @@ -2,7 +2,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/interfaces/timeline.interface.dart'; import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/database.repository.dart'; diff --git a/mobile/lib/repositories/user.repository.dart b/mobile/lib/repositories/user.repository.dart deleted file mode 100644 index ea67b30e0d..0000000000 --- a/mobile/lib/repositories/user.repository.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; -import 'package:immich_mobile/interfaces/user.interface.dart'; -import 'package:immich_mobile/providers/db.provider.dart'; -import 'package:immich_mobile/repositories/database.repository.dart'; -import 'package:isar/isar.dart'; - -final userRepositoryProvider = - Provider((ref) => UserRepository(ref.watch(dbProvider))); - -class UserRepository extends DatabaseRepository implements IUserRepository { - UserRepository(super.db); - - @override - Future> getByIds(List ids) async => - (await db.users.getAllById(ids)).nonNulls.toList(); - - @override - Future get(String id) => db.users.getById(id); - - @override - Future> getAll({bool self = true, UserSort? sortBy}) { - final baseQuery = db.users.where(); - final int userId = Store.get(StoreKey.currentUser).isarId; - final QueryBuilder afterWhere = - self ? baseQuery.noOp() : baseQuery.isarIdNotEqualTo(userId); - final QueryBuilder query = switch (sortBy) { - null => afterWhere.noOp(), - UserSort.id => afterWhere.sortById(), - }; - return query.findAll(); - } - - @override - Future update(User user) async { - await txn(() => db.users.put(user)); - return user; - } - - @override - Future me() => Future.value(Store.get(StoreKey.currentUser)); - - @override - Future deleteById(List ids) => txn(() => db.users.deleteAll(ids)); - - @override - Future> upsertAll(List users) async { - await txn(() => db.users.putAll(users)); - return users; - } - - @override - Future> getAllAccessible() => db.users - .filter() - .isPartnerSharedWithEqualTo(true) - .or() - .isarIdEqualTo(Store.get(StoreKey.currentUser).isarId) - .findAll(); - - @override - Future getByDbId(int id) async { - return await db.users.get(id); - } - - @override - Future clearTable() async { - await txn(() async { - await db.users.clear(); - }); - } -} diff --git a/mobile/lib/repositories/user_api.repository.dart b/mobile/lib/repositories/user_api.repository.dart deleted file mode 100644 index 9641c4e0e6..0000000000 --- a/mobile/lib/repositories/user_api.repository.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'dart:typed_data'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:http/http.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; -import 'package:immich_mobile/interfaces/user_api.interface.dart'; -import 'package:immich_mobile/providers/api.provider.dart'; -import 'package:immich_mobile/repositories/api.repository.dart'; -import 'package:openapi/api.dart'; - -final userApiRepositoryProvider = Provider( - (ref) => UserApiRepository( - ref.watch(apiServiceProvider).usersApi, - ), -); - -class UserApiRepository extends ApiRepository implements IUserApiRepository { - final UsersApi _api; - - UserApiRepository(this._api); - - @override - Future> getAll() async { - final dto = await checkNull(_api.searchUsers()); - return dto.map(User.fromSimpleUserDto).toList(); - } - - @override - Future<({String profileImagePath})> createProfileImage({ - required String name, - required Uint8List data, - }) async { - final response = await checkNull( - _api.createProfileImage( - MultipartFile.fromBytes('file', data, filename: name), - ), - ); - return (profileImagePath: response.profileImagePath); - } -} diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index ae5419b712..d7edc6fd28 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -2,9 +2,10 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/models/folder/recursive_folder.model.dart'; import 'package:immich_mobile/models/memories/memory.model.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; @@ -35,6 +36,7 @@ import 'package:immich_mobile/pages/editing/edit.page.dart'; import 'package:immich_mobile/pages/editing/filter.page.dart'; import 'package:immich_mobile/pages/library/archive.page.dart'; import 'package:immich_mobile/pages/library/favorite.page.dart'; +import 'package:immich_mobile/pages/library/folder/folder.page.dart'; import 'package:immich_mobile/pages/library/library.page.dart'; import 'package:immich_mobile/pages/library/local_albums.page.dart'; import 'package:immich_mobile/pages/library/partner/partner.page.dart'; @@ -207,6 +209,11 @@ class AppRouter extends RootStackRouter { guards: [_authGuard, _duplicateGuard], transitionsBuilder: TransitionsBuilders.slideLeft, ), + CustomRoute( + page: FolderRoute.page, + guards: [_authGuard], + transitionsBuilder: TransitionsBuilders.fadeIn, + ), AutoRoute( page: PartnerDetailRoute.page, guards: [_authGuard, _duplicateGuard], diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 299c8a602f..a78371e05e 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -788,6 +788,53 @@ class FilterImageRouteArgs { } } +/// generated route for +/// [FolderPage] +class FolderRoute extends PageRouteInfo { + FolderRoute({ + Key? key, + RecursiveFolder? folder, + List? children, + }) : super( + FolderRoute.name, + args: FolderRouteArgs( + key: key, + folder: folder, + ), + initialChildren: children, + ); + + static const String name = 'FolderRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = + data.argsAs(orElse: () => const FolderRouteArgs()); + return FolderPage( + key: args.key, + folder: args.folder, + ); + }, + ); +} + +class FolderRouteArgs { + const FolderRouteArgs({ + this.key, + this.folder, + }); + + final Key? key; + + final RecursiveFolder? folder; + + @override + String toString() { + return 'FolderRouteArgs{key: $key, folder: $folder}'; + } +} + /// generated route for /// [GalleryViewerPage] class GalleryViewerRoute extends PageRouteInfo { @@ -1115,7 +1162,7 @@ class NativeVideoViewerRouteArgs { class PartnerDetailRoute extends PageRouteInfo { PartnerDetailRoute({ Key? key, - required User partner, + required UserDto partner, List? children, }) : super( PartnerDetailRoute.name, @@ -1148,7 +1195,7 @@ class PartnerDetailRouteArgs { final Key? key; - final User partner; + final UserDto partner; @override String toString() { diff --git a/mobile/lib/routing/tab_navigation_observer.dart b/mobile/lib/routing/tab_navigation_observer.dart index b6a845a0b3..edbfe6da4c 100644 --- a/mobile/lib/routing/tab_navigation_observer.dart +++ b/mobile/lib/routing/tab_navigation_observer.dart @@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/memory.provider.dart'; @@ -39,7 +39,7 @@ class TabNavigationObserver extends AutoRouterObserver { await Store.put( StoreKey.currentUser, - User.fromUserDto(userResponseDto, userPreferences), + UserConverter.fromAdminDto(userResponseDto, userPreferences), ); ref.read(serverInfoProvider.notifier).getServerVersion(); } catch (e) { diff --git a/mobile/lib/services/album.service.dart b/mobile/lib/services/album.service.dart index 3a44ca7286..d3fe7674d5 100644 --- a/mobile/lib/services/album.service.dart +++ b/mobile/lib/services/album.service.dart @@ -7,11 +7,13 @@ import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart' + as entity; import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/interfaces/album_api.interface.dart'; import 'package:immich_mobile/interfaces/album_media.interface.dart'; @@ -26,12 +28,10 @@ import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/repositories/backup.repository.dart'; import 'package:immich_mobile/services/entity.service.dart'; import 'package:immich_mobile/services/sync.service.dart'; -import 'package:immich_mobile/services/user.service.dart'; import 'package:logging/logging.dart'; final albumServiceProvider = Provider( (ref) => AlbumService( - ref.watch(userServiceProvider), ref.watch(syncServiceProvider), ref.watch(entityServiceProvider), ref.watch(albumRepositoryProvider), @@ -43,7 +43,6 @@ final albumServiceProvider = Provider( ); class AlbumService { - final UserService _userService; final SyncService _syncService; final EntityService _entityService; final IAlbumRepository _albumRepository; @@ -56,7 +55,6 @@ class AlbumService { Completer _remoteCompleter = Completer()..complete(false); AlbumService( - this._userService, this._syncService, this._entityService, this._albumRepository, @@ -169,7 +167,7 @@ class AlbumService { final Stopwatch sw = Stopwatch()..start(); bool changes = false; try { - final users = await _userService.getUsersFromServer(); + final users = await _syncService.getUsersFromServer(); if (users != null) { await _syncService.syncUsersFromServer(users); } @@ -202,12 +200,12 @@ class AlbumService { Future createAlbum( String albumName, Iterable assets, [ - Iterable sharedUsers = const [], + Iterable sharedUsers = const [], ]) async { final Album album = await _albumApiRepository.create( albumName, assetIds: assets.map((asset) => asset.remoteId!), - sharedUserIds: sharedUsers.map((user) => user.id), + sharedUserIds: sharedUsers.map((user) => user.uid), ); await _entityService.fillAlbumWithDatabaseEntities(album); return _albumRepository.create(album); @@ -294,7 +292,7 @@ class AlbumService { Future deleteAlbum(Album album) async { try { - final userId = Store.get(StoreKey.currentUser).isarId; + final userId = Store.get(StoreKey.currentUser).id; if (album.owner.value?.isarId == userId) { await _albumApiRepository.delete(album.remoteId!); } @@ -356,15 +354,15 @@ class AlbumService { Future removeUser( Album album, - User user, + UserDto user, ) async { try { await _albumApiRepository.removeUser( album.remoteId!, - userId: user.id, + userId: user.uid, ); - album.sharedUsers.remove(user); + album.sharedUsers.remove(entity.User.fromDto(user)); await _albumRepository.removeUsers(album, [user]); final a = await _albumRepository.get(album.id); // trigger watcher @@ -388,7 +386,10 @@ class AlbumService { album.sharedUsers.addAll(updatedAlbum.remoteUsers); album.shared = true; - await _albumRepository.addUsers(album, album.sharedUsers.toList()); + await _albumRepository.addUsers( + album, + album.sharedUsers.map((u) => u.toDto()).toList(), + ); await _albumRepository.update(album); return true; diff --git a/mobile/lib/services/api.service.dart b/mobile/lib/services/api.service.dart index b87e10f020..0ef68e1c41 100644 --- a/mobile/lib/services/api.service.dart +++ b/mobile/lib/services/api.service.dart @@ -31,6 +31,7 @@ class ApiService implements Authentication { late DownloadApi downloadApi; late TrashApi trashApi; late StacksApi stacksApi; + late ViewApi viewApi; late MemoriesApi memoriesApi; ApiService() { @@ -64,6 +65,7 @@ class ApiService implements Authentication { downloadApi = DownloadApi(_apiClient); trashApi = TrashApi(_apiClient); stacksApi = StacksApi(_apiClient); + viewApi = ViewApi(_apiClient); memoriesApi = MemoriesApi(_apiClient); } diff --git a/mobile/lib/services/asset.service.dart b/mobile/lib/services/asset.service.dart index a4e77c216d..ff3e908ac3 100644 --- a/mobile/lib/services/asset.service.dart +++ b/mobile/lib/services/asset.service.dart @@ -4,30 +4,33 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; +import 'package:immich_mobile/domain/interfaces/user.interface.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset_api.interface.dart'; import 'package:immich_mobile/interfaces/asset_media.interface.dart'; import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/interfaces/etag.interface.dart'; -import 'package:immich_mobile/interfaces/exif_info.interface.dart'; -import 'package:immich_mobile/interfaces/user.interface.dart'; import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart' + hide userServiceProvider; import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/repositories/backup.repository.dart'; import 'package:immich_mobile/repositories/etag.repository.dart'; -import 'package:immich_mobile/repositories/exif_info.repository.dart'; -import 'package:immich_mobile/repositories/user.repository.dart'; import 'package:immich_mobile/services/album.service.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/backup.service.dart'; import 'package:immich_mobile/services/sync.service.dart'; -import 'package:immich_mobile/services/user.service.dart'; import 'package:logging/logging.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:openapi/api.dart'; @@ -36,15 +39,15 @@ final assetServiceProvider = Provider( (ref) => AssetService( ref.watch(assetApiRepositoryProvider), ref.watch(assetRepositoryProvider), - ref.watch(exifInfoRepositoryProvider), + ref.watch(exifRepositoryProvider), ref.watch(userRepositoryProvider), ref.watch(etagRepositoryProvider), ref.watch(backupAlbumRepositoryProvider), ref.watch(apiServiceProvider), ref.watch(syncServiceProvider), - ref.watch(userServiceProvider), ref.watch(backupServiceProvider), ref.watch(albumServiceProvider), + ref.watch(storeServiceProvider), ref.watch(assetMediaRepositoryProvider), ), ); @@ -58,9 +61,9 @@ class AssetService { final IBackupAlbumRepository _backupRepository; final ApiService _apiService; final SyncService _syncService; - final UserService _userService; final BackupService _backupService; final AlbumService _albumService; + final StoreService _storeService; final IAssetMediaRepository _assetMediaRepository; final log = Logger('AssetService'); @@ -73,9 +76,9 @@ class AssetService { this._backupRepository, this._apiService, this._syncService, - this._userService, this._backupService, this._albumService, + this._storeService, this._assetMediaRepository, ); @@ -83,15 +86,14 @@ class AssetService { /// required. Returns `true` if there were any changes. Future refreshRemoteAssets() async { final syncedUserIds = await _etagRepository.getAllIds(); - final List syncedUsers = syncedUserIds.isEmpty + final List syncedUsers = syncedUserIds.isEmpty ? [] - : await _userRepository.getByIds(syncedUserIds); + : (await _userRepository.getByUserIds(syncedUserIds)).nonNulls.toList(); final Stopwatch sw = Stopwatch()..start(); final bool changes = await _syncService.syncRemoteAssetsToDb( users: syncedUsers, getChangedAssets: _getRemoteAssetChanges, loadAssets: _getRemoteAssets, - refreshUsers: _userService.getUsersFromServer, ); debugPrint("refreshRemoteAssets full took ${sw.elapsedMilliseconds}ms"); return changes; @@ -99,10 +101,10 @@ class AssetService { /// Returns `(null, null)` if changes are invalid -> requires full sync Future<(List? toUpsert, List? toDelete)> - _getRemoteAssetChanges(List users, DateTime since) async { + _getRemoteAssetChanges(List users, DateTime since) async { final dto = AssetDeltaSyncDto( updatedAfter: since, - userIds: users.map((e) => e.id).toList(), + userIds: users.map((e) => e.uid).toList(), ); final changes = await _apiService.syncApi.getDeltaSync(dto); return changes == null || changes.needsFullSync @@ -132,7 +134,7 @@ class AssetService { } /// Returns `null` if the server state did not change, else list of assets - Future?> _getRemoteAssets(User user, DateTime until) async { + Future?> _getRemoteAssets(UserDto user, DateTime until) async { const int chunkSize = 10000; try { final List allAssets = []; @@ -143,7 +145,7 @@ class AssetService { limit: chunkSize, updatedUntil: until, lastId: lastId, - userId: user.id, + userId: user.uid, ); log.fine("Requesting $chunkSize assets from $lastId"); final List? assets = @@ -166,17 +168,18 @@ class AssetService { /// Loads the exif information from the database. If there is none, loads /// the exif info from the server (remote assets only) Future loadExif(Asset a) async { - a.exifInfo ??= await _exifInfoRepository.get(a.id); + a.exifInfo ??= (await _exifInfoRepository.get(a.id)); // fileSize is always filled on the server but not set on client if (a.exifInfo?.fileSize == null) { if (a.isRemote) { final dto = await _apiService.assetsApi.getAssetInfo(a.remoteId!); if (dto != null && dto.exifInfo != null) { - final newExif = Asset.remote(dto).exifInfo!.copyWith(id: a.id); + final newExif = Asset.remote(dto).exifInfo!.copyWith(assetId: a.id); a.exifInfo = newExif; if (newExif != a.exifInfo) { if (a.isInDb) { - _assetRepository.transaction(() => _assetRepository.update(a)); + await _assetRepository + .transaction(() => _assetRepository.update(a)); } else { debugPrint("[loadExif] parameter Asset is not from DB!"); } @@ -257,7 +260,8 @@ class AssetService { for (var element in assets) { element.fileCreatedAt = DateTime.parse(updatedDt); - element.exifInfo?.dateTimeOriginal = DateTime.parse(updatedDt); + element.exifInfo ??= element.exifInfo + ?.copyWith(dateTimeOriginal: DateTime.parse(updatedDt)); } await _syncService.upsertAssetsWithExif(assets); @@ -283,8 +287,10 @@ class AssetService { ); for (var element in assets) { - element.exifInfo?.lat = location.latitude; - element.exifInfo?.long = location.longitude; + element.exifInfo ??= element.exifInfo?.copyWith( + latitude: location.latitude, + longitude: location.longitude, + ); } await _syncService.upsertAssetsWithExif(assets); @@ -310,9 +316,9 @@ class AssetService { ); await refreshRemoteAssets(); - final owner = await _userRepository.me(); + final owner = _storeService.get(StoreKey.currentUser); final remoteAssets = await _assetRepository.getAll( - ownerId: owner.isarId, + ownerId: owner.id, state: AssetState.merged, ); @@ -348,7 +354,7 @@ class AssetService { String newDescription, ) async { final remoteAssetId = asset.remoteId; - final localExifId = asset.exifInfo?.id; + final localExifId = asset.exifInfo?.assetId; // Guard [remoteAssetId] and [localExifId] null if (remoteAssetId == null || localExifId == null) { @@ -366,14 +372,14 @@ class AssetService { var exifInfo = await _exifInfoRepository.get(localExifId); if (exifInfo != null) { - exifInfo.description = description; - await _exifInfoRepository.update(exifInfo); + await _exifInfoRepository + .update(exifInfo.copyWith(description: description)); } } } Future getDescription(Asset asset) async { - final localExifId = asset.exifInfo?.id; + final localExifId = asset.exifInfo?.assetId; // Guard [remoteAssetId] and [localExifId] null if (localExifId == null) { @@ -515,13 +521,13 @@ class AssetService { return _assetRepository.watchAsset(id, fireImmediately: fireImmediately); } - Future> getRecentlyAddedAssets() async { - final me = await _userRepository.me(); - return _assetRepository.getRecentlyAddedAssets(me.isarId); + Future> getRecentlyAddedAssets() { + final me = _storeService.get(StoreKey.currentUser); + return _assetRepository.getRecentlyAddedAssets(me.id); } - Future> getMotionAssets() async { - final me = await _userRepository.me(); - return _assetRepository.getMotionAssets(me.isarId); + Future> getMotionAssets() { + final me = _storeService.get(StoreKey.currentUser); + return _assetRepository.getMotionAssets(me.id); } } diff --git a/mobile/lib/services/background.service.dart b/mobile/lib/services/background.service.dart index 8183282586..d69f282103 100644 --- a/mobile/lib/services/background.service.dart +++ b/mobile/lib/services/background.service.dart @@ -11,10 +11,18 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; +import 'package:immich_mobile/domain/interfaces/user.interface.dart'; +import 'package:immich_mobile/domain/interfaces/user_api.repository.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:immich_mobile/interfaces/backup_album.interface.dart'; +import 'package:immich_mobile/interfaces/partner.interface.dart'; import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; import 'package:immich_mobile/models/backup/current_upload_asset.model.dart'; import 'package:immich_mobile/models/backup/error_upload_asset.model.dart'; @@ -28,13 +36,11 @@ import 'package:immich_mobile/repositories/auth.repository.dart'; import 'package:immich_mobile/repositories/auth_api.repository.dart'; import 'package:immich_mobile/repositories/backup.repository.dart'; import 'package:immich_mobile/repositories/etag.repository.dart'; -import 'package:immich_mobile/repositories/exif_info.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/repositories/network.repository.dart'; +import 'package:immich_mobile/repositories/partner.repository.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart'; import 'package:immich_mobile/repositories/permission.repository.dart'; -import 'package:immich_mobile/repositories/user.repository.dart'; -import 'package:immich_mobile/repositories/user_api.repository.dart'; import 'package:immich_mobile/services/album.service.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; @@ -45,13 +51,12 @@ import 'package:immich_mobile/services/hash.service.dart'; import 'package:immich_mobile/services/localization.service.dart'; import 'package:immich_mobile/services/network.service.dart'; import 'package:immich_mobile/services/sync.service.dart'; -import 'package:immich_mobile/services/user.service.dart'; import 'package:immich_mobile/utils/backup_progress.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/diff.dart'; import 'package:immich_mobile/utils/http_ssl_cert_override.dart'; import 'package:network_info_plus/network_info_plus.dart'; -import 'package:path_provider_ios/path_provider_ios.dart'; +import 'package:path_provider_foundation/path_provider_foundation.dart'; import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; final backgroundServiceProvider = Provider( @@ -321,7 +326,7 @@ class BackgroundService { // NOTE: I'm not sure this is strictly necessary anymore, but // out of an abundance of caution, we will keep it in until someone // can say for sure - PathProviderIOS.registerWith(); + PathProviderFoundation.registerWith(); } switch (call.method) { case "backgroundProcessing": @@ -379,13 +384,13 @@ class BackgroundService { AlbumRepository albumRepository = AlbumRepository(db); AssetRepository assetRepository = AssetRepository(db); BackupAlbumRepository backupRepository = BackupAlbumRepository(db); - ExifInfoRepository exifInfoRepository = ExifInfoRepository(db); + IExifInfoRepository exifInfoRepository = IsarExifRepository(db); ETagRepository eTagRepository = ETagRepository(db); AlbumMediaRepository albumMediaRepository = AlbumMediaRepository(); FileMediaRepository fileMediaRepository = FileMediaRepository(); AssetMediaRepository assetMediaRepository = AssetMediaRepository(); - UserRepository userRepository = UserRepository(db); - UserApiRepository userApiRepository = + IUserRepository userRepository = IsarUserRepository(db); + IUserApiRepository userApiRepository = UserApiRepository(apiService.usersApi); AlbumApiRepository albumApiRepository = AlbumApiRepository(apiService.albumsApi); @@ -395,6 +400,7 @@ class BackgroundService { HashService(assetRepository, this, albumMediaRepository); EntityService entityService = EntityService(assetRepository, userRepository); + IPartnerRepository partnerRepository = PartnerRepository(db); SyncService syncSerive = SyncService( hashService, entityService, @@ -403,16 +409,14 @@ class BackgroundService { albumRepository, assetRepository, exifInfoRepository, + partnerRepository, userRepository, + StoreService.I, eTagRepository, - ); - UserService userService = UserService( partnerApiRepository, userApiRepository, - userRepository, ); AlbumService albumService = AlbumService( - userService, syncSerive, entityService, albumRepository, diff --git a/mobile/lib/services/backup_verification.service.dart b/mobile/lib/services/backup_verification.service.dart index 0d47d1a111..e4d5ab4afd 100644 --- a/mobile/lib/services/backup_verification.service.dart +++ b/mobile/lib/services/backup_verification.service.dart @@ -5,15 +5,16 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; -import 'package:immich_mobile/interfaces/exif_info.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart'; +import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart'; -import 'package:immich_mobile/repositories/exif_info.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; @@ -33,7 +34,7 @@ class BackupVerificationService { /// Returns at most [limit] assets that were backed up without exif Future> findWronglyBackedUpAssets({int limit = 100}) async { - final owner = Store.get(StoreKey.currentUser).isarId; + final owner = Store.get(StoreKey.currentUser).id; final List onlyLocal = await _assetRepository.getAll( ownerId: owner, state: AssetState.local, @@ -149,11 +150,11 @@ class BackupVerificationService { ) async { if (remote.checksum == local.checksum) return false; ExifInfo? exif = remote.exifInfo; - if (exif != null && exif.lat != null) return false; + if (exif != null && exif.latitude != null) return false; if (exif == null || exif.fileSize == null) { final dto = await apiService.assetsApi.getAssetInfo(remote.remoteId!); if (dto != null && dto.exifInfo != null) { - exif = ExifInfo.fromDto(dto.exifInfo!); + exif = ExifDtoConverter.fromDto(dto.exifInfo!); } } final file = await local.local!.originFile; @@ -162,7 +163,7 @@ class BackupVerificationService { if (exif.fileSize! == origSize || exif.fileSize! != origSize) { final latLng = await local.local!.latlngAsync(); - if (exif.lat == null && + if (exif.latitude == null && latLng.latitude != null && (remote.fileCreatedAt.isAtSameMomentAs(local.fileCreatedAt) || remote.fileModifiedAt.isAtSameMomentAs(local.fileModifiedAt) || @@ -215,6 +216,6 @@ final backupVerificationServiceProvider = Provider( (ref) => BackupVerificationService( ref.watch(fileMediaRepositoryProvider), ref.watch(assetRepositoryProvider), - ref.watch(exifInfoRepositoryProvider), + ref.watch(exifRepositoryProvider), ), ); diff --git a/mobile/lib/services/entity.service.dart b/mobile/lib/services/entity.service.dart index ddbe77f8c9..9e61366b94 100644 --- a/mobile/lib/services/entity.service.dart +++ b/mobile/lib/services/entity.service.dart @@ -1,9 +1,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/interfaces/user.interface.dart'; import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; -import 'package:immich_mobile/interfaces/user.interface.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart'; -import 'package:immich_mobile/repositories/user.repository.dart'; class EntityService { final IAssetRepository _assetRepository; @@ -17,7 +18,8 @@ class EntityService { final ownerId = album.ownerId; if (ownerId != null) { // replace owner with user from database - album.owner.value = await _userRepository.get(ownerId); + final user = await _userRepository.getByUserId(ownerId); + album.owner.value = user == null ? null : User.fromDto(user); } final thumbnailAssetId = album.remoteThumbnailAssetId ?? album.thumbnail.value?.remoteId; @@ -29,9 +31,9 @@ class EntityService { if (album.remoteUsers.isNotEmpty) { // replace all users with users from database final users = await _userRepository - .getByIds(album.remoteUsers.map((user) => user.id).toList()); + .getByUserIds(album.remoteUsers.map((user) => user.id).toList()); album.sharedUsers.clear(); - album.sharedUsers.addAll(users); + album.sharedUsers.addAll(users.nonNulls.map(User.fromDto)); album.shared = true; } if (album.remoteAssets.isNotEmpty) { diff --git a/mobile/lib/services/exif.service.dart b/mobile/lib/services/exif.service.dart index 2ce2b1ffa5..973f04303e 100644 --- a/mobile/lib/services/exif.service.dart +++ b/mobile/lib/services/exif.service.dart @@ -1,16 +1,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/interfaces/exif_info.interface.dart'; -import 'package:immich_mobile/repositories/exif_info.repository.dart'; +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; +import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; final exifServiceProvider = - Provider((ref) => ExifService(ref.watch(exifInfoRepositoryProvider))); + Provider((ref) => ExifService(ref.watch(exifRepositoryProvider))); class ExifService { final IExifInfoRepository _exifInfoRepository; - ExifService(this._exifInfoRepository); + const ExifService(this._exifInfoRepository); Future clearTable() { - return _exifInfoRepository.clearTable(); + return _exifInfoRepository.deleteAll(); } } diff --git a/mobile/lib/services/folder.service.dart b/mobile/lib/services/folder.service.dart new file mode 100644 index 0000000000..5b97b475b2 --- /dev/null +++ b/mobile/lib/services/folder.service.dart @@ -0,0 +1,132 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/models/folder/recursive_folder.model.dart'; +import 'package:immich_mobile/models/folder/root_folder.model.dart'; +import 'package:immich_mobile/repositories/folder_api.repository.dart'; +import 'package:logging/logging.dart'; + +final folderServiceProvider = Provider( + (ref) => FolderService(ref.watch(folderApiRepositoryProvider)), +); + +class FolderService { + final FolderApiRepository _folderApiRepository; + final Logger _log = Logger("FolderService"); + + FolderService(this._folderApiRepository); + + Future getFolderStructure(SortOrder order) async { + final paths = await _folderApiRepository.getAllUniquePaths(); + + // Create folder structure + Map> folderMap = {}; + + for (String fullPath in paths) { + if (fullPath == '/') continue; + + // Ensure the path starts with a slash + if (!fullPath.startsWith('/')) { + fullPath = '/$fullPath'; + } + + List segments = fullPath.split('/') + ..removeWhere((s) => s.isEmpty); + + String currentPath = ''; + + for (int i = 0; i < segments.length; i++) { + String parentPath = currentPath.isEmpty ? '_root_' : currentPath; + currentPath = + i == 0 ? '/${segments[i]}' : '$currentPath/${segments[i]}'; + + if (!folderMap.containsKey(parentPath)) { + folderMap[parentPath] = []; + } + + if (!folderMap[parentPath]!.any((f) => f.name == segments[i])) { + folderMap[parentPath]!.add( + RecursiveFolder( + path: parentPath == '_root_' ? '' : parentPath, + name: segments[i], + subfolders: [], + ), + ); + // Sort folders based on order parameter + folderMap[parentPath]!.sort( + (a, b) => order == SortOrder.desc + ? b.name.compareTo(a.name) + : a.name.compareTo(b.name), + ); + } + } + } + + void attachSubfolders(RecursiveFolder folder) { + String fullPath = folder.path.isEmpty + ? '/${folder.name}' + : '${folder.path}/${folder.name}'; + + if (folderMap.containsKey(fullPath)) { + folder.subfolders.addAll(folderMap[fullPath]!); + // Sort subfolders based on order parameter + folder.subfolders.sort( + (a, b) => order == SortOrder.desc + ? b.name.compareTo(a.name) + : a.name.compareTo(b.name), + ); + for (var subfolder in folder.subfolders) { + attachSubfolders(subfolder); + } + } + } + + List rootSubfolders = folderMap['_root_'] ?? []; + // Sort root subfolders based on order parameter + rootSubfolders.sort( + (a, b) => order == SortOrder.desc + ? b.name.compareTo(a.name) + : a.name.compareTo(b.name), + ); + + for (var folder in rootSubfolders) { + attachSubfolders(folder); + } + + return RootFolder( + subfolders: rootSubfolders, + path: '/', + ); + } + + Future> getFolderAssets( + RootFolder folder, + SortOrder order, + ) async { + try { + if (folder is RecursiveFolder) { + String fullPath = + folder.path.isEmpty ? folder.name : '${folder.path}/${folder.name}'; + fullPath = fullPath[0] == '/' ? fullPath.substring(1) : fullPath; + var result = await _folderApiRepository.getAssetsForPath(fullPath); + + if (order == SortOrder.desc) { + result.sort((a, b) => b.fileCreatedAt.compareTo(a.fileCreatedAt)); + } else { + result.sort((a, b) => a.fileCreatedAt.compareTo(b.fileCreatedAt)); + } + + return result; + } + final result = await _folderApiRepository.getAssetsForPath('/'); + return result; + } catch (e, stack) { + _log.severe( + "Failed to fetch assets for folder ${folder is RecursiveFolder ? folder.name : "root"}", + e, + stack, + ); + return []; + } + } +} diff --git a/mobile/lib/services/oauth.service.dart b/mobile/lib/services/oauth.service.dart index 30e6448d7f..ddd97522f8 100644 --- a/mobile/lib/services/oauth.service.dart +++ b/mobile/lib/services/oauth.service.dart @@ -1,7 +1,7 @@ +import 'package:flutter_web_auth_2/flutter_web_auth_2.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; -import 'package:flutter_web_auth/flutter_web_auth.dart'; // Redirect URL = app.immich:///oauth-callback @@ -32,7 +32,7 @@ class OAuthService { } Future oAuthLogin(String oauthUrl) async { - String result = await FlutterWebAuth.authenticate( + String result = await FlutterWebAuth2.authenticate( url: oauthUrl, callbackUrlScheme: callbackUrlScheme, ); diff --git a/mobile/lib/services/partner.service.dart b/mobile/lib/services/partner.service.dart index 6bd429b51d..cc3631a0b1 100644 --- a/mobile/lib/services/partner.service.dart +++ b/mobile/lib/services/partner.service.dart @@ -1,11 +1,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/interfaces/user.interface.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/interfaces/partner.interface.dart'; import 'package:immich_mobile/interfaces/partner_api.interface.dart'; -import 'package:immich_mobile/interfaces/user.interface.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/repositories/partner.repository.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart'; -import 'package:immich_mobile/repositories/user.repository.dart'; import 'package:logging/logging.dart'; final partnerServiceProvider = Provider( @@ -28,57 +28,58 @@ class PartnerService { this._partnerRepository, ); - Future> getSharedWith() async { + Future> getSharedWith() async { return _partnerRepository.getSharedWith(); } - Future> getSharedBy() async { + Future> getSharedBy() async { return _partnerRepository.getSharedBy(); } - Stream> watchSharedWith() { + Stream> watchSharedWith() { return _partnerRepository.watchSharedWith(); } - Stream> watchSharedBy() { + Stream> watchSharedBy() { return _partnerRepository.watchSharedBy(); } - Future removePartner(User partner) async { + Future removePartner(UserDto partner) async { try { - await _partnerApiRepository.delete(partner.id); - partner.isPartnerSharedBy = false; - await _userRepository.update(partner); + await _partnerApiRepository.delete(partner.uid); + await _userRepository.update(partner.copyWith(isPartnerSharedBy: false)); } catch (e) { - _log.warning("Failed to remove partner ${partner.id}", e); + _log.warning("Failed to remove partner ${partner.uid}", e); return false; } return true; } - Future addPartner(User partner) async { + Future addPartner(UserDto partner) async { try { - await _partnerApiRepository.create(partner.id); - partner.isPartnerSharedBy = true; - await _userRepository.update(partner); + await _partnerApiRepository.create(partner.uid); + await _userRepository.update(partner.copyWith(isPartnerSharedBy: true)); return true; } catch (e) { - _log.warning("Failed to add partner ${partner.id}", e); + _log.warning("Failed to add partner ${partner.uid}", e); } return false; } - Future updatePartner(User partner, {required bool inTimeline}) async { + Future updatePartner( + UserDto partner, { + required bool inTimeline, + }) async { try { final dto = await _partnerApiRepository.update( - partner.id, + partner.uid, inTimeline: inTimeline, ); - partner.inTimeline = dto.inTimeline; - await _userRepository.update(partner); + await _userRepository + .update(partner.copyWith(inTimeline: dto.inTimeline)); return true; } catch (e) { - _log.warning("Failed to update partner ${partner.id}", e); + _log.warning("Failed to update partner ${partner.uid}", e); } return false; } diff --git a/mobile/lib/services/person.service.dart b/mobile/lib/services/person.service.dart index 5b325acdc5..a8289ac37d 100644 --- a/mobile/lib/services/person.service.dart +++ b/mobile/lib/services/person.service.dart @@ -1,3 +1,4 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset_api.interface.dart'; @@ -11,7 +12,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'person.service.g.dart'; @riverpod -PersonService personService(PersonServiceRef ref) => PersonService( +PersonService personService(Ref ref) => PersonService( ref.watch(personApiRepositoryProvider), ref.watch(assetApiRepositoryProvider), ref.read(assetRepositoryProvider), diff --git a/mobile/lib/services/person.service.g.dart b/mobile/lib/services/person.service.g.dart index 9a24069fbf..8c2d46b3bd 100644 --- a/mobile/lib/services/person.service.g.dart +++ b/mobile/lib/services/person.service.g.dart @@ -6,7 +6,7 @@ part of 'person.service.dart'; // RiverpodGenerator // ************************************************************************** -String _$personServiceHash() => r'32f28cb5a3de0553c17447e33a0efde7409a43ed'; +String _$personServiceHash() => r'10883bccc6c402205e6785cf9ee6cd7142cd0983'; /// See also [personService]. @ProviderFor(personService) @@ -20,6 +20,8 @@ final personServiceProvider = AutoDisposeProvider.internal( allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef PersonServiceRef = AutoDisposeProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/services/sync.service.dart b/mobile/lib/services/sync.service.dart index d01a49a38d..a598941f81 100644 --- a/mobile/lib/services/sync.service.dart +++ b/mobile/lib/services/sync.service.dart @@ -2,25 +2,33 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; +import 'package:immich_mobile/domain/interfaces/user.interface.dart'; +import 'package:immich_mobile/domain/interfaces/user_api.repository.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/extensions/collection_extensions.dart'; import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/interfaces/album_api.interface.dart'; import 'package:immich_mobile/interfaces/album_media.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/etag.interface.dart'; -import 'package:immich_mobile/interfaces/exif_info.interface.dart'; -import 'package:immich_mobile/interfaces/user.interface.dart'; +import 'package:immich_mobile/interfaces/partner.interface.dart'; +import 'package:immich_mobile/interfaces/partner_api.interface.dart'; +import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/repositories/album.repository.dart'; import 'package:immich_mobile/repositories/album_api.repository.dart'; import 'package:immich_mobile/repositories/album_media.repository.dart'; import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/repositories/etag.repository.dart'; -import 'package:immich_mobile/repositories/exif_info.repository.dart'; -import 'package:immich_mobile/repositories/user.repository.dart'; +import 'package:immich_mobile/repositories/partner.repository.dart'; +import 'package:immich_mobile/repositories/partner_api.repository.dart'; import 'package:immich_mobile/services/entity.service.dart'; import 'package:immich_mobile/services/hash.service.dart'; import 'package:immich_mobile/utils/async_mutex.dart'; @@ -36,9 +44,13 @@ final syncServiceProvider = Provider( ref.watch(albumApiRepositoryProvider), ref.watch(albumRepositoryProvider), ref.watch(assetRepositoryProvider), - ref.watch(exifInfoRepositoryProvider), + ref.watch(exifRepositoryProvider), + ref.watch(partnerRepositoryProvider), ref.watch(userRepositoryProvider), + ref.watch(storeServiceProvider), ref.watch(etagRepositoryProvider), + ref.watch(partnerApiRepositoryProvider), + ref.watch(userApiRepositoryProvider), ), ); @@ -51,7 +63,11 @@ class SyncService { final IAssetRepository _assetRepository; final IExifInfoRepository _exifInfoRepository; final IUserRepository _userRepository; + final IPartnerRepository _partnerRepository; + final StoreService _storeService; final IETagRepository _eTagRepository; + final IPartnerApiRepository _partnerApiRepository; + final IUserApiRepository _userApiRepository; final AsyncMutex _lock = AsyncMutex(); final Logger _log = Logger('SyncService'); @@ -63,33 +79,36 @@ class SyncService { this._albumRepository, this._assetRepository, this._exifInfoRepository, + this._partnerRepository, this._userRepository, + this._storeService, this._eTagRepository, + this._partnerApiRepository, + this._userApiRepository, ); // public methods: /// Syncs users from the server to the local database /// Returns `true`if there were any changes - Future syncUsersFromServer(List users) => + Future syncUsersFromServer(List users) => _lock.run(() => _syncUsersFromServer(users)); /// Syncs remote assets owned by the logged-in user to the DB /// Returns `true` if there were any changes Future syncRemoteAssetsToDb({ - required List users, + required List users, required Future<(List? toUpsert, List? toDelete)> Function( - List users, + List users, DateTime since, ) getChangedAssets, - required FutureOr?> Function(User user, DateTime until) + required FutureOr?> Function(UserDto user, DateTime until) loadAssets, - required FutureOr?> Function() refreshUsers, }) => _lock.run( () async => await _syncRemoteAssetChanges(users, getChangedAssets) ?? - await _syncRemoteAssetsFull(refreshUsers, loadAssets), + await _syncRemoteAssetsFull(getUsersFromServer, loadAssets), ); /// Syncs remote albums to the database @@ -134,16 +153,16 @@ class SyncService { /// Syncs users from the server to the local database /// Returns `true`if there were any changes - Future _syncUsersFromServer(List users) async { - users.sortBy((u) => u.id); - final dbUsers = await _userRepository.getAll(sortBy: UserSort.id); + Future _syncUsersFromServer(List users) async { + users.sortBy((u) => u.uid); + final dbUsers = await _userRepository.getAll(sortBy: SortUserBy.id); final List toDelete = []; - final List toUpsert = []; + final List toUpsert = []; final changes = diffSortedListsSync( users, dbUsers, - compare: (User a, User b) => a.id.compareTo(b.id), - both: (User a, User b) { + compare: (UserDto a, UserDto b) => a.uid.compareTo(b.uid), + both: (UserDto a, UserDto b) { if (!a.updatedAt.isAtSameMomentAs(b.updatedAt) || a.isPartnerSharedBy != b.isPartnerSharedBy || a.isPartnerSharedWith != b.isPartnerSharedWith || @@ -153,13 +172,13 @@ class SyncService { } return false; }, - onlyFirst: (User a) => toUpsert.add(a), - onlySecond: (User b) => toDelete.add(b.isarId), + onlyFirst: (UserDto a) => toUpsert.add(a), + onlySecond: (UserDto b) => toDelete.add(b.id), ); if (changes) { await _userRepository.transaction(() async { - await _userRepository.deleteById(toDelete); - await _userRepository.upsertAll(toUpsert); + await _userRepository.delete(toDelete); + await _userRepository.updateAll(toUpsert); }); } return changes; @@ -185,15 +204,15 @@ class SyncService { /// Efficiently syncs assets via changes. Returns `null` when a full sync is required. Future _syncRemoteAssetChanges( - List users, + List users, Future<(List? toUpsert, List? toDelete)> Function( - List users, + List users, DateTime since, ) getChangedAssets, ) async { - final currentUser = await _userRepository.me(); + final currentUser = _storeService.get(StoreKey.currentUser); final DateTime? since = - (await _eTagRepository.get(currentUser.isarId))?.time?.toUtc(); + (await _eTagRepository.get(currentUser.id))?.time?.toUtc(); if (since == null) return null; final DateTime now = DateTime.now(); final (toUpsert, toDelete) = await getChangedAssets(users, since); @@ -240,10 +259,16 @@ class SyncService { }); } + Future> _getAllAccessibleUsers() async { + final sharedWith = (await _partnerRepository.getSharedWith()).toSet(); + sharedWith.add(_storeService.get(StoreKey.currentUser)); + return sharedWith.toList(); + } + /// Syncs assets by loading and comparing all assets from the server. Future _syncRemoteAssetsFull( - FutureOr?> Function() refreshUsers, - FutureOr?> Function(User user, DateTime until) loadAssets, + FutureOr?> Function() refreshUsers, + FutureOr?> Function(UserDto user, DateTime until) loadAssets, ) async { final serverUsers = await refreshUsers(); if (serverUsers == null) { @@ -251,17 +276,17 @@ class SyncService { return false; } await _syncUsersFromServer(serverUsers); - final List users = await _userRepository.getAllAccessible(); + final List users = await _getAllAccessibleUsers(); bool changes = false; - for (User u in users) { + for (UserDto u in users) { changes |= await _syncRemoteAssetsForUser(u, loadAssets); } return changes; } Future _syncRemoteAssetsForUser( - User user, - FutureOr?> Function(User user, DateTime until) loadAssets, + UserDto user, + FutureOr?> Function(UserDto user, DateTime until) loadAssets, ) async { final DateTime now = DateTime.now().toUtc(); final List? remote = await loadAssets(user, now); @@ -269,7 +294,7 @@ class SyncService { return false; } final List inDb = await _assetRepository.getAll( - ownerId: user.isarId, + ownerId: user.id, sortBy: AssetSort.checksum, ); assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!"); @@ -295,13 +320,13 @@ class SyncService { return true; } - Future _updateUserAssetsETag(List users, DateTime time) { - final etags = users.map((u) => ETag(id: u.id, time: time)).toList(); + Future _updateUserAssetsETag(List users, DateTime time) { + final etags = users.map((u) => ETag(id: u.uid, time: time)).toList(); return _eTagRepository.upsertAll(etags); } - Future _clearUserAssetsETag(List users) { - final ids = users.map((u) => u.id).toList(); + Future _clearUserAssetsETag(List users) { + final ids = users.map((u) => u.uid).toList(); return _eTagRepository.deleteByIds(ids); } @@ -373,26 +398,27 @@ class SyncService { ); // update shared users - final List sharedUsers = album.sharedUsers.toList(growable: false); + final List sharedUsers = + album.sharedUsers.map((u) => u.toDto()).toList(growable: false); sharedUsers.sort((a, b) => a.id.compareTo(b.id)); - final List users = dto.remoteUsers.toList() + final List users = dto.remoteUsers.map((u) => u.toDto()).toList() ..sort((a, b) => a.id.compareTo(b.id)); final List userIdsToAdd = []; - final List usersToUnlink = []; + final List usersToUnlink = []; diffSortedListsSync( users, sharedUsers, - compare: (User a, User b) => a.id.compareTo(b.id), + compare: (UserDto a, UserDto b) => a.id.compareTo(b.id), both: (a, b) => false, - onlyFirst: (User a) => userIdsToAdd.add(a.id), - onlySecond: (User a) => usersToUnlink.add(a), + onlyFirst: (UserDto a) => userIdsToAdd.add(a.uid), + onlySecond: (UserDto a) => usersToUnlink.add(a), ); // for shared album: put missing album assets into local DB final (existingInDb, updated) = await _linkWithExistingFromDb(toAdd); await upsertAssetsWithExif(updated); final assetsToLink = existingInDb + updated; - final usersToLink = await _userRepository.getByIds(userIdsToAdd); + final usersToLink = await _userRepository.getByUserIds(userIdsToAdd); album.name = dto.name; album.shared = dto.shared; @@ -416,7 +442,7 @@ class SyncService { try { await _assetRepository.transaction(() async { await _assetRepository.updateAll(toUpdate); - await _albumRepository.addUsers(album, usersToLink); + await _albumRepository.addUsers(album, usersToLink.nonNulls.toList()); await _albumRepository.removeUsers(album, usersToUnlink); await _albumRepository.addAssets(album, assetsToLink); await _albumRepository.removeAssets(album, toUnlink); @@ -429,7 +455,7 @@ class SyncService { } if (album.shared || dto.shared) { - final userId = (await _userRepository.me()).isarId; + final userId = (_storeService.get(StoreKey.currentUser)).id; final foreign = await _assetRepository.getByAlbum(album, notOwnedBy: [userId]); existing.addAll(foreign); @@ -482,8 +508,7 @@ class SyncService { ); } else if (album.shared) { // delete assets in DB unless they belong to this user or are part of some other shared album or belong to a partner - final userIds = - (await _userRepository.getAllAccessible()).map((user) => user.isarId); + final userIds = (await _getAllAccessibleUsers()).map((user) => user.id); final orphanedAssets = await _assetRepository.getByAlbum(album, notOwnedBy: userIds); deleteCandidates.addAll(orphanedAssets); @@ -566,7 +591,7 @@ class SyncService { // general case, e.g. some assets have been deleted or there are excluded albums on iOS final inDb = await _assetRepository.getByAlbum( dbAlbum, - ownerId: (await _userRepository.me()).isarId, + ownerId: (_storeService.get(StoreKey.currentUser)).id, sortBy: AssetSort.checksum, ); @@ -756,7 +781,7 @@ class SyncService { await _assetRepository.transaction(() async { await _assetRepository.updateAll(assets); for (final Asset added in assets) { - added.exifInfo?.id = added.id; + added.exifInfo ??= added.exifInfo?.copyWith(assetId: added.id); } await _exifInfoRepository.updateAll(exifInfos); }); @@ -836,6 +861,61 @@ class SyncService { return false; } } + + Future?> getUsersFromServer() async { + List? users; + try { + users = await _userApiRepository.getAll(); + } catch (e) { + _log.warning("Failed to fetch users", e); + users = null; + } + final List sharedBy = + await _partnerApiRepository.getAll(Direction.sharedByMe); + final List sharedWith = + await _partnerApiRepository.getAll(Direction.sharedWithMe); + + if (users == null) { + _log.warning("Failed to refresh users"); + return null; + } + + users.sortBy((u) => u.uid); + sharedBy.sortBy((u) => u.uid); + sharedWith.sortBy((u) => u.uid); + + final updatedSharedBy = []; + + diffSortedListsSync( + users, + sharedBy, + compare: (UserDto a, UserDto b) => a.uid.compareTo(b.uid), + both: (UserDto a, UserDto b) { + updatedSharedBy.add(a.copyWith(isPartnerSharedBy: true)); + return true; + }, + onlyFirst: (UserDto a) => updatedSharedBy.add(a), + onlySecond: (UserDto b) => updatedSharedBy.add(b), + ); + + final updatedSharedWith = []; + + diffSortedListsSync( + updatedSharedBy, + sharedWith, + compare: (UserDto a, UserDto b) => a.uid.compareTo(b.uid), + both: (UserDto a, UserDto b) { + updatedSharedWith.add( + a.copyWith(inTimeline: b.inTimeline, isPartnerSharedWith: true), + ); + return true; + }, + onlyFirst: (UserDto a) => updatedSharedWith.add(a), + onlySecond: (UserDto b) => updatedSharedWith.add(b), + ); + + return updatedSharedWith; + } } /// Returns a triple(toAdd, toUpdate, toRemove) diff --git a/mobile/lib/services/timeline.service.dart b/mobile/lib/services/timeline.service.dart index db85230662..03042e266b 100644 --- a/mobile/lib/services/timeline.service.dart +++ b/mobile/lib/services/timeline.service.dart @@ -1,41 +1,42 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/interfaces/timeline.interface.dart'; -import 'package:immich_mobile/interfaces/user.interface.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; import 'package:immich_mobile/repositories/timeline.repository.dart'; -import 'package:immich_mobile/repositories/user.repository.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; final timelineServiceProvider = Provider((ref) { return TimelineService( ref.watch(timelineRepositoryProvider), - ref.watch(userRepositoryProvider), ref.watch(appSettingsServiceProvider), + ref.watch(storeServiceProvider), ); }); class TimelineService { final ITimelineRepository _timelineRepository; - final IUserRepository _userRepository; final AppSettingsService _appSettingsService; + final StoreService _storeService; const TimelineService( this._timelineRepository, - this._userRepository, this._appSettingsService, + this._storeService, ); Future> getTimelineUserIds() async { - final me = await _userRepository.me(); - return _timelineRepository.getTimelineUserIds(me.isarId); + final me = _storeService.get(StoreKey.currentUser); + return _timelineRepository.getTimelineUserIds(me.id); } Stream> watchTimelineUserIds() async* { - final me = await _userRepository.me(); - yield* _timelineRepository.watchTimelineUsers(me.isarId); + final me = _storeService.get(StoreKey.currentUser); + yield* _timelineRepository.watchTimelineUsers(me.id); } Stream watchHomeTimeline(int userId) { @@ -50,15 +51,15 @@ class TimelineService { } Stream watchArchiveTimeline() async* { - final user = await _userRepository.me(); + final user = _storeService.get(StoreKey.currentUser); - yield* _timelineRepository.watchArchiveTimeline(user.isarId); + yield* _timelineRepository.watchArchiveTimeline(user.id); } Stream watchFavoriteTimeline() async* { - final user = await _userRepository.me(); + final user = _storeService.get(StoreKey.currentUser); - yield* _timelineRepository.watchFavoriteTimeline(user.isarId); + yield* _timelineRepository.watchFavoriteTimeline(user.id); } Stream watchAlbumTimeline(Album album) async* { @@ -69,9 +70,9 @@ class TimelineService { } Stream watchTrashTimeline() async* { - final user = await _userRepository.me(); + final user = _storeService.get(StoreKey.currentUser); - yield* _timelineRepository.watchTrashTimeline(user.isarId); + yield* _timelineRepository.watchTrashTimeline(user.id); } Stream watchAllVideosTimeline() { @@ -96,9 +97,9 @@ class TimelineService { } Stream watchAssetSelectionTimeline() async* { - final user = await _userRepository.me(); + final user = _storeService.get(StoreKey.currentUser); - yield* _timelineRepository.watchAssetSelectionTimeline(user.isarId); + yield* _timelineRepository.watchAssetSelectionTimeline(user.id); } GroupAssetsBy _getGroupByOption() { diff --git a/mobile/lib/services/trash.service.dart b/mobile/lib/services/trash.service.dart index 8d6cdd8bab..338f063fd3 100644 --- a/mobile/lib/services/trash.service.dart +++ b/mobile/lib/services/trash.service.dart @@ -1,12 +1,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; -import 'package:immich_mobile/interfaces/user.interface.dart'; - import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart'; -import 'package:immich_mobile/repositories/user.repository.dart'; - import 'package:immich_mobile/services/api.service.dart'; import 'package:openapi/api.dart'; @@ -14,16 +13,20 @@ final trashServiceProvider = Provider((ref) { return TrashService( ref.watch(apiServiceProvider), ref.watch(assetRepositoryProvider), - ref.watch(userRepositoryProvider), + ref.watch(storeServiceProvider), ); }); class TrashService { final ApiService _apiService; final IAssetRepository _assetRepository; - final IUserRepository _userRepository; + final StoreService _storeService; - TrashService(this._apiService, this._assetRepository, this._userRepository); + TrashService( + this._apiService, + this._assetRepository, + this._storeService, + ); Future restoreAssets(Iterable assetList) async { final remoteAssets = assetList.where((a) => a.isRemote); @@ -40,11 +43,11 @@ class TrashService { } Future emptyTrash() async { - final user = await _userRepository.me(); + final user = _storeService.get(StoreKey.currentUser); await _apiService.trashApi.emptyTrash(); - final trashedAssets = await _assetRepository.getTrashAssets(user.isarId); + final trashedAssets = await _assetRepository.getTrashAssets(user.id); final ids = trashedAssets.map((e) => e.remoteId!).toList(); await _assetRepository.transaction(() async { @@ -71,11 +74,11 @@ class TrashService { } Future restoreTrash() async { - final user = await _userRepository.me(); + final user = _storeService.get(StoreKey.currentUser); await _apiService.trashApi.restoreTrash(); - final trashedAssets = await _assetRepository.getTrashAssets(user.isarId); + final trashedAssets = await _assetRepository.getTrashAssets(user.id); final updatedAssets = trashedAssets.map((asset) { asset.isTrashed = false; return asset; diff --git a/mobile/lib/services/user.service.dart b/mobile/lib/services/user.service.dart deleted file mode 100644 index 921202ec59..0000000000 --- a/mobile/lib/services/user.service.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; -import 'package:immich_mobile/interfaces/partner_api.interface.dart'; -import 'package:immich_mobile/interfaces/user.interface.dart'; -import 'package:immich_mobile/interfaces/user_api.interface.dart'; -import 'package:immich_mobile/repositories/partner_api.repository.dart'; -import 'package:immich_mobile/repositories/user.repository.dart'; -import 'package:immich_mobile/repositories/user_api.repository.dart'; -import 'package:immich_mobile/utils/diff.dart'; -import 'package:logging/logging.dart'; - -final userServiceProvider = Provider( - (ref) => UserService( - ref.watch(partnerApiRepositoryProvider), - ref.watch(userApiRepositoryProvider), - ref.watch(userRepositoryProvider), - ), -); - -class UserService { - final IPartnerApiRepository _partnerApiRepository; - final IUserApiRepository _userApiRepository; - final IUserRepository _userRepository; - final Logger _log = Logger("UserService"); - - UserService( - this._partnerApiRepository, - this._userApiRepository, - this._userRepository, - ); - - Future> getUsers({bool self = false}) { - return _userRepository.getAll(self: self); - } - - Future<({String profileImagePath})?> uploadProfileImage(XFile image) async { - try { - return await _userApiRepository.createProfileImage( - name: image.name, - data: await image.readAsBytes(), - ); - } catch (e) { - _log.warning("Failed to upload profile image", e); - return null; - } - } - - Future?> getUsersFromServer() async { - List? users; - try { - users = await _userApiRepository.getAll(); - } catch (e) { - _log.warning("Failed to fetch users", e); - users = null; - } - final List sharedBy = - await _partnerApiRepository.getAll(Direction.sharedByMe); - final List sharedWith = - await _partnerApiRepository.getAll(Direction.sharedWithMe); - - if (users == null) { - _log.warning("Failed to refresh users"); - return null; - } - - users.sortBy((u) => u.id); - sharedBy.sortBy((u) => u.id); - sharedWith.sortBy((u) => u.id); - - diffSortedListsSync( - users, - sharedBy, - compare: (User a, User b) => a.id.compareTo(b.id), - both: (User a, User b) => a.isPartnerSharedBy = true, - onlyFirst: (_) {}, - onlySecond: (_) {}, - ); - - diffSortedListsSync( - users, - sharedWith, - compare: (User a, User b) => a.id.compareTo(b.id), - both: (User a, User b) { - a.isPartnerSharedWith = true; - a.inTimeline = b.inTimeline; - return true; - }, - onlyFirst: (_) {}, - onlySecond: (_) {}, - ); - - return users; - } - - Future clearTable() { - return _userRepository.clearTable(); - } -} diff --git a/mobile/lib/theme/theme_data.dart b/mobile/lib/theme/theme_data.dart index de96e12c5d..33dc5dff54 100644 --- a/mobile/lib/theme/theme_data.dart +++ b/mobile/lib/theme/theme_data.dart @@ -24,9 +24,8 @@ ThemeData getThemeData({ hintColor: colorScheme.onSurfaceSecondary, focusColor: colorScheme.primary, scaffoldBackgroundColor: colorScheme.surface, - splashColor: colorScheme.primary.withOpacity(0.1), - highlightColor: colorScheme.primary.withOpacity(0.1), - dialogBackgroundColor: colorScheme.surfaceContainer, + splashColor: colorScheme.primary.withValues(alpha: 0.1), + highlightColor: colorScheme.primary.withValues(alpha: 0.1), bottomSheetTheme: BottomSheetThemeData( backgroundColor: colorScheme.surfaceContainer, ), @@ -163,6 +162,7 @@ ThemeData getThemeData({ ), ), ), + dialogTheme: DialogThemeData(backgroundColor: colorScheme.surfaceContainer), ); } diff --git a/mobile/lib/utils/bootstrap.dart b/mobile/lib/utils/bootstrap.dart index 4a9ce1a5e1..21231becf6 100644 --- a/mobile/lib/utils/bootstrap.dart +++ b/mobile/lib/utils/bootstrap.dart @@ -9,11 +9,11 @@ import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; import 'package:immich_mobile/infrastructure/entities/log.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:isar/isar.dart'; diff --git a/mobile/lib/utils/migration.dart b/mobile/lib/utils/migration.dart index eedf8f40f8..d2f0a2ac9d 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -4,9 +4,9 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:isar/isar.dart'; const int targetVersion = 8; diff --git a/mobile/lib/utils/provider_utils.dart b/mobile/lib/utils/provider_utils.dart index 3eac55089d..bf18d24213 100644 --- a/mobile/lib/utils/provider_utils.dart +++ b/mobile/lib/utils/provider_utils.dart @@ -1,10 +1,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/repositories/activity_api.repository.dart'; import 'package:immich_mobile/repositories/album_api.repository.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart'; import 'package:immich_mobile/repositories/person_api.repository.dart'; -import 'package:immich_mobile/repositories/user_api.repository.dart'; +import 'package:immich_mobile/repositories/timeline.repository.dart'; void invalidateAllApiRepositoryProviders(WidgetRef ref) { ref.invalidate(userApiRepositoryProvider); @@ -13,4 +14,5 @@ void invalidateAllApiRepositoryProviders(WidgetRef ref) { ref.invalidate(albumApiRepositoryProvider); ref.invalidate(personApiRepositoryProvider); ref.invalidate(assetApiRepositoryProvider); + ref.invalidate(timelineRepositoryProvider); } diff --git a/mobile/lib/utils/selection_handlers.dart b/mobile/lib/utils/selection_handlers.dart index 56160a2efc..4a9fcc8c99 100644 --- a/mobile/lib/utils/selection_handlers.dart +++ b/mobile/lib/utils/selection_handlers.dart @@ -2,9 +2,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/asset_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/services/asset.service.dart'; import 'package:immich_mobile/services/share.service.dart'; diff --git a/mobile/lib/widgets/activities/activity_tile.dart b/mobile/lib/widgets/activities/activity_tile.dart index 1c89a39b46..2dd16b73cb 100644 --- a/mobile/lib/widgets/activities/activity_tile.dart +++ b/mobile/lib/widgets/activities/activity_tile.dart @@ -63,7 +63,7 @@ class _ActivityTitle extends StatelessWidget { Widget build(BuildContext context) { final textColor = context.isDarkTheme ? Colors.white : Colors.black; final textStyle = context.textTheme.bodyMedium - ?.copyWith(color: textColor.withOpacity(0.6)); + ?.copyWith(color: textColor.withValues(alpha: 0.6)); return Row( mainAxisAlignment: diff --git a/mobile/lib/widgets/album/album_thumbnail_card.dart b/mobile/lib/widgets/album/album_thumbnail_card.dart index ac62ecee03..ec984d1017 100644 --- a/mobile/lib/widgets/album/album_thumbnail_card.dart +++ b/mobile/lib/widgets/album/album_thumbnail_card.dart @@ -58,7 +58,7 @@ class AlbumThumbnailCard extends StatelessWidget { // Add the owner name to the subtitle String? owner; if (showOwner) { - if (album.ownerId == Store.get(StoreKey.currentUser).id) { + if (album.ownerId == Store.get(StoreKey.currentUser).uid) { owner = 'album_thumbnail_owned'.tr(); } else if (album.ownerName != null) { owner = 'album_thumbnail_shared_by'.tr(args: [album.ownerName!]); diff --git a/mobile/lib/widgets/album/album_viewer_appbar.dart b/mobile/lib/widgets/album/album_viewer_appbar.dart index b058f29e7d..451987316b 100644 --- a/mobile/lib/widgets/album/album_viewer_appbar.dart +++ b/mobile/lib/widgets/album/album_viewer_appbar.dart @@ -58,12 +58,7 @@ class AlbumViewerAppbar extends HookConsumerWidget final bool success = await ref.watch(albumProvider.notifier).deleteAlbum(album); - if (album.shared) { - context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])); - } else { - context - .navigateTo(const TabControllerRoute(children: [LibraryRoute()])); - } + context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])); if (!success) { ImmichToast.show( diff --git a/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart b/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart index c38e61a473..941a2a7ac6 100644 --- a/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart +++ b/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart @@ -332,7 +332,7 @@ class ImmichAssetGridViewState extends ConsumerState { ); } - if (index != -1 && index < widget.renderList.elements.length) { + if (index < widget.renderList.elements.length) { // Not sure why the index is shifted, but it works. :3 _scrollToIndex(index + 1); } else { @@ -542,7 +542,24 @@ class ImmichAssetGridViewState extends ConsumerState { Widget build(BuildContext context) { return PopScope( canPop: !(widget.selectionActive && _selectedAssets.isNotEmpty), - onPopInvokedWithResult: (didPop, _) => !didPop ? _deselectAll() : null, + onPopInvokedWithResult: (didPop, _) { + if (didPop) { + return; + } else { + if (widget.preselectedAssets == null) { + Navigator.of(context).canPop() ? Navigator.of(context).pop() : null; + } + if (_selectedAssets.length != widget.preselectedAssets!.length && + !widget.preselectedAssets!.containsAll(_selectedAssets)) { + { + _deselectAll(); + return; + } + } else { + Navigator.of(context).canPop() ? Navigator.of(context).pop() : null; + } + } + }, child: Stack( children: [ AssetDragRegion( diff --git a/mobile/lib/widgets/asset_grid/thumbnail_image.dart b/mobile/lib/widgets/asset_grid/thumbnail_image.dart index 81932e2b94..d25b7a3e90 100644 --- a/mobile/lib/widgets/asset_grid/thumbnail_image.dart +++ b/mobile/lib/widgets/asset_grid/thumbnail_image.dart @@ -202,12 +202,12 @@ class ThumbnailImage extends ConsumerWidget { bottom: 5, child: Icon( storageIcon(asset), - color: Colors.white.withOpacity(.8), + color: Colors.white.withValues(alpha: .8), size: 16, shadows: [ Shadow( blurRadius: 5.0, - color: Colors.black.withOpacity(0.6), + color: Colors.black.withValues(alpha: 0.6), offset: const Offset(0.0, 0.0), ), ], diff --git a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart index 256141dc7d..1b225f106f 100644 --- a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart +++ b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart @@ -5,25 +5,25 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/pages/editing/edit.page.dart'; import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/album/current_album.provider.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/download.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; -import 'package:immich_mobile/services/stack.service.dart'; -import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; -import 'package:immich_mobile/widgets/asset_viewer/video_controls.dart'; -import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart'; -import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/widgets/common/immich_image.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/stack.service.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart'; +import 'package:immich_mobile/widgets/asset_viewer/video_controls.dart'; +import 'package:immich_mobile/widgets/common/immich_image.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; -import 'package:immich_mobile/pages/editing/edit.page.dart'; class BottomGalleryBar extends ConsumerWidget { final ValueNotifier assetIndex; @@ -49,7 +49,7 @@ class BottomGalleryBar extends ConsumerWidget { if (asset == null) { return const SizedBox(); } - final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.isarId; + final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.id; final showControls = ref.watch(showControlsProvider); final stackId = asset.stackId; diff --git a/mobile/lib/widgets/asset_viewer/description_input.dart b/mobile/lib/widgets/asset_viewer/description_input.dart index 3fdd40130a..844e4744b3 100644 --- a/mobile/lib/widgets/asset_viewer/description_input.dart +++ b/mobile/lib/widgets/asset_viewer/description_input.dart @@ -2,9 +2,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; @@ -81,7 +81,7 @@ class DescriptionInput extends HookConsumerWidget { } return TextField( - enabled: owner?.isarId == asset.ownerId, + enabled: owner?.id == asset.ownerId, focusNode: focusNode, onTap: () => isFocus.value = true, onChanged: (value) { diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/asset_details.dart b/mobile/lib/widgets/asset_viewer/detail_panel/asset_details.dart index a78a309512..59b52344e7 100644 --- a/mobile/lib/widgets/asset_viewer/detail_panel/asset_details.dart +++ b/mobile/lib/widgets/asset_viewer/detail_panel/asset_details.dart @@ -1,12 +1,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; -import 'package:immich_mobile/widgets/asset_viewer/detail_panel/file_info.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/widgets/asset_viewer/detail_panel/camera_info.dart'; +import 'package:immich_mobile/widgets/asset_viewer/detail_panel/file_info.dart'; class AssetDetails extends ConsumerWidget { final Asset asset; diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/asset_location.dart b/mobile/lib/widgets/asset_viewer/detail_panel/asset_location.dart index 364b568d0a..40e09cb5c8 100644 --- a/mobile/lib/widgets/asset_viewer/detail_panel/asset_location.dart +++ b/mobile/lib/widgets/asset_viewer/detail_panel/asset_location.dart @@ -1,12 +1,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/utils/selection_handlers.dart'; import 'package:immich_mobile/widgets/asset_viewer/detail_panel/exif_map.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; class AssetLocation extends HookConsumerWidget { final Asset asset; diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/camera_info.dart b/mobile/lib/widgets/asset_viewer/detail_panel/camera_info.dart index e6720e0255..aec18c6a16 100644 --- a/mobile/lib/widgets/asset_viewer/detail_panel/camera_info.dart +++ b/mobile/lib/widgets/asset_viewer/detail_panel/camera_info.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; class CameraInfo extends StatelessWidget { diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart b/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart index 7878404273..f3f72dfd87 100644 --- a/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart +++ b/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart @@ -1,8 +1,8 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/widgets/map/map_thumbnail.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/people_info.dart b/mobile/lib/widgets/asset_viewer/detail_panel/people_info.dart index f917f03b37..2e868682f8 100644 --- a/mobile/lib/widgets/asset_viewer/detail_panel/people_info.dart +++ b/mobile/lib/widgets/asset_viewer/detail_panel/people_info.dart @@ -44,7 +44,19 @@ class PeopleInfo extends ConsumerWidget { } final curatedPeople = people - ?.map((p) => SearchCuratedContent(id: p.id, label: p.name)) + ?.map( + (p) => SearchCuratedContent( + id: p.id, + label: p.name, + subtitle: p.birthDate != null + ? "exif_bottom_sheet_person_age".tr( + args: [ + _calculateAge(p.birthDate!).toString(), + ], + ) + : null, + ), + ) .toList() ?? []; @@ -99,4 +111,17 @@ class PeopleInfo extends ConsumerWidget { ), ); } + + int _calculateAge(DateTime birthDate) { + DateTime today = DateTime.now(); + int age = today.year - birthDate.year; + + // Check if the birthday has occurred this year + if (today.month < birthDate.month || + (today.month == birthDate.month && today.day < birthDate.day)) { + age--; + } + + return age; + } } diff --git a/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart b/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart index 2cb90da599..dcef6c79d4 100644 --- a/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart +++ b/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart @@ -3,20 +3,22 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/providers/album/current_album.provider.dart'; -import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; -import 'package:immich_mobile/widgets/album/add_to_album_bottom_sheet.dart'; -import 'package:immich_mobile/providers/asset_viewer/download.provider.dart'; -import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; -import 'package:immich_mobile/widgets/asset_viewer/top_control_app_bar.dart'; -import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; -import 'package:immich_mobile/providers/trash.provider.dart'; -import 'package:immich_mobile/widgets/asset_grid/upload_dialog.dart'; -import 'package:immich_mobile/providers/partner.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/providers/album/current_album.provider.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/download.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/scroll_to_date_notifier.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; +import 'package:immich_mobile/providers/partner.provider.dart'; +import 'package:immich_mobile/providers/tab.provider.dart'; +import 'package:immich_mobile/providers/trash.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/album/add_to_album_bottom_sheet.dart'; +import 'package:immich_mobile/widgets/asset_grid/upload_dialog.dart'; +import 'package:immich_mobile/widgets/asset_viewer/top_control_app_bar.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; class GalleryAppBar extends ConsumerWidget { @@ -31,12 +33,12 @@ class GalleryAppBar extends ConsumerWidget { return const SizedBox(); } final album = ref.watch(currentAlbumProvider); - final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.isarId; + final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.id; final showControls = ref.watch(showControlsProvider); final isPartner = ref .watch(partnerSharedWithProvider) - .map((e) => e.isarId) + .map((e) => e.id) .contains(asset.ownerId); toggleFavorite(Asset asset) => @@ -95,18 +97,29 @@ class GalleryAppBar extends ConsumerWidget { ref.read(downloadStateProvider.notifier).downloadAsset(asset, context); } + handleLocateAsset() async { + // Go back to the gallery + await context.maybePop(); + await context + .navigateTo(const TabControllerRoute(children: [PhotosRoute()])); + ref.read(tabProvider.notifier).update((state) => state = TabEnum.home); + // Scroll to the asset's date + scrollToDateNotifierProvider.scrollToDate(asset.fileCreatedAt); + } + return IgnorePointer( ignoring: !showControls, child: AnimatedOpacity( duration: const Duration(milliseconds: 100), opacity: showControls ? 1.0 : 0.0, child: Container( - color: Colors.black.withOpacity(0.4), + color: Colors.black.withValues(alpha: 0.4), child: TopControlAppBar( isOwner: isOwner, isPartner: isPartner, asset: asset, onMoreInfoPressed: showInfo, + onLocatePressed: handleLocateAsset, onFavorite: toggleFavorite, onRestorePressed: () => handleRestore(asset), onUploadPressed: asset.isLocal ? () => handleUpload(asset) : null, diff --git a/mobile/lib/widgets/asset_viewer/top_control_app_bar.dart b/mobile/lib/widgets/asset_viewer/top_control_app_bar.dart index 2bdbb72ec0..ef8f2e687b 100644 --- a/mobile/lib/widgets/asset_viewer/top_control_app_bar.dart +++ b/mobile/lib/widgets/asset_viewer/top_control_app_bar.dart @@ -5,6 +5,7 @@ import 'package:immich_mobile/providers/activity_statistics.provider.dart'; import 'package:immich_mobile/providers/album/current_album.provider.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/tab.provider.dart'; import 'package:immich_mobile/widgets/asset_viewer/motion_photo_button.dart'; class TopControlAppBar extends HookConsumerWidget { @@ -13,6 +14,7 @@ class TopControlAppBar extends HookConsumerWidget { required this.asset, required this.onMoreInfoPressed, required this.onDownloadPressed, + required this.onLocatePressed, required this.onAddToAlbumPressed, required this.onRestorePressed, required this.onFavorite, @@ -26,6 +28,7 @@ class TopControlAppBar extends HookConsumerWidget { final Function onMoreInfoPressed; final VoidCallback? onUploadPressed; final VoidCallback? onDownloadPressed; + final VoidCallback onLocatePressed; final VoidCallback onAddToAlbumPressed; final VoidCallback onRestorePressed; final VoidCallback onActivitiesPressed; @@ -54,6 +57,18 @@ class TopControlAppBar extends HookConsumerWidget { ); } + Widget buildLocateButton() { + return IconButton( + onPressed: () { + onLocatePressed(); + }, + icon: Icon( + Icons.image_search, + color: Colors.grey[200], + ), + ); + } + Widget buildMoreInfoButton() { return IconButton( onPressed: () { @@ -159,6 +174,8 @@ class TopControlAppBar extends HookConsumerWidget { shape: const Border(), actions: [ if (asset.isRemote && isOwner) buildFavoriteButton(a), + if (isOwner && ref.read(tabProvider.notifier).state != TabEnum.home) + buildLocateButton(), if (asset.livePhotoVideoId != null) const MotionPhotoButton(), if (asset.isLocal && !asset.isRemote) buildUploadButton(), if (asset.isRemote && !asset.isLocal && isOwner) buildDownloadButton(), diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart index d51e122954..4d4376b71d 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart @@ -67,8 +67,9 @@ class AppBarProfileInfoBox extends HookConsumerWidget { profileImagePath, ); if (user != null) { - user.profileImagePath = profileImagePath; - await Store.put(StoreKey.currentUser, user); + final updatedUser = + user.copyWith(profileImagePath: profileImagePath); + await Store.put(StoreKey.currentUser, updatedUser); ref.read(currentUserProvider.notifier).refresh(); } } diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart index 8cab0bd72f..9c6f4a62fa 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart @@ -170,7 +170,7 @@ class AppBarServerInfo extends HookConsumerWidget { child: Tooltip( verticalOffset: 0, decoration: BoxDecoration( - color: context.primaryColor.withOpacity(0.9), + color: context.primaryColor.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(10), ), textStyle: TextStyle( diff --git a/mobile/lib/widgets/common/dropdown_search_menu.dart b/mobile/lib/widgets/common/dropdown_search_menu.dart index 2fd5539b01..5b73d649f8 100644 --- a/mobile/lib/widgets/common/dropdown_search_menu.dart +++ b/mobile/lib/widgets/common/dropdown_search_menu.dart @@ -146,7 +146,7 @@ class DropdownSearchMenu extends HookWidget { ? Theme.of(context) .colorScheme .onSurface - .withOpacity(0.12) + .withValues(alpha: 0.12) : null, padding: const EdgeInsets.all(16.0), child: Text( diff --git a/mobile/lib/widgets/common/immich_app_bar.dart b/mobile/lib/widgets/common/immich_app_bar.dart index 7a42606797..7c1e0c1c4e 100644 --- a/mobile/lib/widgets/common/immich_app_bar.dart +++ b/mobile/lib/widgets/common/immich_app_bar.dart @@ -124,7 +124,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { decoration: BoxDecoration( color: badgeBackground, border: Border.all( - color: context.colorScheme.outline.withOpacity(.3), + color: context.colorScheme.outline.withValues(alpha: .3), ), borderRadius: BorderRadius.circular(widgetSize / 2), ), diff --git a/mobile/lib/widgets/common/immich_toast.dart b/mobile/lib/widgets/common/immich_toast.dart index b0f1306aba..7f3207032b 100644 --- a/mobile/lib/widgets/common/immich_toast.dart +++ b/mobile/lib/widgets/common/immich_toast.dart @@ -43,7 +43,7 @@ class ImmichToast { borderRadius: BorderRadius.circular(5.0), color: context.colorScheme.surfaceContainer, border: Border.all( - color: context.colorScheme.outline.withOpacity(.5), + color: context.colorScheme.outline.withValues(alpha: .5), width: 1, ), ), diff --git a/mobile/lib/widgets/common/scaffold_error_body.dart b/mobile/lib/widgets/common/scaffold_error_body.dart index bca2934c23..5011d229e7 100644 --- a/mobile/lib/widgets/common/scaffold_error_body.dart +++ b/mobile/lib/widgets/common/scaffold_error_body.dart @@ -27,7 +27,8 @@ class ScaffoldErrorBody extends StatelessWidget { child: Icon( Icons.error_outline, size: 100, - color: context.themeData.iconTheme.color?.withOpacity(0.5), + color: + context.themeData.iconTheme.color?.withValues(alpha: 0.5), ), ), ), diff --git a/mobile/lib/widgets/common/user_avatar.dart b/mobile/lib/widgets/common/user_avatar.dart index 62491210c9..753a1f5d37 100644 --- a/mobile/lib/widgets/common/user_avatar.dart +++ b/mobile/lib/widgets/common/user_avatar.dart @@ -1,14 +1,14 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/services/api.service.dart'; -Widget userAvatar(BuildContext context, User u, {double? radius}) { +Widget userAvatar(BuildContext context, UserDto u, {double? radius}) { final url = - "${Store.get(StoreKey.serverEndpoint)}/users/${u.id}/profile-image"; + "${Store.get(StoreKey.serverEndpoint)}/users/${u.uid}/profile-image"; final nameFirstLetter = u.name.isNotEmpty ? u.name[0] : ""; return CircleAvatar( radius: radius, diff --git a/mobile/lib/widgets/common/user_circle_avatar.dart b/mobile/lib/widgets/common/user_circle_avatar.dart index 2b7eadf04b..4bc7bfa0f4 100644 --- a/mobile/lib/widgets/common/user_circle_avatar.dart +++ b/mobile/lib/widgets/common/user_circle_avatar.dart @@ -4,15 +4,15 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/widgets/common/transparent_image.dart'; // ignore: must_be_immutable class UserCircleAvatar extends ConsumerWidget { - final User user; + final UserDto user; double radius; double size; @@ -27,13 +27,13 @@ class UserCircleAvatar extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { bool isDarkTheme = context.themeData.brightness == Brightness.dark; final profileImageUrl = - '${Store.get(StoreKey.serverEndpoint)}/users/${user.id}/profile-image?d=${Random().nextInt(1024)}'; + '${Store.get(StoreKey.serverEndpoint)}/users/${user.uid}/profile-image?d=${Random().nextInt(1024)}'; final textIcon = DefaultTextStyle( style: TextStyle( fontWeight: FontWeight.bold, fontSize: 12, - color: isDarkTheme && user.avatarColor == AvatarColorEnum.primary + color: isDarkTheme && user.avatarColor == AvatarColor.primary ? Colors.black : Colors.white, ), @@ -42,7 +42,7 @@ class UserCircleAvatar extends ConsumerWidget { return CircleAvatar( backgroundColor: user.avatarColor.toColor(), radius: radius, - child: user.profileImagePath.isEmpty + child: user.profileImagePath == null ? textIcon : ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(50)), diff --git a/mobile/lib/widgets/map/map_thumbnail.dart b/mobile/lib/widgets/map/map_thumbnail.dart index b856f09787..b225a2edcb 100644 --- a/mobile/lib/widgets/map/map_thumbnail.dart +++ b/mobile/lib/widgets/map/map_thumbnail.dart @@ -41,10 +41,10 @@ class MapThumbnail extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final offsettedCentre = LatLng(centre.latitude + 0.002, centre.longitude); - final controller = useRef(null); + final controller = useRef(null); final position = useValueNotifier?>(null); - Future onMapCreated(MaplibreMapController mapController) async { + Future onMapCreated(MapLibreMapController mapController) async { controller.value = mapController; if (assetMarkerRemoteId != null) { // The iOS impl returns wrong toScreenLocation without the delay @@ -73,7 +73,7 @@ class MapThumbnail extends HookConsumerWidget { alignment: Alignment.center, children: [ style.widgetWhen( - onData: (style) => MaplibreMap( + onData: (style) => MapLibreMap( initialCameraPosition: CameraPosition(target: offsettedCentre, zoom: zoom), styleString: style, diff --git a/mobile/lib/widgets/memories/memory_bottom_info.dart b/mobile/lib/widgets/memories/memory_bottom_info.dart index 84f4cb6c72..6adf1d46b0 100644 --- a/mobile/lib/widgets/memories/memory_bottom_info.dart +++ b/mobile/lib/widgets/memories/memory_bottom_info.dart @@ -48,7 +48,7 @@ class MemoryBottomInfo extends StatelessWidget { .scrollToDate(memory.assets[0].fileCreatedAt); }, shape: const CircleBorder(), - color: Colors.white.withOpacity(0.2), + color: Colors.white.withValues(alpha: 0.2), elevation: 0, child: const Icon( Icons.open_in_new, diff --git a/mobile/lib/widgets/memories/memory_card.dart b/mobile/lib/widgets/memories/memory_card.dart index b63a310b32..abe3586194 100644 --- a/mobile/lib/widgets/memories/memory_card.dart +++ b/mobile/lib/widgets/memories/memory_card.dart @@ -126,7 +126,7 @@ class _BlurredBackdrop extends HookWidget { ), ), child: Container( - color: Colors.black.withOpacity(0.2), + color: Colors.black.withValues(alpha: 0.2), ), ); } else { @@ -147,7 +147,7 @@ class _BlurredBackdrop extends HookWidget { ), ), child: Container( - color: Colors.black.withOpacity(0.2), + color: Colors.black.withValues(alpha: 0.2), ), ), ); diff --git a/mobile/lib/widgets/memories/memory_lane.dart b/mobile/lib/widgets/memories/memory_lane.dart index d5b46dab51..3f97bd1ea4 100644 --- a/mobile/lib/widgets/memories/memory_lane.dart +++ b/mobile/lib/widgets/memories/memory_lane.dart @@ -31,7 +31,7 @@ class MemoryLane extends HookConsumerWidget { elevation: 2, backgroundColor: Colors.black, overlayColor: WidgetStateProperty.all( - Colors.white.withOpacity(0.1), + Colors.white.withValues(alpha: 0.1), ), onTap: (memoryIndex) { ref.read(hapticFeedbackProvider.notifier).heavyImpact(); @@ -84,7 +84,7 @@ class MemoryCard extends ConsumerWidget { children: [ ColorFiltered( colorFilter: ColorFilter.mode( - Colors.black.withOpacity(0.2), + Colors.black.withValues(alpha: 0.2), BlendMode.darken, ), child: Hero( diff --git a/mobile/lib/widgets/search/curated_people_row.dart b/mobile/lib/widgets/search/curated_people_row.dart index 2ec57d6a1a..eece328392 100644 --- a/mobile/lib/widgets/search/curated_people_row.dart +++ b/mobile/lib/widgets/search/curated_people_row.dart @@ -86,12 +86,22 @@ class CuratedPeopleRow extends StatelessWidget { ).tr(), ); } - return Text( - person.label, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - style: context.textTheme.labelLarge, - maxLines: 2, + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + person.label, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: context.textTheme.labelLarge, + maxLines: 2, + ), + if (person.subtitle != null) + Text( + person.subtitle!, + textAlign: TextAlign.center, + ), + ], ); } } diff --git a/mobile/lib/widgets/search/search_filter/people_picker.dart b/mobile/lib/widgets/search/search_filter/people_picker.dart index 04f9538875..2c45e2097c 100644 --- a/mobile/lib/widgets/search/search_filter/people_picker.dart +++ b/mobile/lib/widgets/search/search_filter/people_picker.dart @@ -37,7 +37,7 @@ class PeoplePicker extends HookConsumerWidget { decoration: InputDecoration( contentPadding: const EdgeInsets.only(left: 24), filled: true, - fillColor: context.primaryColor.withOpacity(0.1), + fillColor: context.primaryColor.withValues(alpha: 0.1), hintStyle: context.textTheme.bodyLarge?.copyWith( color: context.themeData.colorScheme.onSurfaceSecondary, ), diff --git a/mobile/lib/widgets/search/search_filter/search_filter_chip.dart b/mobile/lib/widgets/search/search_filter/search_filter_chip.dart index 2a445c8ad7..c1e628adeb 100644 --- a/mobile/lib/widgets/search/search_filter/search_filter_chip.dart +++ b/mobile/lib/widgets/search/search_filter/search_filter_chip.dart @@ -22,7 +22,7 @@ class SearchFilterChip extends StatelessWidget { onTap: onTap, child: Card( elevation: 0, - color: context.primaryColor.withOpacity(.5), + color: context.primaryColor.withValues(alpha: .5), shape: StadiumBorder( side: BorderSide(color: context.colorScheme.secondaryContainer), ), diff --git a/mobile/lib/widgets/search/thumbnail_with_info_container.dart b/mobile/lib/widgets/search/thumbnail_with_info_container.dart index d2084bdcc8..1f5f3c2d16 100644 --- a/mobile/lib/widgets/search/thumbnail_with_info_container.dart +++ b/mobile/lib/widgets/search/thumbnail_with_info_container.dart @@ -44,8 +44,8 @@ class ThumbnailWithInfoContainer extends StatelessWidget { colors: [ Colors.transparent, label == '' - ? Colors.black.withOpacity(0.1) - : Colors.black.withOpacity(0.5), + ? Colors.black.withValues(alpha: 0.1) + : Colors.black.withValues(alpha: 0.5), ], stops: const [0.0, 1.0], ), diff --git a/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart b/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart index 09f2617152..633d84c9c8 100644 --- a/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart +++ b/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart @@ -72,7 +72,7 @@ class ExternalNetworkPreference extends HookConsumerWidget { builder: (BuildContext context, Widget? child) { return Material( color: context.colorScheme.surfaceContainerHighest, - shadowColor: context.colorScheme.primary.withOpacity(0.2), + shadowColor: context.colorScheme.primary.withValues(alpha: 0.2), child: child, ); }, @@ -116,7 +116,7 @@ class ExternalNetworkPreference extends HookConsumerWidget { child: Icon( Icons.dns_rounded, size: 120, - color: context.primaryColor.withOpacity(0.05), + color: context.primaryColor.withValues(alpha: 0.05), ), ), ListView( diff --git a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart index 6e38531afc..a50d216a9d 100644 --- a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart +++ b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart @@ -161,7 +161,7 @@ class LocalNetworkPreference extends HookConsumerWidget { child: Icon( Icons.home_outlined, size: 120, - color: context.primaryColor.withOpacity(0.05), + color: context.primaryColor.withValues(alpha: 0.05), ), ), ListView( diff --git a/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart b/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart index 119407ccad..af34ab9e16 100644 --- a/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart +++ b/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart @@ -98,7 +98,7 @@ class PrimaryColorSetting extends HookConsumerWidget { child: Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(100)), - color: Colors.grey[900]?.withOpacity(.4), + color: Colors.grey[900]?.withValues(alpha: .4), ), child: const Padding( padding: EdgeInsets.all(3), diff --git a/mobile/lib/widgets/shared_link/shared_link_item.dart b/mobile/lib/widgets/shared_link/shared_link_item.dart index a9ed359280..09724a37d3 100644 --- a/mobile/lib/widgets/shared_link/shared_link_item.dart +++ b/mobile/lib/widgets/shared_link/shared_link_item.dart @@ -240,7 +240,7 @@ class SharedLinkItem extends ConsumerWidget { child: Tooltip( verticalOffset: 0, decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.9), + color: colorScheme.primary.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(10), ), textStyle: TextStyle( @@ -268,7 +268,7 @@ class SharedLinkItem extends ConsumerWidget { child: Tooltip( verticalOffset: 0, decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.9), + color: colorScheme.primary.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(10), ), textStyle: TextStyle( diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 3a3a3bc6ca..5bd01b2e62 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -3,7 +3,7 @@ Immich API This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: -- API version: 1.128.0 +- API version: 1.129.0 - Generator version: 7.8.0 - Build package: org.openapitools.codegen.languages.DartClientCodegen @@ -225,6 +225,7 @@ Class | Method | HTTP request | Description *TagsApi* | [**untagAssets**](doc//TagsApi.md#untagassets) | **DELETE** /tags/{id}/assets | *TagsApi* | [**updateTag**](doc//TagsApi.md#updatetag) | **PUT** /tags/{id} | *TagsApi* | [**upsertTags**](doc//TagsApi.md#upserttags) | **PUT** /tags | +*TimelineApi* | [**getLiteTimeBucket**](doc//TimelineApi.md#getlitetimebucket) | **GET** /timeline/liteBucket | *TimelineApi* | [**getTimeBucket**](doc//TimelineApi.md#gettimebucket) | **GET** /timeline/bucket | *TimelineApi* | [**getTimeBuckets**](doc//TimelineApi.md#gettimebuckets) | **GET** /timeline/buckets | *TrashApi* | [**emptyTrash**](doc//TrashApi.md#emptytrash) | **POST** /trash/empty | @@ -345,6 +346,8 @@ Class | Method | HTTP request | Description - [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md) - [LicenseKeyDto](doc//LicenseKeyDto.md) - [LicenseResponseDto](doc//LicenseResponseDto.md) + - [LiteTimeBucketAssetResponseDto](doc//LiteTimeBucketAssetResponseDto.md) + - [LiteTimeBucketResponseDto](doc//LiteTimeBucketResponseDto.md) - [LogLevel](doc//LogLevel.md) - [LoginCredentialDto](doc//LoginCredentialDto.md) - [LoginResponseDto](doc//LoginResponseDto.md) @@ -424,6 +427,9 @@ Class | Method | HTTP request | Description - [SyncAckDeleteDto](doc//SyncAckDeleteDto.md) - [SyncAckDto](doc//SyncAckDto.md) - [SyncAckSetDto](doc//SyncAckSetDto.md) + - [SyncAssetDeleteV1](doc//SyncAssetDeleteV1.md) + - [SyncAssetExifV1](doc//SyncAssetExifV1.md) + - [SyncAssetV1](doc//SyncAssetV1.md) - [SyncEntityType](doc//SyncEntityType.md) - [SyncPartnerDeleteV1](doc//SyncPartnerDeleteV1.md) - [SyncPartnerV1](doc//SyncPartnerV1.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 04dc43f88c..3c6209c3f7 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -152,6 +152,8 @@ part 'model/library_response_dto.dart'; part 'model/library_stats_response_dto.dart'; part 'model/license_key_dto.dart'; part 'model/license_response_dto.dart'; +part 'model/lite_time_bucket_asset_response_dto.dart'; +part 'model/lite_time_bucket_response_dto.dart'; part 'model/log_level.dart'; part 'model/login_credential_dto.dart'; part 'model/login_response_dto.dart'; @@ -231,6 +233,9 @@ part 'model/stack_update_dto.dart'; part 'model/sync_ack_delete_dto.dart'; part 'model/sync_ack_dto.dart'; part 'model/sync_ack_set_dto.dart'; +part 'model/sync_asset_delete_v1.dart'; +part 'model/sync_asset_exif_v1.dart'; +part 'model/sync_asset_v1.dart'; part 'model/sync_entity_type.dart'; part 'model/sync_partner_delete_v1.dart'; part 'model/sync_partner_v1.dart'; diff --git a/mobile/openapi/lib/api/activities_api.dart b/mobile/openapi/lib/api/activities_api.dart index e5075eee16..5c83ba7db9 100644 --- a/mobile/openapi/lib/api/activities_api.dart +++ b/mobile/openapi/lib/api/activities_api.dart @@ -22,7 +22,7 @@ class ActivitiesApi { /// * [ActivityCreateDto] activityCreateDto (required): Future createActivityWithHttpInfo(ActivityCreateDto activityCreateDto,) async { // ignore: prefer_const_declarations - final path = r'/activities'; + final apiPath = r'/activities'; // ignore: prefer_final_locals Object? postBody = activityCreateDto; @@ -35,7 +35,7 @@ class ActivitiesApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -69,7 +69,7 @@ class ActivitiesApi { /// * [String] id (required): Future deleteActivityWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/activities/{id}' + final apiPath = r'/activities/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -83,7 +83,7 @@ class ActivitiesApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -117,7 +117,7 @@ class ActivitiesApi { /// * [String] userId: Future getActivitiesWithHttpInfo(String albumId, { String? assetId, ReactionLevel? level, ReactionType? type, String? userId, }) async { // ignore: prefer_const_declarations - final path = r'/activities'; + final apiPath = r'/activities'; // ignore: prefer_final_locals Object? postBody; @@ -144,7 +144,7 @@ class ActivitiesApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -191,7 +191,7 @@ class ActivitiesApi { /// * [String] assetId: Future getActivityStatisticsWithHttpInfo(String albumId, { String? assetId, }) async { // ignore: prefer_const_declarations - final path = r'/activities/statistics'; + final apiPath = r'/activities/statistics'; // ignore: prefer_final_locals Object? postBody; @@ -209,7 +209,7 @@ class ActivitiesApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, diff --git a/mobile/openapi/lib/api/albums_api.dart b/mobile/openapi/lib/api/albums_api.dart index eb2bb7c0bd..a8c518ace2 100644 --- a/mobile/openapi/lib/api/albums_api.dart +++ b/mobile/openapi/lib/api/albums_api.dart @@ -26,7 +26,7 @@ class AlbumsApi { /// * [String] key: Future addAssetsToAlbumWithHttpInfo(String id, BulkIdsDto bulkIdsDto, { String? key, }) async { // ignore: prefer_const_declarations - final path = r'/albums/{id}/assets' + final apiPath = r'/albums/{id}/assets' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -44,7 +44,7 @@ class AlbumsApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -87,7 +87,7 @@ class AlbumsApi { /// * [AddUsersDto] addUsersDto (required): Future addUsersToAlbumWithHttpInfo(String id, AddUsersDto addUsersDto,) async { // ignore: prefer_const_declarations - final path = r'/albums/{id}/users' + final apiPath = r'/albums/{id}/users' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -101,7 +101,7 @@ class AlbumsApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -137,7 +137,7 @@ class AlbumsApi { /// * [CreateAlbumDto] createAlbumDto (required): Future createAlbumWithHttpInfo(CreateAlbumDto createAlbumDto,) async { // ignore: prefer_const_declarations - final path = r'/albums'; + final apiPath = r'/albums'; // ignore: prefer_final_locals Object? postBody = createAlbumDto; @@ -150,7 +150,7 @@ class AlbumsApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -184,7 +184,7 @@ class AlbumsApi { /// * [String] id (required): Future deleteAlbumWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/albums/{id}' + final apiPath = r'/albums/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -198,7 +198,7 @@ class AlbumsApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -228,7 +228,7 @@ class AlbumsApi { /// * [bool] withoutAssets: Future getAlbumInfoWithHttpInfo(String id, { String? key, bool? withoutAssets, }) async { // ignore: prefer_const_declarations - final path = r'/albums/{id}' + final apiPath = r'/albums/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -249,7 +249,7 @@ class AlbumsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -284,7 +284,7 @@ class AlbumsApi { /// Performs an HTTP 'GET /albums/statistics' operation and returns the [Response]. Future getAlbumStatisticsWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/albums/statistics'; + final apiPath = r'/albums/statistics'; // ignore: prefer_final_locals Object? postBody; @@ -297,7 +297,7 @@ class AlbumsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -331,7 +331,7 @@ class AlbumsApi { /// * [bool] shared: Future getAllAlbumsWithHttpInfo({ String? assetId, bool? shared, }) async { // ignore: prefer_const_declarations - final path = r'/albums'; + final apiPath = r'/albums'; // ignore: prefer_final_locals Object? postBody; @@ -351,7 +351,7 @@ class AlbumsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -393,7 +393,7 @@ class AlbumsApi { /// * [BulkIdsDto] bulkIdsDto (required): Future removeAssetFromAlbumWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { // ignore: prefer_const_declarations - final path = r'/albums/{id}/assets' + final apiPath = r'/albums/{id}/assets' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -407,7 +407,7 @@ class AlbumsApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -448,7 +448,7 @@ class AlbumsApi { /// * [String] userId (required): Future removeUserFromAlbumWithHttpInfo(String id, String userId,) async { // ignore: prefer_const_declarations - final path = r'/albums/{id}/user/{userId}' + final apiPath = r'/albums/{id}/user/{userId}' .replaceAll('{id}', id) .replaceAll('{userId}', userId); @@ -463,7 +463,7 @@ class AlbumsApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -493,7 +493,7 @@ class AlbumsApi { /// * [UpdateAlbumDto] updateAlbumDto (required): Future updateAlbumInfoWithHttpInfo(String id, UpdateAlbumDto updateAlbumDto,) async { // ignore: prefer_const_declarations - final path = r'/albums/{id}' + final apiPath = r'/albums/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -507,7 +507,7 @@ class AlbumsApi { return apiClient.invokeAPI( - path, + apiPath, 'PATCH', queryParams, postBody, @@ -547,7 +547,7 @@ class AlbumsApi { /// * [UpdateAlbumUserDto] updateAlbumUserDto (required): Future updateAlbumUserWithHttpInfo(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto,) async { // ignore: prefer_const_declarations - final path = r'/albums/{id}/user/{userId}' + final apiPath = r'/albums/{id}/user/{userId}' .replaceAll('{id}', id) .replaceAll('{userId}', userId); @@ -562,7 +562,7 @@ class AlbumsApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, diff --git a/mobile/openapi/lib/api/api_keys_api.dart b/mobile/openapi/lib/api/api_keys_api.dart index 2e7757f20a..cf54ac5c04 100644 --- a/mobile/openapi/lib/api/api_keys_api.dart +++ b/mobile/openapi/lib/api/api_keys_api.dart @@ -22,7 +22,7 @@ class APIKeysApi { /// * [APIKeyCreateDto] aPIKeyCreateDto (required): Future createApiKeyWithHttpInfo(APIKeyCreateDto aPIKeyCreateDto,) async { // ignore: prefer_const_declarations - final path = r'/api-keys'; + final apiPath = r'/api-keys'; // ignore: prefer_final_locals Object? postBody = aPIKeyCreateDto; @@ -35,7 +35,7 @@ class APIKeysApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -69,7 +69,7 @@ class APIKeysApi { /// * [String] id (required): Future deleteApiKeyWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/api-keys/{id}' + final apiPath = r'/api-keys/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -83,7 +83,7 @@ class APIKeysApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -109,7 +109,7 @@ class APIKeysApi { /// * [String] id (required): Future getApiKeyWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/api-keys/{id}' + final apiPath = r'/api-keys/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -123,7 +123,7 @@ class APIKeysApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -154,7 +154,7 @@ class APIKeysApi { /// Performs an HTTP 'GET /api-keys' operation and returns the [Response]. Future getApiKeysWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/api-keys'; + final apiPath = r'/api-keys'; // ignore: prefer_final_locals Object? postBody; @@ -167,7 +167,7 @@ class APIKeysApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -203,7 +203,7 @@ class APIKeysApi { /// * [APIKeyUpdateDto] aPIKeyUpdateDto (required): Future updateApiKeyWithHttpInfo(String id, APIKeyUpdateDto aPIKeyUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/api-keys/{id}' + final apiPath = r'/api-keys/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -217,7 +217,7 @@ class APIKeysApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, diff --git a/mobile/openapi/lib/api/assets_api.dart b/mobile/openapi/lib/api/assets_api.dart index e7272d094c..f52c70b37f 100644 --- a/mobile/openapi/lib/api/assets_api.dart +++ b/mobile/openapi/lib/api/assets_api.dart @@ -27,7 +27,7 @@ class AssetsApi { /// * [AssetBulkUploadCheckDto] assetBulkUploadCheckDto (required): Future checkBulkUploadWithHttpInfo(AssetBulkUploadCheckDto assetBulkUploadCheckDto,) async { // ignore: prefer_const_declarations - final path = r'/assets/bulk-upload-check'; + final apiPath = r'/assets/bulk-upload-check'; // ignore: prefer_final_locals Object? postBody = assetBulkUploadCheckDto; @@ -40,7 +40,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -83,7 +83,7 @@ class AssetsApi { /// * [CheckExistingAssetsDto] checkExistingAssetsDto (required): Future checkExistingAssetsWithHttpInfo(CheckExistingAssetsDto checkExistingAssetsDto,) async { // ignore: prefer_const_declarations - final path = r'/assets/exist'; + final apiPath = r'/assets/exist'; // ignore: prefer_final_locals Object? postBody = checkExistingAssetsDto; @@ -96,7 +96,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -134,7 +134,7 @@ class AssetsApi { /// * [AssetBulkDeleteDto] assetBulkDeleteDto (required): Future deleteAssetsWithHttpInfo(AssetBulkDeleteDto assetBulkDeleteDto,) async { // ignore: prefer_const_declarations - final path = r'/assets'; + final apiPath = r'/assets'; // ignore: prefer_final_locals Object? postBody = assetBulkDeleteDto; @@ -147,7 +147,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -175,7 +175,7 @@ class AssetsApi { /// * [String] key: Future downloadAssetWithHttpInfo(String id, { String? key, }) async { // ignore: prefer_const_declarations - final path = r'/assets/{id}/original' + final apiPath = r'/assets/{id}/original' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -193,7 +193,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -234,7 +234,7 @@ class AssetsApi { /// * [String] deviceId (required): Future getAllUserAssetsByDeviceIdWithHttpInfo(String deviceId,) async { // ignore: prefer_const_declarations - final path = r'/assets/device/{deviceId}' + final apiPath = r'/assets/device/{deviceId}' .replaceAll('{deviceId}', deviceId); // ignore: prefer_final_locals @@ -248,7 +248,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -291,7 +291,7 @@ class AssetsApi { /// * [String] key: Future getAssetInfoWithHttpInfo(String id, { String? key, }) async { // ignore: prefer_const_declarations - final path = r'/assets/{id}' + final apiPath = r'/assets/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -309,7 +309,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -349,7 +349,7 @@ class AssetsApi { /// * [bool] isTrashed: Future getAssetStatisticsWithHttpInfo({ bool? isArchived, bool? isFavorite, bool? isTrashed, }) async { // ignore: prefer_const_declarations - final path = r'/assets/statistics'; + final apiPath = r'/assets/statistics'; // ignore: prefer_final_locals Object? postBody; @@ -372,7 +372,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -412,7 +412,7 @@ class AssetsApi { /// * [int] month (required): Future getMemoryLaneWithHttpInfo(int day, int month,) async { // ignore: prefer_const_declarations - final path = r'/assets/memory-lane'; + final apiPath = r'/assets/memory-lane'; // ignore: prefer_final_locals Object? postBody; @@ -428,7 +428,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -470,7 +470,7 @@ class AssetsApi { /// * [num] count: Future getRandomWithHttpInfo({ num? count, }) async { // ignore: prefer_const_declarations - final path = r'/assets/random'; + final apiPath = r'/assets/random'; // ignore: prefer_final_locals Object? postBody; @@ -487,7 +487,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -528,7 +528,7 @@ class AssetsApi { /// * [String] key: Future playAssetVideoWithHttpInfo(String id, { String? key, }) async { // ignore: prefer_const_declarations - final path = r'/assets/{id}/video/playback' + final apiPath = r'/assets/{id}/video/playback' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -546,7 +546,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -601,7 +601,7 @@ class AssetsApi { /// * [String] duration: Future replaceAssetWithHttpInfo(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? duration, }) async { // ignore: prefer_const_declarations - final path = r'/assets/{id}/original' + final apiPath = r'/assets/{id}/original' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -618,7 +618,7 @@ class AssetsApi { const contentTypes = ['multipart/form-data']; bool hasFields = false; - final mp = MultipartRequest('PUT', Uri.parse(path)); + final mp = MultipartRequest('PUT', Uri.parse(apiPath)); if (assetData != null) { hasFields = true; mp.fields[r'assetData'] = assetData.field; @@ -649,7 +649,7 @@ class AssetsApi { } return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -701,7 +701,7 @@ class AssetsApi { /// * [AssetJobsDto] assetJobsDto (required): Future runAssetJobsWithHttpInfo(AssetJobsDto assetJobsDto,) async { // ignore: prefer_const_declarations - final path = r'/assets/jobs'; + final apiPath = r'/assets/jobs'; // ignore: prefer_final_locals Object? postBody = assetJobsDto; @@ -714,7 +714,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -742,7 +742,7 @@ class AssetsApi { /// * [UpdateAssetDto] updateAssetDto (required): Future updateAssetWithHttpInfo(String id, UpdateAssetDto updateAssetDto,) async { // ignore: prefer_const_declarations - final path = r'/assets/{id}' + final apiPath = r'/assets/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -756,7 +756,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -792,7 +792,7 @@ class AssetsApi { /// * [AssetBulkUpdateDto] assetBulkUpdateDto (required): Future updateAssetsWithHttpInfo(AssetBulkUpdateDto assetBulkUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/assets'; + final apiPath = r'/assets'; // ignore: prefer_final_locals Object? postBody = assetBulkUpdateDto; @@ -805,7 +805,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -856,7 +856,7 @@ class AssetsApi { /// * [MultipartFile] sidecarData: Future uploadAssetWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isArchived, bool? isFavorite, bool? isVisible, String? livePhotoVideoId, MultipartFile? sidecarData, }) async { // ignore: prefer_const_declarations - final path = r'/assets'; + final apiPath = r'/assets'; // ignore: prefer_final_locals Object? postBody; @@ -876,7 +876,7 @@ class AssetsApi { const contentTypes = ['multipart/form-data']; bool hasFields = false; - final mp = MultipartRequest('POST', Uri.parse(path)); + final mp = MultipartRequest('POST', Uri.parse(apiPath)); if (assetData != null) { hasFields = true; mp.fields[r'assetData'] = assetData.field; @@ -928,7 +928,7 @@ class AssetsApi { } return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -991,7 +991,7 @@ class AssetsApi { /// * [AssetMediaSize] size: Future viewAssetWithHttpInfo(String id, { String? key, AssetMediaSize? size, }) async { // ignore: prefer_const_declarations - final path = r'/assets/{id}/thumbnail' + final apiPath = r'/assets/{id}/thumbnail' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -1012,7 +1012,7 @@ class AssetsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, diff --git a/mobile/openapi/lib/api/authentication_api.dart b/mobile/openapi/lib/api/authentication_api.dart index cb81867425..bf987f441e 100644 --- a/mobile/openapi/lib/api/authentication_api.dart +++ b/mobile/openapi/lib/api/authentication_api.dart @@ -22,7 +22,7 @@ class AuthenticationApi { /// * [ChangePasswordDto] changePasswordDto (required): Future changePasswordWithHttpInfo(ChangePasswordDto changePasswordDto,) async { // ignore: prefer_const_declarations - final path = r'/auth/change-password'; + final apiPath = r'/auth/change-password'; // ignore: prefer_final_locals Object? postBody = changePasswordDto; @@ -35,7 +35,7 @@ class AuthenticationApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -69,7 +69,7 @@ class AuthenticationApi { /// * [LoginCredentialDto] loginCredentialDto (required): Future loginWithHttpInfo(LoginCredentialDto loginCredentialDto,) async { // ignore: prefer_const_declarations - final path = r'/auth/login'; + final apiPath = r'/auth/login'; // ignore: prefer_final_locals Object? postBody = loginCredentialDto; @@ -82,7 +82,7 @@ class AuthenticationApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -113,7 +113,7 @@ class AuthenticationApi { /// Performs an HTTP 'POST /auth/logout' operation and returns the [Response]. Future logoutWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/auth/logout'; + final apiPath = r'/auth/logout'; // ignore: prefer_final_locals Object? postBody; @@ -126,7 +126,7 @@ class AuthenticationApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -157,7 +157,7 @@ class AuthenticationApi { /// * [SignUpDto] signUpDto (required): Future signUpAdminWithHttpInfo(SignUpDto signUpDto,) async { // ignore: prefer_const_declarations - final path = r'/auth/admin-sign-up'; + final apiPath = r'/auth/admin-sign-up'; // ignore: prefer_final_locals Object? postBody = signUpDto; @@ -170,7 +170,7 @@ class AuthenticationApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -201,7 +201,7 @@ class AuthenticationApi { /// Performs an HTTP 'POST /auth/validateToken' operation and returns the [Response]. Future validateAccessTokenWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/auth/validateToken'; + final apiPath = r'/auth/validateToken'; // ignore: prefer_final_locals Object? postBody; @@ -214,7 +214,7 @@ class AuthenticationApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, diff --git a/mobile/openapi/lib/api/deprecated_api.dart b/mobile/openapi/lib/api/deprecated_api.dart index 30e35b451c..7aa9662c23 100644 --- a/mobile/openapi/lib/api/deprecated_api.dart +++ b/mobile/openapi/lib/api/deprecated_api.dart @@ -25,7 +25,7 @@ class DeprecatedApi { /// * [num] count: Future getRandomWithHttpInfo({ num? count, }) async { // ignore: prefer_const_declarations - final path = r'/assets/random'; + final apiPath = r'/assets/random'; // ignore: prefer_final_locals Object? postBody; @@ -42,7 +42,7 @@ class DeprecatedApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, diff --git a/mobile/openapi/lib/api/download_api.dart b/mobile/openapi/lib/api/download_api.dart index b89f340ec7..3b11c2f630 100644 --- a/mobile/openapi/lib/api/download_api.dart +++ b/mobile/openapi/lib/api/download_api.dart @@ -24,7 +24,7 @@ class DownloadApi { /// * [String] key: Future downloadArchiveWithHttpInfo(AssetIdsDto assetIdsDto, { String? key, }) async { // ignore: prefer_const_declarations - final path = r'/download/archive'; + final apiPath = r'/download/archive'; // ignore: prefer_final_locals Object? postBody = assetIdsDto; @@ -41,7 +41,7 @@ class DownloadApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -79,7 +79,7 @@ class DownloadApi { /// * [String] key: Future getDownloadInfoWithHttpInfo(DownloadInfoDto downloadInfoDto, { String? key, }) async { // ignore: prefer_const_declarations - final path = r'/download/info'; + final apiPath = r'/download/info'; // ignore: prefer_final_locals Object? postBody = downloadInfoDto; @@ -96,7 +96,7 @@ class DownloadApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, diff --git a/mobile/openapi/lib/api/duplicates_api.dart b/mobile/openapi/lib/api/duplicates_api.dart index b82290e47b..715c6d6112 100644 --- a/mobile/openapi/lib/api/duplicates_api.dart +++ b/mobile/openapi/lib/api/duplicates_api.dart @@ -19,7 +19,7 @@ class DuplicatesApi { /// Performs an HTTP 'GET /duplicates' operation and returns the [Response]. Future getAssetDuplicatesWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/duplicates'; + final apiPath = r'/duplicates'; // ignore: prefer_final_locals Object? postBody; @@ -32,7 +32,7 @@ class DuplicatesApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, diff --git a/mobile/openapi/lib/api/faces_api.dart b/mobile/openapi/lib/api/faces_api.dart index e92ee93e42..44e3d53f8e 100644 --- a/mobile/openapi/lib/api/faces_api.dart +++ b/mobile/openapi/lib/api/faces_api.dart @@ -22,7 +22,7 @@ class FacesApi { /// * [AssetFaceCreateDto] assetFaceCreateDto (required): Future createFaceWithHttpInfo(AssetFaceCreateDto assetFaceCreateDto,) async { // ignore: prefer_const_declarations - final path = r'/faces'; + final apiPath = r'/faces'; // ignore: prefer_final_locals Object? postBody = assetFaceCreateDto; @@ -35,7 +35,7 @@ class FacesApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -63,7 +63,7 @@ class FacesApi { /// * [AssetFaceDeleteDto] assetFaceDeleteDto (required): Future deleteFaceWithHttpInfo(String id, AssetFaceDeleteDto assetFaceDeleteDto,) async { // ignore: prefer_const_declarations - final path = r'/faces/{id}' + final apiPath = r'/faces/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -77,7 +77,7 @@ class FacesApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -105,7 +105,7 @@ class FacesApi { /// * [String] id (required): Future getFacesWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/faces'; + final apiPath = r'/faces'; // ignore: prefer_final_locals Object? postBody; @@ -120,7 +120,7 @@ class FacesApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -159,7 +159,7 @@ class FacesApi { /// * [FaceDto] faceDto (required): Future reassignFacesByIdWithHttpInfo(String id, FaceDto faceDto,) async { // ignore: prefer_const_declarations - final path = r'/faces/{id}' + final apiPath = r'/faces/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -173,7 +173,7 @@ class FacesApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, diff --git a/mobile/openapi/lib/api/file_reports_api.dart b/mobile/openapi/lib/api/file_reports_api.dart index 5eab91576e..73b3feaedb 100644 --- a/mobile/openapi/lib/api/file_reports_api.dart +++ b/mobile/openapi/lib/api/file_reports_api.dart @@ -22,7 +22,7 @@ class FileReportsApi { /// * [FileReportFixDto] fileReportFixDto (required): Future fixAuditFilesWithHttpInfo(FileReportFixDto fileReportFixDto,) async { // ignore: prefer_const_declarations - final path = r'/reports/fix'; + final apiPath = r'/reports/fix'; // ignore: prefer_final_locals Object? postBody = fileReportFixDto; @@ -35,7 +35,7 @@ class FileReportsApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -58,7 +58,7 @@ class FileReportsApi { /// Performs an HTTP 'GET /reports' operation and returns the [Response]. Future getAuditFilesWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/reports'; + final apiPath = r'/reports'; // ignore: prefer_final_locals Object? postBody; @@ -71,7 +71,7 @@ class FileReportsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -102,7 +102,7 @@ class FileReportsApi { /// * [FileChecksumDto] fileChecksumDto (required): Future getFileChecksumsWithHttpInfo(FileChecksumDto fileChecksumDto,) async { // ignore: prefer_const_declarations - final path = r'/reports/checksum'; + final apiPath = r'/reports/checksum'; // ignore: prefer_final_locals Object? postBody = fileChecksumDto; @@ -115,7 +115,7 @@ class FileReportsApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, diff --git a/mobile/openapi/lib/api/jobs_api.dart b/mobile/openapi/lib/api/jobs_api.dart index 78afc15c93..182bb14e4f 100644 --- a/mobile/openapi/lib/api/jobs_api.dart +++ b/mobile/openapi/lib/api/jobs_api.dart @@ -22,7 +22,7 @@ class JobsApi { /// * [JobCreateDto] jobCreateDto (required): Future createJobWithHttpInfo(JobCreateDto jobCreateDto,) async { // ignore: prefer_const_declarations - final path = r'/jobs'; + final apiPath = r'/jobs'; // ignore: prefer_final_locals Object? postBody = jobCreateDto; @@ -35,7 +35,7 @@ class JobsApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -58,7 +58,7 @@ class JobsApi { /// Performs an HTTP 'GET /jobs' operation and returns the [Response]. Future getAllJobsStatusWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/jobs'; + final apiPath = r'/jobs'; // ignore: prefer_final_locals Object? postBody; @@ -71,7 +71,7 @@ class JobsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -104,7 +104,7 @@ class JobsApi { /// * [JobCommandDto] jobCommandDto (required): Future sendJobCommandWithHttpInfo(JobName id, JobCommandDto jobCommandDto,) async { // ignore: prefer_const_declarations - final path = r'/jobs/{id}' + final apiPath = r'/jobs/{id}' .replaceAll('{id}', id.toString()); // ignore: prefer_final_locals @@ -118,7 +118,7 @@ class JobsApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, diff --git a/mobile/openapi/lib/api/libraries_api.dart b/mobile/openapi/lib/api/libraries_api.dart index 36d98d9a88..86acce76b4 100644 --- a/mobile/openapi/lib/api/libraries_api.dart +++ b/mobile/openapi/lib/api/libraries_api.dart @@ -22,7 +22,7 @@ class LibrariesApi { /// * [CreateLibraryDto] createLibraryDto (required): Future createLibraryWithHttpInfo(CreateLibraryDto createLibraryDto,) async { // ignore: prefer_const_declarations - final path = r'/libraries'; + final apiPath = r'/libraries'; // ignore: prefer_final_locals Object? postBody = createLibraryDto; @@ -35,7 +35,7 @@ class LibrariesApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -69,7 +69,7 @@ class LibrariesApi { /// * [String] id (required): Future deleteLibraryWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/libraries/{id}' + final apiPath = r'/libraries/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -83,7 +83,7 @@ class LibrariesApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -106,7 +106,7 @@ class LibrariesApi { /// Performs an HTTP 'GET /libraries' operation and returns the [Response]. Future getAllLibrariesWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/libraries'; + final apiPath = r'/libraries'; // ignore: prefer_final_locals Object? postBody; @@ -119,7 +119,7 @@ class LibrariesApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -153,7 +153,7 @@ class LibrariesApi { /// * [String] id (required): Future getLibraryWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/libraries/{id}' + final apiPath = r'/libraries/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -167,7 +167,7 @@ class LibrariesApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -201,7 +201,7 @@ class LibrariesApi { /// * [String] id (required): Future getLibraryStatisticsWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/libraries/{id}/statistics' + final apiPath = r'/libraries/{id}/statistics' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -215,7 +215,7 @@ class LibrariesApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -249,7 +249,7 @@ class LibrariesApi { /// * [String] id (required): Future scanLibraryWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/libraries/{id}/scan' + final apiPath = r'/libraries/{id}/scan' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -263,7 +263,7 @@ class LibrariesApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -291,7 +291,7 @@ class LibrariesApi { /// * [UpdateLibraryDto] updateLibraryDto (required): Future updateLibraryWithHttpInfo(String id, UpdateLibraryDto updateLibraryDto,) async { // ignore: prefer_const_declarations - final path = r'/libraries/{id}' + final apiPath = r'/libraries/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -305,7 +305,7 @@ class LibrariesApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -343,7 +343,7 @@ class LibrariesApi { /// * [ValidateLibraryDto] validateLibraryDto (required): Future validateWithHttpInfo(String id, ValidateLibraryDto validateLibraryDto,) async { // ignore: prefer_const_declarations - final path = r'/libraries/{id}/validate' + final apiPath = r'/libraries/{id}/validate' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -357,7 +357,7 @@ class LibrariesApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, diff --git a/mobile/openapi/lib/api/map_api.dart b/mobile/openapi/lib/api/map_api.dart index 9644fbfc5c..ffe72df453 100644 --- a/mobile/openapi/lib/api/map_api.dart +++ b/mobile/openapi/lib/api/map_api.dart @@ -32,7 +32,7 @@ class MapApi { /// * [bool] withSharedAlbums: Future getMapMarkersWithHttpInfo({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, bool? withSharedAlbums, }) async { // ignore: prefer_const_declarations - final path = r'/map/markers'; + final apiPath = r'/map/markers'; // ignore: prefer_final_locals Object? postBody; @@ -64,7 +64,7 @@ class MapApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -113,7 +113,7 @@ class MapApi { /// * [double] lon (required): Future reverseGeocodeWithHttpInfo(double lat, double lon,) async { // ignore: prefer_const_declarations - final path = r'/map/reverse-geocode'; + final apiPath = r'/map/reverse-geocode'; // ignore: prefer_final_locals Object? postBody; @@ -129,7 +129,7 @@ class MapApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, diff --git a/mobile/openapi/lib/api/memories_api.dart b/mobile/openapi/lib/api/memories_api.dart index c5b04a7c7c..88897d3038 100644 --- a/mobile/openapi/lib/api/memories_api.dart +++ b/mobile/openapi/lib/api/memories_api.dart @@ -24,7 +24,7 @@ class MemoriesApi { /// * [BulkIdsDto] bulkIdsDto (required): Future addMemoryAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { // ignore: prefer_const_declarations - final path = r'/memories/{id}/assets' + final apiPath = r'/memories/{id}/assets' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -38,7 +38,7 @@ class MemoriesApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -77,7 +77,7 @@ class MemoriesApi { /// * [MemoryCreateDto] memoryCreateDto (required): Future createMemoryWithHttpInfo(MemoryCreateDto memoryCreateDto,) async { // ignore: prefer_const_declarations - final path = r'/memories'; + final apiPath = r'/memories'; // ignore: prefer_final_locals Object? postBody = memoryCreateDto; @@ -90,7 +90,7 @@ class MemoriesApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -124,7 +124,7 @@ class MemoriesApi { /// * [String] id (required): Future deleteMemoryWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/memories/{id}' + final apiPath = r'/memories/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -138,7 +138,7 @@ class MemoriesApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -164,7 +164,7 @@ class MemoriesApi { /// * [String] id (required): Future getMemoryWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/memories/{id}' + final apiPath = r'/memories/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -178,7 +178,7 @@ class MemoriesApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -214,7 +214,7 @@ class MemoriesApi { /// * [BulkIdsDto] bulkIdsDto (required): Future removeMemoryAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { // ignore: prefer_const_declarations - final path = r'/memories/{id}/assets' + final apiPath = r'/memories/{id}/assets' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -228,7 +228,7 @@ class MemoriesApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -273,7 +273,7 @@ class MemoriesApi { /// * [MemoryType] type: Future searchMemoriesWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemoryType? type, }) async { // ignore: prefer_const_declarations - final path = r'/memories'; + final apiPath = r'/memories'; // ignore: prefer_final_locals Object? postBody; @@ -299,7 +299,7 @@ class MemoriesApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -344,7 +344,7 @@ class MemoriesApi { /// * [MemoryUpdateDto] memoryUpdateDto (required): Future updateMemoryWithHttpInfo(String id, MemoryUpdateDto memoryUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/memories/{id}' + final apiPath = r'/memories/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -358,7 +358,7 @@ class MemoriesApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, diff --git a/mobile/openapi/lib/api/notifications_api.dart b/mobile/openapi/lib/api/notifications_api.dart index 323fbcc3d6..518a1baa4a 100644 --- a/mobile/openapi/lib/api/notifications_api.dart +++ b/mobile/openapi/lib/api/notifications_api.dart @@ -24,7 +24,7 @@ class NotificationsApi { /// * [TemplateDto] templateDto (required): Future getNotificationTemplateWithHttpInfo(String name, TemplateDto templateDto,) async { // ignore: prefer_const_declarations - final path = r'/notifications/templates/{name}' + final apiPath = r'/notifications/templates/{name}' .replaceAll('{name}', name); // ignore: prefer_final_locals @@ -38,7 +38,7 @@ class NotificationsApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -74,7 +74,7 @@ class NotificationsApi { /// * [SystemConfigSmtpDto] systemConfigSmtpDto (required): Future sendTestEmailWithHttpInfo(SystemConfigSmtpDto systemConfigSmtpDto,) async { // ignore: prefer_const_declarations - final path = r'/notifications/test-email'; + final apiPath = r'/notifications/test-email'; // ignore: prefer_final_locals Object? postBody = systemConfigSmtpDto; @@ -87,7 +87,7 @@ class NotificationsApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, diff --git a/mobile/openapi/lib/api/o_auth_api.dart b/mobile/openapi/lib/api/o_auth_api.dart index aafcb28461..9f16e37c70 100644 --- a/mobile/openapi/lib/api/o_auth_api.dart +++ b/mobile/openapi/lib/api/o_auth_api.dart @@ -22,7 +22,7 @@ class OAuthApi { /// * [OAuthCallbackDto] oAuthCallbackDto (required): Future finishOAuthWithHttpInfo(OAuthCallbackDto oAuthCallbackDto,) async { // ignore: prefer_const_declarations - final path = r'/oauth/callback'; + final apiPath = r'/oauth/callback'; // ignore: prefer_final_locals Object? postBody = oAuthCallbackDto; @@ -35,7 +35,7 @@ class OAuthApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -69,7 +69,7 @@ class OAuthApi { /// * [OAuthCallbackDto] oAuthCallbackDto (required): Future linkOAuthAccountWithHttpInfo(OAuthCallbackDto oAuthCallbackDto,) async { // ignore: prefer_const_declarations - final path = r'/oauth/link'; + final apiPath = r'/oauth/link'; // ignore: prefer_final_locals Object? postBody = oAuthCallbackDto; @@ -82,7 +82,7 @@ class OAuthApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -113,7 +113,7 @@ class OAuthApi { /// Performs an HTTP 'GET /oauth/mobile-redirect' operation and returns the [Response]. Future redirectOAuthToMobileWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/oauth/mobile-redirect'; + final apiPath = r'/oauth/mobile-redirect'; // ignore: prefer_final_locals Object? postBody; @@ -126,7 +126,7 @@ class OAuthApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -149,7 +149,7 @@ class OAuthApi { /// * [OAuthConfigDto] oAuthConfigDto (required): Future startOAuthWithHttpInfo(OAuthConfigDto oAuthConfigDto,) async { // ignore: prefer_const_declarations - final path = r'/oauth/authorize'; + final apiPath = r'/oauth/authorize'; // ignore: prefer_final_locals Object? postBody = oAuthConfigDto; @@ -162,7 +162,7 @@ class OAuthApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -193,7 +193,7 @@ class OAuthApi { /// Performs an HTTP 'POST /oauth/unlink' operation and returns the [Response]. Future unlinkOAuthAccountWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/oauth/unlink'; + final apiPath = r'/oauth/unlink'; // ignore: prefer_final_locals Object? postBody; @@ -206,7 +206,7 @@ class OAuthApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, diff --git a/mobile/openapi/lib/api/partners_api.dart b/mobile/openapi/lib/api/partners_api.dart index ac0d03054a..9f10ea4d1e 100644 --- a/mobile/openapi/lib/api/partners_api.dart +++ b/mobile/openapi/lib/api/partners_api.dart @@ -22,7 +22,7 @@ class PartnersApi { /// * [String] id (required): Future createPartnerWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/partners/{id}' + final apiPath = r'/partners/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -36,7 +36,7 @@ class PartnersApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -70,7 +70,7 @@ class PartnersApi { /// * [PartnerDirection] direction (required): Future getPartnersWithHttpInfo(PartnerDirection direction,) async { // ignore: prefer_const_declarations - final path = r'/partners'; + final apiPath = r'/partners'; // ignore: prefer_final_locals Object? postBody; @@ -85,7 +85,7 @@ class PartnersApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -122,7 +122,7 @@ class PartnersApi { /// * [String] id (required): Future removePartnerWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/partners/{id}' + final apiPath = r'/partners/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -136,7 +136,7 @@ class PartnersApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -164,7 +164,7 @@ class PartnersApi { /// * [UpdatePartnerDto] updatePartnerDto (required): Future updatePartnerWithHttpInfo(String id, UpdatePartnerDto updatePartnerDto,) async { // ignore: prefer_const_declarations - final path = r'/partners/{id}' + final apiPath = r'/partners/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -178,7 +178,7 @@ class PartnersApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, diff --git a/mobile/openapi/lib/api/people_api.dart b/mobile/openapi/lib/api/people_api.dart index 92bd0fdeea..1cdb878852 100644 --- a/mobile/openapi/lib/api/people_api.dart +++ b/mobile/openapi/lib/api/people_api.dart @@ -22,7 +22,7 @@ class PeopleApi { /// * [PersonCreateDto] personCreateDto (required): Future createPersonWithHttpInfo(PersonCreateDto personCreateDto,) async { // ignore: prefer_const_declarations - final path = r'/people'; + final apiPath = r'/people'; // ignore: prefer_final_locals Object? postBody = personCreateDto; @@ -35,7 +35,7 @@ class PeopleApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -79,7 +79,7 @@ class PeopleApi { /// * [bool] withHidden: Future getAllPeopleWithHttpInfo({ String? closestAssetId, String? closestPersonId, num? page, num? size, bool? withHidden, }) async { // ignore: prefer_const_declarations - final path = r'/people'; + final apiPath = r'/people'; // ignore: prefer_final_locals Object? postBody; @@ -108,7 +108,7 @@ class PeopleApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -152,7 +152,7 @@ class PeopleApi { /// * [String] id (required): Future getPersonWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/people/{id}' + final apiPath = r'/people/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -166,7 +166,7 @@ class PeopleApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -200,7 +200,7 @@ class PeopleApi { /// * [String] id (required): Future getPersonStatisticsWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/people/{id}/statistics' + final apiPath = r'/people/{id}/statistics' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -214,7 +214,7 @@ class PeopleApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -248,7 +248,7 @@ class PeopleApi { /// * [String] id (required): Future getPersonThumbnailWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/people/{id}/thumbnail' + final apiPath = r'/people/{id}/thumbnail' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -262,7 +262,7 @@ class PeopleApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -298,7 +298,7 @@ class PeopleApi { /// * [MergePersonDto] mergePersonDto (required): Future mergePersonWithHttpInfo(String id, MergePersonDto mergePersonDto,) async { // ignore: prefer_const_declarations - final path = r'/people/{id}/merge' + final apiPath = r'/people/{id}/merge' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -312,7 +312,7 @@ class PeopleApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -353,7 +353,7 @@ class PeopleApi { /// * [AssetFaceUpdateDto] assetFaceUpdateDto (required): Future reassignFacesWithHttpInfo(String id, AssetFaceUpdateDto assetFaceUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/people/{id}/reassign' + final apiPath = r'/people/{id}/reassign' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -367,7 +367,7 @@ class PeopleApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -406,7 +406,7 @@ class PeopleApi { /// * [PeopleUpdateDto] peopleUpdateDto (required): Future updatePeopleWithHttpInfo(PeopleUpdateDto peopleUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/people'; + final apiPath = r'/people'; // ignore: prefer_final_locals Object? postBody = peopleUpdateDto; @@ -419,7 +419,7 @@ class PeopleApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -458,7 +458,7 @@ class PeopleApi { /// * [PersonUpdateDto] personUpdateDto (required): Future updatePersonWithHttpInfo(String id, PersonUpdateDto personUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/people/{id}' + final apiPath = r'/people/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -472,7 +472,7 @@ class PeopleApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, diff --git a/mobile/openapi/lib/api/search_api.dart b/mobile/openapi/lib/api/search_api.dart index 70af3ab0a3..632107ff79 100644 --- a/mobile/openapi/lib/api/search_api.dart +++ b/mobile/openapi/lib/api/search_api.dart @@ -19,7 +19,7 @@ class SearchApi { /// Performs an HTTP 'GET /search/cities' operation and returns the [Response]. Future getAssetsByCityWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/search/cities'; + final apiPath = r'/search/cities'; // ignore: prefer_final_locals Object? postBody; @@ -32,7 +32,7 @@ class SearchApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -63,7 +63,7 @@ class SearchApi { /// Performs an HTTP 'GET /search/explore' operation and returns the [Response]. Future getExploreDataWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/search/explore'; + final apiPath = r'/search/explore'; // ignore: prefer_final_locals Object? postBody; @@ -76,7 +76,7 @@ class SearchApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -121,7 +121,7 @@ class SearchApi { /// * [String] state: Future getSearchSuggestionsWithHttpInfo(SearchSuggestionType type, { String? country, bool? includeNull, String? make, String? model, String? state, }) async { // ignore: prefer_const_declarations - final path = r'/search/suggestions'; + final apiPath = r'/search/suggestions'; // ignore: prefer_final_locals Object? postBody; @@ -151,7 +151,7 @@ class SearchApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -199,7 +199,7 @@ class SearchApi { /// * [MetadataSearchDto] metadataSearchDto (required): Future searchAssetsWithHttpInfo(MetadataSearchDto metadataSearchDto,) async { // ignore: prefer_const_declarations - final path = r'/search/metadata'; + final apiPath = r'/search/metadata'; // ignore: prefer_final_locals Object? postBody = metadataSearchDto; @@ -212,7 +212,7 @@ class SearchApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -248,7 +248,7 @@ class SearchApi { /// * [bool] withHidden: Future searchPersonWithHttpInfo(String name, { bool? withHidden, }) async { // ignore: prefer_const_declarations - final path = r'/search/person'; + final apiPath = r'/search/person'; // ignore: prefer_final_locals Object? postBody; @@ -266,7 +266,7 @@ class SearchApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -305,7 +305,7 @@ class SearchApi { /// * [String] name (required): Future searchPlacesWithHttpInfo(String name,) async { // ignore: prefer_const_declarations - final path = r'/search/places'; + final apiPath = r'/search/places'; // ignore: prefer_final_locals Object? postBody; @@ -320,7 +320,7 @@ class SearchApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -357,7 +357,7 @@ class SearchApi { /// * [RandomSearchDto] randomSearchDto (required): Future searchRandomWithHttpInfo(RandomSearchDto randomSearchDto,) async { // ignore: prefer_const_declarations - final path = r'/search/random'; + final apiPath = r'/search/random'; // ignore: prefer_final_locals Object? postBody = randomSearchDto; @@ -370,7 +370,7 @@ class SearchApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -407,7 +407,7 @@ class SearchApi { /// * [SmartSearchDto] smartSearchDto (required): Future searchSmartWithHttpInfo(SmartSearchDto smartSearchDto,) async { // ignore: prefer_const_declarations - final path = r'/search/smart'; + final apiPath = r'/search/smart'; // ignore: prefer_final_locals Object? postBody = smartSearchDto; @@ -420,7 +420,7 @@ class SearchApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, diff --git a/mobile/openapi/lib/api/server_api.dart b/mobile/openapi/lib/api/server_api.dart index 7a832ad61a..629949db32 100644 --- a/mobile/openapi/lib/api/server_api.dart +++ b/mobile/openapi/lib/api/server_api.dart @@ -19,7 +19,7 @@ class ServerApi { /// Performs an HTTP 'DELETE /server/license' operation and returns the [Response]. Future deleteServerLicenseWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/server/license'; + final apiPath = r'/server/license'; // ignore: prefer_final_locals Object? postBody; @@ -32,7 +32,7 @@ class ServerApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -52,7 +52,7 @@ class ServerApi { /// Performs an HTTP 'GET /server/about' operation and returns the [Response]. Future getAboutInfoWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/server/about'; + final apiPath = r'/server/about'; // ignore: prefer_final_locals Object? postBody; @@ -65,7 +65,7 @@ class ServerApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -93,7 +93,7 @@ class ServerApi { /// Performs an HTTP 'GET /server/config' operation and returns the [Response]. Future getServerConfigWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/server/config'; + final apiPath = r'/server/config'; // ignore: prefer_final_locals Object? postBody; @@ -106,7 +106,7 @@ class ServerApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -134,7 +134,7 @@ class ServerApi { /// Performs an HTTP 'GET /server/features' operation and returns the [Response]. Future getServerFeaturesWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/server/features'; + final apiPath = r'/server/features'; // ignore: prefer_final_locals Object? postBody; @@ -147,7 +147,7 @@ class ServerApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -175,7 +175,7 @@ class ServerApi { /// Performs an HTTP 'GET /server/license' operation and returns the [Response]. Future getServerLicenseWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/server/license'; + final apiPath = r'/server/license'; // ignore: prefer_final_locals Object? postBody; @@ -188,7 +188,7 @@ class ServerApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -216,7 +216,7 @@ class ServerApi { /// Performs an HTTP 'GET /server/statistics' operation and returns the [Response]. Future getServerStatisticsWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/server/statistics'; + final apiPath = r'/server/statistics'; // ignore: prefer_final_locals Object? postBody; @@ -229,7 +229,7 @@ class ServerApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -257,7 +257,7 @@ class ServerApi { /// Performs an HTTP 'GET /server/version' operation and returns the [Response]. Future getServerVersionWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/server/version'; + final apiPath = r'/server/version'; // ignore: prefer_final_locals Object? postBody; @@ -270,7 +270,7 @@ class ServerApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -298,7 +298,7 @@ class ServerApi { /// Performs an HTTP 'GET /server/storage' operation and returns the [Response]. Future getStorageWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/server/storage'; + final apiPath = r'/server/storage'; // ignore: prefer_final_locals Object? postBody; @@ -311,7 +311,7 @@ class ServerApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -339,7 +339,7 @@ class ServerApi { /// Performs an HTTP 'GET /server/media-types' operation and returns the [Response]. Future getSupportedMediaTypesWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/server/media-types'; + final apiPath = r'/server/media-types'; // ignore: prefer_final_locals Object? postBody; @@ -352,7 +352,7 @@ class ServerApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -380,7 +380,7 @@ class ServerApi { /// Performs an HTTP 'GET /server/theme' operation and returns the [Response]. Future getThemeWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/server/theme'; + final apiPath = r'/server/theme'; // ignore: prefer_final_locals Object? postBody; @@ -393,7 +393,7 @@ class ServerApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -421,7 +421,7 @@ class ServerApi { /// Performs an HTTP 'GET /server/version-history' operation and returns the [Response]. Future getVersionHistoryWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/server/version-history'; + final apiPath = r'/server/version-history'; // ignore: prefer_final_locals Object? postBody; @@ -434,7 +434,7 @@ class ServerApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -465,7 +465,7 @@ class ServerApi { /// Performs an HTTP 'GET /server/ping' operation and returns the [Response]. Future pingServerWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/server/ping'; + final apiPath = r'/server/ping'; // ignore: prefer_final_locals Object? postBody; @@ -478,7 +478,7 @@ class ServerApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -509,7 +509,7 @@ class ServerApi { /// * [LicenseKeyDto] licenseKeyDto (required): Future setServerLicenseWithHttpInfo(LicenseKeyDto licenseKeyDto,) async { // ignore: prefer_const_declarations - final path = r'/server/license'; + final apiPath = r'/server/license'; // ignore: prefer_final_locals Object? postBody = licenseKeyDto; @@ -522,7 +522,7 @@ class ServerApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, diff --git a/mobile/openapi/lib/api/sessions_api.dart b/mobile/openapi/lib/api/sessions_api.dart index fcc6cb836f..203f801b72 100644 --- a/mobile/openapi/lib/api/sessions_api.dart +++ b/mobile/openapi/lib/api/sessions_api.dart @@ -19,7 +19,7 @@ class SessionsApi { /// Performs an HTTP 'DELETE /sessions' operation and returns the [Response]. Future deleteAllSessionsWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/sessions'; + final apiPath = r'/sessions'; // ignore: prefer_final_locals Object? postBody; @@ -32,7 +32,7 @@ class SessionsApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -55,7 +55,7 @@ class SessionsApi { /// * [String] id (required): Future deleteSessionWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/sessions/{id}' + final apiPath = r'/sessions/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -69,7 +69,7 @@ class SessionsApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -92,7 +92,7 @@ class SessionsApi { /// Performs an HTTP 'GET /sessions' operation and returns the [Response]. Future getSessionsWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/sessions'; + final apiPath = r'/sessions'; // ignore: prefer_final_locals Object? postBody; @@ -105,7 +105,7 @@ class SessionsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, diff --git a/mobile/openapi/lib/api/shared_links_api.dart b/mobile/openapi/lib/api/shared_links_api.dart index a6b2978fe2..5bac8988dc 100644 --- a/mobile/openapi/lib/api/shared_links_api.dart +++ b/mobile/openapi/lib/api/shared_links_api.dart @@ -26,7 +26,7 @@ class SharedLinksApi { /// * [String] key: Future addSharedLinkAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto, { String? key, }) async { // ignore: prefer_const_declarations - final path = r'/shared-links/{id}/assets' + final apiPath = r'/shared-links/{id}/assets' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -44,7 +44,7 @@ class SharedLinksApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -85,7 +85,7 @@ class SharedLinksApi { /// * [SharedLinkCreateDto] sharedLinkCreateDto (required): Future createSharedLinkWithHttpInfo(SharedLinkCreateDto sharedLinkCreateDto,) async { // ignore: prefer_const_declarations - final path = r'/shared-links'; + final apiPath = r'/shared-links'; // ignore: prefer_final_locals Object? postBody = sharedLinkCreateDto; @@ -98,7 +98,7 @@ class SharedLinksApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -132,7 +132,7 @@ class SharedLinksApi { /// * [String] albumId: Future getAllSharedLinksWithHttpInfo({ String? albumId, }) async { // ignore: prefer_const_declarations - final path = r'/shared-links'; + final apiPath = r'/shared-links'; // ignore: prefer_final_locals Object? postBody; @@ -149,7 +149,7 @@ class SharedLinksApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -190,7 +190,7 @@ class SharedLinksApi { /// * [String] token: Future getMySharedLinkWithHttpInfo({ String? key, String? password, String? token, }) async { // ignore: prefer_const_declarations - final path = r'/shared-links/me'; + final apiPath = r'/shared-links/me'; // ignore: prefer_final_locals Object? postBody; @@ -213,7 +213,7 @@ class SharedLinksApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -251,7 +251,7 @@ class SharedLinksApi { /// * [String] id (required): Future getSharedLinkByIdWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/shared-links/{id}' + final apiPath = r'/shared-links/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -265,7 +265,7 @@ class SharedLinksApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -299,7 +299,7 @@ class SharedLinksApi { /// * [String] id (required): Future removeSharedLinkWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/shared-links/{id}' + final apiPath = r'/shared-links/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -313,7 +313,7 @@ class SharedLinksApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -343,7 +343,7 @@ class SharedLinksApi { /// * [String] key: Future removeSharedLinkAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto, { String? key, }) async { // ignore: prefer_const_declarations - final path = r'/shared-links/{id}/assets' + final apiPath = r'/shared-links/{id}/assets' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -361,7 +361,7 @@ class SharedLinksApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -404,7 +404,7 @@ class SharedLinksApi { /// * [SharedLinkEditDto] sharedLinkEditDto (required): Future updateSharedLinkWithHttpInfo(String id, SharedLinkEditDto sharedLinkEditDto,) async { // ignore: prefer_const_declarations - final path = r'/shared-links/{id}' + final apiPath = r'/shared-links/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -418,7 +418,7 @@ class SharedLinksApi { return apiClient.invokeAPI( - path, + apiPath, 'PATCH', queryParams, postBody, diff --git a/mobile/openapi/lib/api/stacks_api.dart b/mobile/openapi/lib/api/stacks_api.dart index aa1d9b3416..84f23ec55d 100644 --- a/mobile/openapi/lib/api/stacks_api.dart +++ b/mobile/openapi/lib/api/stacks_api.dart @@ -22,7 +22,7 @@ class StacksApi { /// * [StackCreateDto] stackCreateDto (required): Future createStackWithHttpInfo(StackCreateDto stackCreateDto,) async { // ignore: prefer_const_declarations - final path = r'/stacks'; + final apiPath = r'/stacks'; // ignore: prefer_final_locals Object? postBody = stackCreateDto; @@ -35,7 +35,7 @@ class StacksApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -69,7 +69,7 @@ class StacksApi { /// * [String] id (required): Future deleteStackWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/stacks/{id}' + final apiPath = r'/stacks/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -83,7 +83,7 @@ class StacksApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -109,7 +109,7 @@ class StacksApi { /// * [BulkIdsDto] bulkIdsDto (required): Future deleteStacksWithHttpInfo(BulkIdsDto bulkIdsDto,) async { // ignore: prefer_const_declarations - final path = r'/stacks'; + final apiPath = r'/stacks'; // ignore: prefer_final_locals Object? postBody = bulkIdsDto; @@ -122,7 +122,7 @@ class StacksApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -148,7 +148,7 @@ class StacksApi { /// * [String] id (required): Future getStackWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/stacks/{id}' + final apiPath = r'/stacks/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -162,7 +162,7 @@ class StacksApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -196,7 +196,7 @@ class StacksApi { /// * [String] primaryAssetId: Future searchStacksWithHttpInfo({ String? primaryAssetId, }) async { // ignore: prefer_const_declarations - final path = r'/stacks'; + final apiPath = r'/stacks'; // ignore: prefer_final_locals Object? postBody; @@ -213,7 +213,7 @@ class StacksApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -252,7 +252,7 @@ class StacksApi { /// * [StackUpdateDto] stackUpdateDto (required): Future updateStackWithHttpInfo(String id, StackUpdateDto stackUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/stacks/{id}' + final apiPath = r'/stacks/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -266,7 +266,7 @@ class StacksApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, diff --git a/mobile/openapi/lib/api/sync_api.dart b/mobile/openapi/lib/api/sync_api.dart index 49a4963bff..fe2876ddd8 100644 --- a/mobile/openapi/lib/api/sync_api.dart +++ b/mobile/openapi/lib/api/sync_api.dart @@ -22,7 +22,7 @@ class SyncApi { /// * [SyncAckDeleteDto] syncAckDeleteDto (required): Future deleteSyncAckWithHttpInfo(SyncAckDeleteDto syncAckDeleteDto,) async { // ignore: prefer_const_declarations - final path = r'/sync/ack'; + final apiPath = r'/sync/ack'; // ignore: prefer_final_locals Object? postBody = syncAckDeleteDto; @@ -35,7 +35,7 @@ class SyncApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -61,7 +61,7 @@ class SyncApi { /// * [AssetDeltaSyncDto] assetDeltaSyncDto (required): Future getDeltaSyncWithHttpInfo(AssetDeltaSyncDto assetDeltaSyncDto,) async { // ignore: prefer_const_declarations - final path = r'/sync/delta-sync'; + final apiPath = r'/sync/delta-sync'; // ignore: prefer_final_locals Object? postBody = assetDeltaSyncDto; @@ -74,7 +74,7 @@ class SyncApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -108,7 +108,7 @@ class SyncApi { /// * [AssetFullSyncDto] assetFullSyncDto (required): Future getFullSyncForUserWithHttpInfo(AssetFullSyncDto assetFullSyncDto,) async { // ignore: prefer_const_declarations - final path = r'/sync/full-sync'; + final apiPath = r'/sync/full-sync'; // ignore: prefer_final_locals Object? postBody = assetFullSyncDto; @@ -121,7 +121,7 @@ class SyncApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -155,7 +155,7 @@ class SyncApi { /// Performs an HTTP 'GET /sync/ack' operation and returns the [Response]. Future getSyncAckWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/sync/ack'; + final apiPath = r'/sync/ack'; // ignore: prefer_final_locals Object? postBody; @@ -168,7 +168,7 @@ class SyncApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -202,7 +202,7 @@ class SyncApi { /// * [SyncStreamDto] syncStreamDto (required): Future getSyncStreamWithHttpInfo(SyncStreamDto syncStreamDto,) async { // ignore: prefer_const_declarations - final path = r'/sync/stream'; + final apiPath = r'/sync/stream'; // ignore: prefer_final_locals Object? postBody = syncStreamDto; @@ -215,7 +215,7 @@ class SyncApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -241,7 +241,7 @@ class SyncApi { /// * [SyncAckSetDto] syncAckSetDto (required): Future sendSyncAckWithHttpInfo(SyncAckSetDto syncAckSetDto,) async { // ignore: prefer_const_declarations - final path = r'/sync/ack'; + final apiPath = r'/sync/ack'; // ignore: prefer_final_locals Object? postBody = syncAckSetDto; @@ -254,7 +254,7 @@ class SyncApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, diff --git a/mobile/openapi/lib/api/system_config_api.dart b/mobile/openapi/lib/api/system_config_api.dart index b63b2b70c4..a03b9d3e72 100644 --- a/mobile/openapi/lib/api/system_config_api.dart +++ b/mobile/openapi/lib/api/system_config_api.dart @@ -19,7 +19,7 @@ class SystemConfigApi { /// Performs an HTTP 'GET /system-config' operation and returns the [Response]. Future getConfigWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/system-config'; + final apiPath = r'/system-config'; // ignore: prefer_final_locals Object? postBody; @@ -32,7 +32,7 @@ class SystemConfigApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -60,7 +60,7 @@ class SystemConfigApi { /// Performs an HTTP 'GET /system-config/defaults' operation and returns the [Response]. Future getConfigDefaultsWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/system-config/defaults'; + final apiPath = r'/system-config/defaults'; // ignore: prefer_final_locals Object? postBody; @@ -73,7 +73,7 @@ class SystemConfigApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -101,7 +101,7 @@ class SystemConfigApi { /// Performs an HTTP 'GET /system-config/storage-template-options' operation and returns the [Response]. Future getStorageTemplateOptionsWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/system-config/storage-template-options'; + final apiPath = r'/system-config/storage-template-options'; // ignore: prefer_final_locals Object? postBody; @@ -114,7 +114,7 @@ class SystemConfigApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -145,7 +145,7 @@ class SystemConfigApi { /// * [SystemConfigDto] systemConfigDto (required): Future updateConfigWithHttpInfo(SystemConfigDto systemConfigDto,) async { // ignore: prefer_const_declarations - final path = r'/system-config'; + final apiPath = r'/system-config'; // ignore: prefer_final_locals Object? postBody = systemConfigDto; @@ -158,7 +158,7 @@ class SystemConfigApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, diff --git a/mobile/openapi/lib/api/system_metadata_api.dart b/mobile/openapi/lib/api/system_metadata_api.dart index 822a54b14f..3bd8bddcac 100644 --- a/mobile/openapi/lib/api/system_metadata_api.dart +++ b/mobile/openapi/lib/api/system_metadata_api.dart @@ -19,7 +19,7 @@ class SystemMetadataApi { /// Performs an HTTP 'GET /system-metadata/admin-onboarding' operation and returns the [Response]. Future getAdminOnboardingWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/system-metadata/admin-onboarding'; + final apiPath = r'/system-metadata/admin-onboarding'; // ignore: prefer_final_locals Object? postBody; @@ -32,7 +32,7 @@ class SystemMetadataApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -60,7 +60,7 @@ class SystemMetadataApi { /// Performs an HTTP 'GET /system-metadata/reverse-geocoding-state' operation and returns the [Response]. Future getReverseGeocodingStateWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/system-metadata/reverse-geocoding-state'; + final apiPath = r'/system-metadata/reverse-geocoding-state'; // ignore: prefer_final_locals Object? postBody; @@ -73,7 +73,7 @@ class SystemMetadataApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -104,7 +104,7 @@ class SystemMetadataApi { /// * [AdminOnboardingUpdateDto] adminOnboardingUpdateDto (required): Future updateAdminOnboardingWithHttpInfo(AdminOnboardingUpdateDto adminOnboardingUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/system-metadata/admin-onboarding'; + final apiPath = r'/system-metadata/admin-onboarding'; // ignore: prefer_final_locals Object? postBody = adminOnboardingUpdateDto; @@ -117,7 +117,7 @@ class SystemMetadataApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, diff --git a/mobile/openapi/lib/api/tags_api.dart b/mobile/openapi/lib/api/tags_api.dart index 87c9001a3c..f6cfc8720b 100644 --- a/mobile/openapi/lib/api/tags_api.dart +++ b/mobile/openapi/lib/api/tags_api.dart @@ -22,7 +22,7 @@ class TagsApi { /// * [TagBulkAssetsDto] tagBulkAssetsDto (required): Future bulkTagAssetsWithHttpInfo(TagBulkAssetsDto tagBulkAssetsDto,) async { // ignore: prefer_const_declarations - final path = r'/tags/assets'; + final apiPath = r'/tags/assets'; // ignore: prefer_final_locals Object? postBody = tagBulkAssetsDto; @@ -35,7 +35,7 @@ class TagsApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -69,7 +69,7 @@ class TagsApi { /// * [TagCreateDto] tagCreateDto (required): Future createTagWithHttpInfo(TagCreateDto tagCreateDto,) async { // ignore: prefer_const_declarations - final path = r'/tags'; + final apiPath = r'/tags'; // ignore: prefer_final_locals Object? postBody = tagCreateDto; @@ -82,7 +82,7 @@ class TagsApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -116,7 +116,7 @@ class TagsApi { /// * [String] id (required): Future deleteTagWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/tags/{id}' + final apiPath = r'/tags/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -130,7 +130,7 @@ class TagsApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -153,7 +153,7 @@ class TagsApi { /// Performs an HTTP 'GET /tags' operation and returns the [Response]. Future getAllTagsWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/tags'; + final apiPath = r'/tags'; // ignore: prefer_final_locals Object? postBody; @@ -166,7 +166,7 @@ class TagsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -200,7 +200,7 @@ class TagsApi { /// * [String] id (required): Future getTagByIdWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/tags/{id}' + final apiPath = r'/tags/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -214,7 +214,7 @@ class TagsApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -250,7 +250,7 @@ class TagsApi { /// * [BulkIdsDto] bulkIdsDto (required): Future tagAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { // ignore: prefer_const_declarations - final path = r'/tags/{id}/assets' + final apiPath = r'/tags/{id}/assets' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -264,7 +264,7 @@ class TagsApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -305,7 +305,7 @@ class TagsApi { /// * [BulkIdsDto] bulkIdsDto (required): Future untagAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { // ignore: prefer_const_declarations - final path = r'/tags/{id}/assets' + final apiPath = r'/tags/{id}/assets' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -319,7 +319,7 @@ class TagsApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -360,7 +360,7 @@ class TagsApi { /// * [TagUpdateDto] tagUpdateDto (required): Future updateTagWithHttpInfo(String id, TagUpdateDto tagUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/tags/{id}' + final apiPath = r'/tags/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -374,7 +374,7 @@ class TagsApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -410,7 +410,7 @@ class TagsApi { /// * [TagUpsertDto] tagUpsertDto (required): Future upsertTagsWithHttpInfo(TagUpsertDto tagUpsertDto,) async { // ignore: prefer_const_declarations - final path = r'/tags'; + final apiPath = r'/tags'; // ignore: prefer_final_locals Object? postBody = tagUpsertDto; @@ -423,7 +423,7 @@ class TagsApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, diff --git a/mobile/openapi/lib/api/timeline_api.dart b/mobile/openapi/lib/api/timeline_api.dart index 8c94e09bf5..9a8863f004 100644 --- a/mobile/openapi/lib/api/timeline_api.dart +++ b/mobile/openapi/lib/api/timeline_api.dart @@ -16,7 +16,7 @@ class TimelineApi { final ApiClient apiClient; - /// Performs an HTTP 'GET /timeline/bucket' operation and returns the [Response]. + /// Performs an HTTP 'GET /timeline/liteBucket' operation and returns the [Response]. /// Parameters: /// /// * [TimeBucketSize] size (required): @@ -44,9 +44,9 @@ class TimelineApi { /// * [bool] withPartners: /// /// * [bool] withStacked: - Future getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async { + Future getLiteTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async { // ignore: prefer_const_declarations - final path = r'/timeline/bucket'; + final apiPath = r'/timeline/liteBucket'; // ignore: prefer_final_locals Object? postBody; @@ -95,7 +95,138 @@ class TimelineApi { return apiClient.invokeAPI( - path, + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [TimeBucketSize] size (required): + /// + /// * [String] timeBucket (required): + /// + /// * [String] albumId: + /// + /// * [bool] isArchived: + /// + /// * [bool] isFavorite: + /// + /// * [bool] isTrashed: + /// + /// * [String] key: + /// + /// * [AssetOrder] order: + /// + /// * [String] personId: + /// + /// * [String] tagId: + /// + /// * [String] userId: + /// + /// * [bool] withPartners: + /// + /// * [bool] withStacked: + Future getLiteTimeBucket(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async { + final response = await getLiteTimeBucketWithHttpInfo(size, timeBucket, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, withPartners: withPartners, withStacked: withStacked, ); + 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), 'LiteTimeBucketResponseDto',) as LiteTimeBucketResponseDto; + + } + return null; + } + + /// Performs an HTTP 'GET /timeline/bucket' operation and returns the [Response]. + /// Parameters: + /// + /// * [TimeBucketSize] size (required): + /// + /// * [String] timeBucket (required): + /// + /// * [String] albumId: + /// + /// * [bool] isArchived: + /// + /// * [bool] isFavorite: + /// + /// * [bool] isTrashed: + /// + /// * [String] key: + /// + /// * [AssetOrder] order: + /// + /// * [String] personId: + /// + /// * [String] tagId: + /// + /// * [String] userId: + /// + /// * [bool] withPartners: + /// + /// * [bool] withStacked: + Future getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/timeline/bucket'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (albumId != null) { + queryParams.addAll(_queryParams('', 'albumId', albumId)); + } + if (isArchived != null) { + queryParams.addAll(_queryParams('', 'isArchived', isArchived)); + } + if (isFavorite != null) { + queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); + } + if (isTrashed != null) { + queryParams.addAll(_queryParams('', 'isTrashed', isTrashed)); + } + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (order != null) { + queryParams.addAll(_queryParams('', 'order', order)); + } + if (personId != null) { + queryParams.addAll(_queryParams('', 'personId', personId)); + } + queryParams.addAll(_queryParams('', 'size', size)); + if (tagId != null) { + queryParams.addAll(_queryParams('', 'tagId', tagId)); + } + queryParams.addAll(_queryParams('', 'timeBucket', timeBucket)); + if (userId != null) { + queryParams.addAll(_queryParams('', 'userId', userId)); + } + if (withPartners != null) { + queryParams.addAll(_queryParams('', 'withPartners', withPartners)); + } + if (withStacked != null) { + queryParams.addAll(_queryParams('', 'withStacked', withStacked)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, 'GET', queryParams, postBody, @@ -178,7 +309,7 @@ class TimelineApi { /// * [bool] withStacked: Future getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async { // ignore: prefer_const_declarations - final path = r'/timeline/buckets'; + final apiPath = r'/timeline/buckets'; // ignore: prefer_final_locals Object? postBody; @@ -226,7 +357,7 @@ class TimelineApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, diff --git a/mobile/openapi/lib/api/trash_api.dart b/mobile/openapi/lib/api/trash_api.dart index 8f8c6ffb3a..982dbcbeda 100644 --- a/mobile/openapi/lib/api/trash_api.dart +++ b/mobile/openapi/lib/api/trash_api.dart @@ -19,7 +19,7 @@ class TrashApi { /// Performs an HTTP 'POST /trash/empty' operation and returns the [Response]. Future emptyTrashWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/trash/empty'; + final apiPath = r'/trash/empty'; // ignore: prefer_final_locals Object? postBody; @@ -32,7 +32,7 @@ class TrashApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -63,7 +63,7 @@ class TrashApi { /// * [BulkIdsDto] bulkIdsDto (required): Future restoreAssetsWithHttpInfo(BulkIdsDto bulkIdsDto,) async { // ignore: prefer_const_declarations - final path = r'/trash/restore/assets'; + final apiPath = r'/trash/restore/assets'; // ignore: prefer_final_locals Object? postBody = bulkIdsDto; @@ -76,7 +76,7 @@ class TrashApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -107,7 +107,7 @@ class TrashApi { /// Performs an HTTP 'POST /trash/restore' operation and returns the [Response]. Future restoreTrashWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/trash/restore'; + final apiPath = r'/trash/restore'; // ignore: prefer_final_locals Object? postBody; @@ -120,7 +120,7 @@ class TrashApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, diff --git a/mobile/openapi/lib/api/users_admin_api.dart b/mobile/openapi/lib/api/users_admin_api.dart index a074645e08..b4508d7dcd 100644 --- a/mobile/openapi/lib/api/users_admin_api.dart +++ b/mobile/openapi/lib/api/users_admin_api.dart @@ -22,7 +22,7 @@ class UsersAdminApi { /// * [UserAdminCreateDto] userAdminCreateDto (required): Future createUserAdminWithHttpInfo(UserAdminCreateDto userAdminCreateDto,) async { // ignore: prefer_const_declarations - final path = r'/admin/users'; + final apiPath = r'/admin/users'; // ignore: prefer_final_locals Object? postBody = userAdminCreateDto; @@ -35,7 +35,7 @@ class UsersAdminApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -71,7 +71,7 @@ class UsersAdminApi { /// * [UserAdminDeleteDto] userAdminDeleteDto (required): Future deleteUserAdminWithHttpInfo(String id, UserAdminDeleteDto userAdminDeleteDto,) async { // ignore: prefer_const_declarations - final path = r'/admin/users/{id}' + final apiPath = r'/admin/users/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -85,7 +85,7 @@ class UsersAdminApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -121,7 +121,7 @@ class UsersAdminApi { /// * [String] id (required): Future getUserAdminWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/admin/users/{id}' + final apiPath = r'/admin/users/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -135,7 +135,7 @@ class UsersAdminApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -169,7 +169,7 @@ class UsersAdminApi { /// * [String] id (required): Future getUserPreferencesAdminWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/admin/users/{id}/preferences' + final apiPath = r'/admin/users/{id}/preferences' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -183,7 +183,7 @@ class UsersAdminApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -217,7 +217,7 @@ class UsersAdminApi { /// * [String] id (required): Future restoreUserAdminWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/admin/users/{id}/restore' + final apiPath = r'/admin/users/{id}/restore' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -231,7 +231,7 @@ class UsersAdminApi { return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -265,7 +265,7 @@ class UsersAdminApi { /// * [bool] withDeleted: Future searchUsersAdminWithHttpInfo({ bool? withDeleted, }) async { // ignore: prefer_const_declarations - final path = r'/admin/users'; + final apiPath = r'/admin/users'; // ignore: prefer_final_locals Object? postBody; @@ -282,7 +282,7 @@ class UsersAdminApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -321,7 +321,7 @@ class UsersAdminApi { /// * [UserAdminUpdateDto] userAdminUpdateDto (required): Future updateUserAdminWithHttpInfo(String id, UserAdminUpdateDto userAdminUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/admin/users/{id}' + final apiPath = r'/admin/users/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -335,7 +335,7 @@ class UsersAdminApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -373,7 +373,7 @@ class UsersAdminApi { /// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required): Future updateUserPreferencesAdminWithHttpInfo(String id, UserPreferencesUpdateDto userPreferencesUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/admin/users/{id}/preferences' + final apiPath = r'/admin/users/{id}/preferences' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -387,7 +387,7 @@ class UsersAdminApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, diff --git a/mobile/openapi/lib/api/users_api.dart b/mobile/openapi/lib/api/users_api.dart index b2b9fa8826..a48ec54cfe 100644 --- a/mobile/openapi/lib/api/users_api.dart +++ b/mobile/openapi/lib/api/users_api.dart @@ -22,7 +22,7 @@ class UsersApi { /// * [MultipartFile] file (required): Future createProfileImageWithHttpInfo(MultipartFile file,) async { // ignore: prefer_const_declarations - final path = r'/users/profile-image'; + final apiPath = r'/users/profile-image'; // ignore: prefer_final_locals Object? postBody; @@ -34,7 +34,7 @@ class UsersApi { const contentTypes = ['multipart/form-data']; bool hasFields = false; - final mp = MultipartRequest('POST', Uri.parse(path)); + final mp = MultipartRequest('POST', Uri.parse(apiPath)); if (file != null) { hasFields = true; mp.fields[r'file'] = file.field; @@ -45,7 +45,7 @@ class UsersApi { } return apiClient.invokeAPI( - path, + apiPath, 'POST', queryParams, postBody, @@ -76,7 +76,7 @@ class UsersApi { /// Performs an HTTP 'DELETE /users/profile-image' operation and returns the [Response]. Future deleteProfileImageWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/users/profile-image'; + final apiPath = r'/users/profile-image'; // ignore: prefer_final_locals Object? postBody; @@ -89,7 +89,7 @@ class UsersApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -109,7 +109,7 @@ class UsersApi { /// Performs an HTTP 'DELETE /users/me/license' operation and returns the [Response]. Future deleteUserLicenseWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/users/me/license'; + final apiPath = r'/users/me/license'; // ignore: prefer_final_locals Object? postBody; @@ -122,7 +122,7 @@ class UsersApi { return apiClient.invokeAPI( - path, + apiPath, 'DELETE', queryParams, postBody, @@ -142,7 +142,7 @@ class UsersApi { /// Performs an HTTP 'GET /users/me/preferences' operation and returns the [Response]. Future getMyPreferencesWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/users/me/preferences'; + final apiPath = r'/users/me/preferences'; // ignore: prefer_final_locals Object? postBody; @@ -155,7 +155,7 @@ class UsersApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -183,7 +183,7 @@ class UsersApi { /// Performs an HTTP 'GET /users/me' operation and returns the [Response]. Future getMyUserWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/users/me'; + final apiPath = r'/users/me'; // ignore: prefer_final_locals Object? postBody; @@ -196,7 +196,7 @@ class UsersApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -227,7 +227,7 @@ class UsersApi { /// * [String] id (required): Future getProfileImageWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/users/{id}/profile-image' + final apiPath = r'/users/{id}/profile-image' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -241,7 +241,7 @@ class UsersApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -275,7 +275,7 @@ class UsersApi { /// * [String] id (required): Future getUserWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/users/{id}' + final apiPath = r'/users/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -289,7 +289,7 @@ class UsersApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -320,7 +320,7 @@ class UsersApi { /// Performs an HTTP 'GET /users/me/license' operation and returns the [Response]. Future getUserLicenseWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/users/me/license'; + final apiPath = r'/users/me/license'; // ignore: prefer_final_locals Object? postBody; @@ -333,7 +333,7 @@ class UsersApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -361,7 +361,7 @@ class UsersApi { /// Performs an HTTP 'GET /users' operation and returns the [Response]. Future searchUsersWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/users'; + final apiPath = r'/users'; // ignore: prefer_final_locals Object? postBody; @@ -374,7 +374,7 @@ class UsersApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -408,7 +408,7 @@ class UsersApi { /// * [LicenseKeyDto] licenseKeyDto (required): Future setUserLicenseWithHttpInfo(LicenseKeyDto licenseKeyDto,) async { // ignore: prefer_const_declarations - final path = r'/users/me/license'; + final apiPath = r'/users/me/license'; // ignore: prefer_final_locals Object? postBody = licenseKeyDto; @@ -421,7 +421,7 @@ class UsersApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -455,7 +455,7 @@ class UsersApi { /// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required): Future updateMyPreferencesWithHttpInfo(UserPreferencesUpdateDto userPreferencesUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/users/me/preferences'; + final apiPath = r'/users/me/preferences'; // ignore: prefer_final_locals Object? postBody = userPreferencesUpdateDto; @@ -468,7 +468,7 @@ class UsersApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, @@ -502,7 +502,7 @@ class UsersApi { /// * [UserUpdateMeDto] userUpdateMeDto (required): Future updateMyUserWithHttpInfo(UserUpdateMeDto userUpdateMeDto,) async { // ignore: prefer_const_declarations - final path = r'/users/me'; + final apiPath = r'/users/me'; // ignore: prefer_final_locals Object? postBody = userUpdateMeDto; @@ -515,7 +515,7 @@ class UsersApi { return apiClient.invokeAPI( - path, + apiPath, 'PUT', queryParams, postBody, diff --git a/mobile/openapi/lib/api/view_api.dart b/mobile/openapi/lib/api/view_api.dart index f4489f2d1a..1fcaec759c 100644 --- a/mobile/openapi/lib/api/view_api.dart +++ b/mobile/openapi/lib/api/view_api.dart @@ -22,7 +22,7 @@ class ViewApi { /// * [String] path (required): Future getAssetsByOriginalPathWithHttpInfo(String path,) async { // ignore: prefer_const_declarations - final path = r'/view/folder'; + final apiPath = r'/view/folder'; // ignore: prefer_final_locals Object? postBody; @@ -37,7 +37,7 @@ class ViewApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, @@ -71,7 +71,7 @@ class ViewApi { /// Performs an HTTP 'GET /view/folder/unique-paths' operation and returns the [Response]. Future getUniqueOriginalPathsWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/view/folder/unique-paths'; + final apiPath = r'/view/folder/unique-paths'; // ignore: prefer_final_locals Object? postBody; @@ -84,7 +84,7 @@ class ViewApi { return apiClient.invokeAPI( - path, + apiPath, 'GET', queryParams, postBody, diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 4d837ccb9d..ba15ae3105 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -360,6 +360,10 @@ class ApiClient { return LicenseKeyDto.fromJson(value); case 'LicenseResponseDto': return LicenseResponseDto.fromJson(value); + case 'LiteTimeBucketAssetResponseDto': + return LiteTimeBucketAssetResponseDto.fromJson(value); + case 'LiteTimeBucketResponseDto': + return LiteTimeBucketResponseDto.fromJson(value); case 'LogLevel': return LogLevelTypeTransformer().decode(value); case 'LoginCredentialDto': @@ -518,6 +522,12 @@ class ApiClient { return SyncAckDto.fromJson(value); case 'SyncAckSetDto': return SyncAckSetDto.fromJson(value); + case 'SyncAssetDeleteV1': + return SyncAssetDeleteV1.fromJson(value); + case 'SyncAssetExifV1': + return SyncAssetExifV1.fromJson(value); + case 'SyncAssetV1': + return SyncAssetV1.fromJson(value); case 'SyncEntityType': return SyncEntityTypeTypeTransformer().decode(value); case 'SyncPartnerDeleteV1': diff --git a/mobile/openapi/lib/model/lite_time_bucket_asset_response_dto.dart b/mobile/openapi/lib/model/lite_time_bucket_asset_response_dto.dart new file mode 100644 index 0000000000..ac3aacb0aa --- /dev/null +++ b/mobile/openapi/lib/model/lite_time_bucket_asset_response_dto.dart @@ -0,0 +1,158 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class LiteTimeBucketAssetResponseDto { + /// Returns a new [LiteTimeBucketAssetResponseDto] instance. + LiteTimeBucketAssetResponseDto({ + required this.height, + required this.id, + required this.isArchived, + required this.isFavorite, + required this.isTrashed, + required this.localDateTime, + this.thumbhash, + required this.width, + }); + + int height; + + String id; + + bool isArchived; + + bool isFavorite; + + bool isTrashed; + + DateTime localDateTime; + + String? thumbhash; + + int width; + + @override + bool operator ==(Object other) => identical(this, other) || other is LiteTimeBucketAssetResponseDto && + other.height == height && + other.id == id && + other.isArchived == isArchived && + other.isFavorite == isFavorite && + other.isTrashed == isTrashed && + other.localDateTime == localDateTime && + other.thumbhash == thumbhash && + other.width == width; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (height.hashCode) + + (id.hashCode) + + (isArchived.hashCode) + + (isFavorite.hashCode) + + (isTrashed.hashCode) + + (localDateTime.hashCode) + + (thumbhash == null ? 0 : thumbhash!.hashCode) + + (width.hashCode); + + @override + String toString() => 'LiteTimeBucketAssetResponseDto[height=$height, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isTrashed=$isTrashed, localDateTime=$localDateTime, thumbhash=$thumbhash, width=$width]'; + + Map toJson() { + final json = {}; + json[r'height'] = this.height; + json[r'id'] = this.id; + json[r'isArchived'] = this.isArchived; + json[r'isFavorite'] = this.isFavorite; + json[r'isTrashed'] = this.isTrashed; + json[r'localDateTime'] = this.localDateTime.toUtc().toIso8601String(); + if (this.thumbhash != null) { + json[r'thumbhash'] = this.thumbhash; + } else { + // json[r'thumbhash'] = null; + } + json[r'width'] = this.width; + return json; + } + + /// Returns a new [LiteTimeBucketAssetResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static LiteTimeBucketAssetResponseDto? fromJson(dynamic value) { + upgradeDto(value, "LiteTimeBucketAssetResponseDto"); + if (value is Map) { + final json = value.cast(); + + return LiteTimeBucketAssetResponseDto( + height: mapValueOfType(json, r'height')!, + id: mapValueOfType(json, r'id')!, + isArchived: mapValueOfType(json, r'isArchived')!, + isFavorite: mapValueOfType(json, r'isFavorite')!, + isTrashed: mapValueOfType(json, r'isTrashed')!, + localDateTime: mapDateTime(json, r'localDateTime', r'')!, + thumbhash: mapValueOfType(json, r'thumbhash'), + width: mapValueOfType(json, r'width')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = LiteTimeBucketAssetResponseDto.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 = LiteTimeBucketAssetResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of LiteTimeBucketAssetResponseDto-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] = LiteTimeBucketAssetResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'height', + 'id', + 'isArchived', + 'isFavorite', + 'isTrashed', + 'localDateTime', + 'width', + }; +} + diff --git a/mobile/openapi/lib/model/lite_time_bucket_response_dto.dart b/mobile/openapi/lib/model/lite_time_bucket_response_dto.dart new file mode 100644 index 0000000000..e83717a80f --- /dev/null +++ b/mobile/openapi/lib/model/lite_time_bucket_response_dto.dart @@ -0,0 +1,107 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class LiteTimeBucketResponseDto { + /// Returns a new [LiteTimeBucketResponseDto] instance. + LiteTimeBucketResponseDto({ + required this.hasNextPage, + this.liteAssets = const [], + }); + + bool hasNextPage; + + List liteAssets; + + @override + bool operator ==(Object other) => identical(this, other) || other is LiteTimeBucketResponseDto && + other.hasNextPage == hasNextPage && + _deepEquality.equals(other.liteAssets, liteAssets); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (hasNextPage.hashCode) + + (liteAssets.hashCode); + + @override + String toString() => 'LiteTimeBucketResponseDto[hasNextPage=$hasNextPage, liteAssets=$liteAssets]'; + + Map toJson() { + final json = {}; + json[r'hasNextPage'] = this.hasNextPage; + json[r'liteAssets'] = this.liteAssets; + return json; + } + + /// Returns a new [LiteTimeBucketResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static LiteTimeBucketResponseDto? fromJson(dynamic value) { + upgradeDto(value, "LiteTimeBucketResponseDto"); + if (value is Map) { + final json = value.cast(); + + return LiteTimeBucketResponseDto( + hasNextPage: mapValueOfType(json, r'hasNextPage')!, + liteAssets: LiteTimeBucketAssetResponseDto.listFromJson(json[r'liteAssets']), + ); + } + 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 = LiteTimeBucketResponseDto.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 = LiteTimeBucketResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of LiteTimeBucketResponseDto-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] = LiteTimeBucketResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'hasNextPage', + 'liteAssets', + }; +} + diff --git a/mobile/openapi/lib/model/manual_job_name.dart b/mobile/openapi/lib/model/manual_job_name.dart index 71c60d8e64..311215ad9e 100644 --- a/mobile/openapi/lib/model/manual_job_name.dart +++ b/mobile/openapi/lib/model/manual_job_name.dart @@ -28,6 +28,7 @@ class ManualJobName { static const userCleanup = ManualJobName._(r'user-cleanup'); static const memoryCleanup = ManualJobName._(r'memory-cleanup'); static const memoryCreate = ManualJobName._(r'memory-create'); + static const backupDatabase = ManualJobName._(r'backup-database'); /// List of all possible values in this [enum][ManualJobName]. static const values = [ @@ -36,6 +37,7 @@ class ManualJobName { userCleanup, memoryCleanup, memoryCreate, + backupDatabase, ]; static ManualJobName? fromJson(dynamic value) => ManualJobNameTypeTransformer().decode(value); @@ -79,6 +81,7 @@ class ManualJobNameTypeTransformer { case r'user-cleanup': return ManualJobName.userCleanup; case r'memory-cleanup': return ManualJobName.memoryCleanup; case r'memory-create': return ManualJobName.memoryCreate; + case r'backup-database': return ManualJobName.backupDatabase; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); diff --git a/mobile/openapi/lib/model/sync_asset_delete_v1.dart b/mobile/openapi/lib/model/sync_asset_delete_v1.dart new file mode 100644 index 0000000000..c1787caf04 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_delete_v1.dart @@ -0,0 +1,99 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetDeleteV1 { + /// Returns a new [SyncAssetDeleteV1] instance. + SyncAssetDeleteV1({ + required this.assetId, + }); + + String assetId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetDeleteV1 && + other.assetId == assetId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode); + + @override + String toString() => 'SyncAssetDeleteV1[assetId=$assetId]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + return json; + } + + /// Returns a new [SyncAssetDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetDeleteV1( + assetId: mapValueOfType(json, r'assetId')!, + ); + } + 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 = SyncAssetDeleteV1.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 = SyncAssetDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetDeleteV1-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] = SyncAssetDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_exif_v1.dart b/mobile/openapi/lib/model/sync_asset_exif_v1.dart new file mode 100644 index 0000000000..b0fef28b76 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_exif_v1.dart @@ -0,0 +1,387 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetExifV1 { + /// Returns a new [SyncAssetExifV1] instance. + SyncAssetExifV1({ + required this.assetId, + required this.city, + required this.country, + required this.dateTimeOriginal, + required this.description, + required this.exifImageHeight, + required this.exifImageWidth, + required this.exposureTime, + required this.fNumber, + required this.fileSizeInByte, + required this.focalLength, + required this.fps, + required this.iso, + required this.latitude, + required this.lensModel, + required this.longitude, + required this.make, + required this.model, + required this.modifyDate, + required this.orientation, + required this.profileDescription, + required this.projectionType, + required this.rating, + required this.state, + required this.timeZone, + }); + + String assetId; + + String? city; + + String? country; + + DateTime? dateTimeOriginal; + + String? description; + + int? exifImageHeight; + + int? exifImageWidth; + + String? exposureTime; + + int? fNumber; + + int? fileSizeInByte; + + int? focalLength; + + int? fps; + + int? iso; + + int? latitude; + + String? lensModel; + + int? longitude; + + String? make; + + String? model; + + DateTime? modifyDate; + + String? orientation; + + String? profileDescription; + + String? projectionType; + + int? rating; + + String? state; + + String? timeZone; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetExifV1 && + other.assetId == assetId && + other.city == city && + other.country == country && + other.dateTimeOriginal == dateTimeOriginal && + other.description == description && + other.exifImageHeight == exifImageHeight && + other.exifImageWidth == exifImageWidth && + other.exposureTime == exposureTime && + other.fNumber == fNumber && + other.fileSizeInByte == fileSizeInByte && + other.focalLength == focalLength && + other.fps == fps && + other.iso == iso && + other.latitude == latitude && + other.lensModel == lensModel && + other.longitude == longitude && + other.make == make && + other.model == model && + other.modifyDate == modifyDate && + other.orientation == orientation && + other.profileDescription == profileDescription && + other.projectionType == projectionType && + other.rating == rating && + other.state == state && + other.timeZone == timeZone; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (city == null ? 0 : city!.hashCode) + + (country == null ? 0 : country!.hashCode) + + (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + + (description == null ? 0 : description!.hashCode) + + (exifImageHeight == null ? 0 : exifImageHeight!.hashCode) + + (exifImageWidth == null ? 0 : exifImageWidth!.hashCode) + + (exposureTime == null ? 0 : exposureTime!.hashCode) + + (fNumber == null ? 0 : fNumber!.hashCode) + + (fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) + + (focalLength == null ? 0 : focalLength!.hashCode) + + (fps == null ? 0 : fps!.hashCode) + + (iso == null ? 0 : iso!.hashCode) + + (latitude == null ? 0 : latitude!.hashCode) + + (lensModel == null ? 0 : lensModel!.hashCode) + + (longitude == null ? 0 : longitude!.hashCode) + + (make == null ? 0 : make!.hashCode) + + (model == null ? 0 : model!.hashCode) + + (modifyDate == null ? 0 : modifyDate!.hashCode) + + (orientation == null ? 0 : orientation!.hashCode) + + (profileDescription == null ? 0 : profileDescription!.hashCode) + + (projectionType == null ? 0 : projectionType!.hashCode) + + (rating == null ? 0 : rating!.hashCode) + + (state == null ? 0 : state!.hashCode) + + (timeZone == null ? 0 : timeZone!.hashCode); + + @override + String toString() => 'SyncAssetExifV1[assetId=$assetId, city=$city, country=$country, dateTimeOriginal=$dateTimeOriginal, description=$description, exifImageHeight=$exifImageHeight, exifImageWidth=$exifImageWidth, exposureTime=$exposureTime, fNumber=$fNumber, fileSizeInByte=$fileSizeInByte, focalLength=$focalLength, fps=$fps, iso=$iso, latitude=$latitude, lensModel=$lensModel, longitude=$longitude, make=$make, model=$model, modifyDate=$modifyDate, orientation=$orientation, profileDescription=$profileDescription, projectionType=$projectionType, rating=$rating, state=$state, timeZone=$timeZone]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + if (this.city != null) { + json[r'city'] = this.city; + } else { + // json[r'city'] = null; + } + if (this.country != null) { + json[r'country'] = this.country; + } else { + // json[r'country'] = null; + } + if (this.dateTimeOriginal != null) { + json[r'dateTimeOriginal'] = this.dateTimeOriginal!.toUtc().toIso8601String(); + } else { + // json[r'dateTimeOriginal'] = null; + } + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.exifImageHeight != null) { + json[r'exifImageHeight'] = this.exifImageHeight; + } else { + // json[r'exifImageHeight'] = null; + } + if (this.exifImageWidth != null) { + json[r'exifImageWidth'] = this.exifImageWidth; + } else { + // json[r'exifImageWidth'] = null; + } + if (this.exposureTime != null) { + json[r'exposureTime'] = this.exposureTime; + } else { + // json[r'exposureTime'] = null; + } + if (this.fNumber != null) { + json[r'fNumber'] = this.fNumber; + } else { + // json[r'fNumber'] = null; + } + if (this.fileSizeInByte != null) { + json[r'fileSizeInByte'] = this.fileSizeInByte; + } else { + // json[r'fileSizeInByte'] = null; + } + if (this.focalLength != null) { + json[r'focalLength'] = this.focalLength; + } else { + // json[r'focalLength'] = null; + } + if (this.fps != null) { + json[r'fps'] = this.fps; + } else { + // json[r'fps'] = null; + } + if (this.iso != null) { + json[r'iso'] = this.iso; + } else { + // json[r'iso'] = null; + } + if (this.latitude != null) { + json[r'latitude'] = this.latitude; + } else { + // json[r'latitude'] = null; + } + if (this.lensModel != null) { + json[r'lensModel'] = this.lensModel; + } else { + // json[r'lensModel'] = null; + } + if (this.longitude != null) { + json[r'longitude'] = this.longitude; + } else { + // json[r'longitude'] = null; + } + if (this.make != null) { + json[r'make'] = this.make; + } else { + // json[r'make'] = null; + } + if (this.model != null) { + json[r'model'] = this.model; + } else { + // json[r'model'] = null; + } + if (this.modifyDate != null) { + json[r'modifyDate'] = this.modifyDate!.toUtc().toIso8601String(); + } else { + // json[r'modifyDate'] = null; + } + if (this.orientation != null) { + json[r'orientation'] = this.orientation; + } else { + // json[r'orientation'] = null; + } + if (this.profileDescription != null) { + json[r'profileDescription'] = this.profileDescription; + } else { + // json[r'profileDescription'] = null; + } + if (this.projectionType != null) { + json[r'projectionType'] = this.projectionType; + } else { + // json[r'projectionType'] = null; + } + if (this.rating != null) { + json[r'rating'] = this.rating; + } else { + // json[r'rating'] = null; + } + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + if (this.timeZone != null) { + json[r'timeZone'] = this.timeZone; + } else { + // json[r'timeZone'] = null; + } + return json; + } + + /// Returns a new [SyncAssetExifV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetExifV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetExifV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetExifV1( + assetId: mapValueOfType(json, r'assetId')!, + city: mapValueOfType(json, r'city'), + country: mapValueOfType(json, r'country'), + dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', r''), + description: mapValueOfType(json, r'description'), + exifImageHeight: mapValueOfType(json, r'exifImageHeight'), + exifImageWidth: mapValueOfType(json, r'exifImageWidth'), + exposureTime: mapValueOfType(json, r'exposureTime'), + fNumber: mapValueOfType(json, r'fNumber'), + fileSizeInByte: mapValueOfType(json, r'fileSizeInByte'), + focalLength: mapValueOfType(json, r'focalLength'), + fps: mapValueOfType(json, r'fps'), + iso: mapValueOfType(json, r'iso'), + latitude: mapValueOfType(json, r'latitude'), + lensModel: mapValueOfType(json, r'lensModel'), + longitude: mapValueOfType(json, r'longitude'), + make: mapValueOfType(json, r'make'), + model: mapValueOfType(json, r'model'), + modifyDate: mapDateTime(json, r'modifyDate', r''), + orientation: mapValueOfType(json, r'orientation'), + profileDescription: mapValueOfType(json, r'profileDescription'), + projectionType: mapValueOfType(json, r'projectionType'), + rating: mapValueOfType(json, r'rating'), + state: mapValueOfType(json, r'state'), + timeZone: mapValueOfType(json, r'timeZone'), + ); + } + 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 = SyncAssetExifV1.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 = SyncAssetExifV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetExifV1-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] = SyncAssetExifV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'city', + 'country', + 'dateTimeOriginal', + 'description', + 'exifImageHeight', + 'exifImageWidth', + 'exposureTime', + 'fNumber', + 'fileSizeInByte', + 'focalLength', + 'fps', + 'iso', + 'latitude', + 'lensModel', + 'longitude', + 'make', + 'model', + 'modifyDate', + 'orientation', + 'profileDescription', + 'projectionType', + 'rating', + 'state', + 'timeZone', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_v1.dart b/mobile/openapi/lib/model/sync_asset_v1.dart new file mode 100644 index 0000000000..6f9d7d7eaf --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_v1.dart @@ -0,0 +1,279 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetV1 { + /// Returns a new [SyncAssetV1] instance. + SyncAssetV1({ + required this.checksum, + required this.deletedAt, + required this.fileCreatedAt, + required this.fileModifiedAt, + required this.id, + required this.isFavorite, + required this.isVisible, + required this.localDateTime, + required this.ownerId, + required this.thumbhash, + required this.type, + }); + + String checksum; + + DateTime? deletedAt; + + DateTime? fileCreatedAt; + + DateTime? fileModifiedAt; + + String id; + + bool isFavorite; + + bool isVisible; + + DateTime? localDateTime; + + String ownerId; + + String? thumbhash; + + SyncAssetV1TypeEnum type; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetV1 && + other.checksum == checksum && + other.deletedAt == deletedAt && + other.fileCreatedAt == fileCreatedAt && + other.fileModifiedAt == fileModifiedAt && + other.id == id && + other.isFavorite == isFavorite && + other.isVisible == isVisible && + other.localDateTime == localDateTime && + other.ownerId == ownerId && + other.thumbhash == thumbhash && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (checksum.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (fileCreatedAt == null ? 0 : fileCreatedAt!.hashCode) + + (fileModifiedAt == null ? 0 : fileModifiedAt!.hashCode) + + (id.hashCode) + + (isFavorite.hashCode) + + (isVisible.hashCode) + + (localDateTime == null ? 0 : localDateTime!.hashCode) + + (ownerId.hashCode) + + (thumbhash == null ? 0 : thumbhash!.hashCode) + + (type.hashCode); + + @override + String toString() => 'SyncAssetV1[checksum=$checksum, deletedAt=$deletedAt, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, id=$id, isFavorite=$isFavorite, isVisible=$isVisible, localDateTime=$localDateTime, ownerId=$ownerId, thumbhash=$thumbhash, type=$type]'; + + Map toJson() { + final json = {}; + json[r'checksum'] = this.checksum; + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); + } else { + // json[r'deletedAt'] = null; + } + if (this.fileCreatedAt != null) { + json[r'fileCreatedAt'] = this.fileCreatedAt!.toUtc().toIso8601String(); + } else { + // json[r'fileCreatedAt'] = null; + } + if (this.fileModifiedAt != null) { + json[r'fileModifiedAt'] = this.fileModifiedAt!.toUtc().toIso8601String(); + } else { + // json[r'fileModifiedAt'] = null; + } + json[r'id'] = this.id; + json[r'isFavorite'] = this.isFavorite; + json[r'isVisible'] = this.isVisible; + if (this.localDateTime != null) { + json[r'localDateTime'] = this.localDateTime!.toUtc().toIso8601String(); + } else { + // json[r'localDateTime'] = null; + } + json[r'ownerId'] = this.ownerId; + if (this.thumbhash != null) { + json[r'thumbhash'] = this.thumbhash; + } else { + // json[r'thumbhash'] = null; + } + json[r'type'] = this.type; + return json; + } + + /// Returns a new [SyncAssetV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetV1( + checksum: mapValueOfType(json, r'checksum')!, + deletedAt: mapDateTime(json, r'deletedAt', r''), + fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r''), + fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r''), + id: mapValueOfType(json, r'id')!, + isFavorite: mapValueOfType(json, r'isFavorite')!, + isVisible: mapValueOfType(json, r'isVisible')!, + localDateTime: mapDateTime(json, r'localDateTime', r''), + ownerId: mapValueOfType(json, r'ownerId')!, + thumbhash: mapValueOfType(json, r'thumbhash'), + type: SyncAssetV1TypeEnum.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 = SyncAssetV1.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 = SyncAssetV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetV1-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] = SyncAssetV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'checksum', + 'deletedAt', + 'fileCreatedAt', + 'fileModifiedAt', + 'id', + 'isFavorite', + 'isVisible', + 'localDateTime', + 'ownerId', + 'thumbhash', + 'type', + }; +} + + +class SyncAssetV1TypeEnum { + /// Instantiate a new enum with the provided [value]. + const SyncAssetV1TypeEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const IMAGE = SyncAssetV1TypeEnum._(r'IMAGE'); + static const VIDEO = SyncAssetV1TypeEnum._(r'VIDEO'); + static const AUDIO = SyncAssetV1TypeEnum._(r'AUDIO'); + static const OTHER = SyncAssetV1TypeEnum._(r'OTHER'); + + /// List of all possible values in this [enum][SyncAssetV1TypeEnum]. + static const values = [ + IMAGE, + VIDEO, + AUDIO, + OTHER, + ]; + + static SyncAssetV1TypeEnum? fromJson(dynamic value) => SyncAssetV1TypeEnumTypeTransformer().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 = SyncAssetV1TypeEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [SyncAssetV1TypeEnum] to String, +/// and [decode] dynamic data back to [SyncAssetV1TypeEnum]. +class SyncAssetV1TypeEnumTypeTransformer { + factory SyncAssetV1TypeEnumTypeTransformer() => _instance ??= const SyncAssetV1TypeEnumTypeTransformer._(); + + const SyncAssetV1TypeEnumTypeTransformer._(); + + String encode(SyncAssetV1TypeEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a SyncAssetV1TypeEnum. + /// + /// 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. + SyncAssetV1TypeEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'IMAGE': return SyncAssetV1TypeEnum.IMAGE; + case r'VIDEO': return SyncAssetV1TypeEnum.VIDEO; + case r'AUDIO': return SyncAssetV1TypeEnum.AUDIO; + case r'OTHER': return SyncAssetV1TypeEnum.OTHER; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SyncAssetV1TypeEnumTypeTransformer] instance. + static SyncAssetV1TypeEnumTypeTransformer? _instance; +} + + diff --git a/mobile/openapi/lib/model/sync_entity_type.dart b/mobile/openapi/lib/model/sync_entity_type.dart index 5d130f7f93..5e52a10e7a 100644 --- a/mobile/openapi/lib/model/sync_entity_type.dart +++ b/mobile/openapi/lib/model/sync_entity_type.dart @@ -27,6 +27,12 @@ class SyncEntityType { static const userDeleteV1 = SyncEntityType._(r'UserDeleteV1'); static const partnerV1 = SyncEntityType._(r'PartnerV1'); static const partnerDeleteV1 = SyncEntityType._(r'PartnerDeleteV1'); + static const assetV1 = SyncEntityType._(r'AssetV1'); + static const assetDeleteV1 = SyncEntityType._(r'AssetDeleteV1'); + static const assetExifV1 = SyncEntityType._(r'AssetExifV1'); + static const partnerAssetV1 = SyncEntityType._(r'PartnerAssetV1'); + static const partnerAssetDeleteV1 = SyncEntityType._(r'PartnerAssetDeleteV1'); + static const partnerAssetExifV1 = SyncEntityType._(r'PartnerAssetExifV1'); /// List of all possible values in this [enum][SyncEntityType]. static const values = [ @@ -34,6 +40,12 @@ class SyncEntityType { userDeleteV1, partnerV1, partnerDeleteV1, + assetV1, + assetDeleteV1, + assetExifV1, + partnerAssetV1, + partnerAssetDeleteV1, + partnerAssetExifV1, ]; static SyncEntityType? fromJson(dynamic value) => SyncEntityTypeTypeTransformer().decode(value); @@ -76,6 +88,12 @@ class SyncEntityTypeTypeTransformer { case r'UserDeleteV1': return SyncEntityType.userDeleteV1; case r'PartnerV1': return SyncEntityType.partnerV1; case r'PartnerDeleteV1': return SyncEntityType.partnerDeleteV1; + case r'AssetV1': return SyncEntityType.assetV1; + case r'AssetDeleteV1': return SyncEntityType.assetDeleteV1; + case r'AssetExifV1': return SyncEntityType.assetExifV1; + case r'PartnerAssetV1': return SyncEntityType.partnerAssetV1; + case r'PartnerAssetDeleteV1': return SyncEntityType.partnerAssetDeleteV1; + case r'PartnerAssetExifV1': return SyncEntityType.partnerAssetExifV1; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); diff --git a/mobile/openapi/lib/model/sync_request_type.dart b/mobile/openapi/lib/model/sync_request_type.dart index c35b17dea1..08f977ad57 100644 --- a/mobile/openapi/lib/model/sync_request_type.dart +++ b/mobile/openapi/lib/model/sync_request_type.dart @@ -25,11 +25,19 @@ class SyncRequestType { static const usersV1 = SyncRequestType._(r'UsersV1'); static const partnersV1 = SyncRequestType._(r'PartnersV1'); + static const assetsV1 = SyncRequestType._(r'AssetsV1'); + static const assetExifsV1 = SyncRequestType._(r'AssetExifsV1'); + static const partnerAssetsV1 = SyncRequestType._(r'PartnerAssetsV1'); + static const partnerAssetExifsV1 = SyncRequestType._(r'PartnerAssetExifsV1'); /// List of all possible values in this [enum][SyncRequestType]. static const values = [ usersV1, partnersV1, + assetsV1, + assetExifsV1, + partnerAssetsV1, + partnerAssetExifsV1, ]; static SyncRequestType? fromJson(dynamic value) => SyncRequestTypeTypeTransformer().decode(value); @@ -70,6 +78,10 @@ class SyncRequestTypeTypeTransformer { switch (data) { case r'UsersV1': return SyncRequestType.usersV1; case r'PartnersV1': return SyncRequestType.partnersV1; + case r'AssetsV1': return SyncRequestType.assetsV1; + case r'AssetExifsV1': return SyncRequestType.assetExifsV1; + case r'PartnerAssetsV1': return SyncRequestType.partnerAssetsV1; + case r'PartnerAssetExifsV1': return SyncRequestType.partnerAssetExifsV1; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 1879df2743..a5556f9dcf 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: "direct overridden" description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" analyzer_plugin: dependency: "direct overridden" description: @@ -34,42 +34,42 @@ packages: dependency: transitive description: name: ansicolor - sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880" + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" archive: dependency: transitive description: name: archive - sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742" url: "https://pub.dev" source: hosted - version: "3.6.1" + version: "4.0.4" args: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" async: dependency: "direct main" description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" auto_route: dependency: "direct main" description: name: auto_route - sha256: b83e8ce46da7228cdd019b5a11205454847f0a971bca59a7529b98df9876889b + sha256: "1d1bd908a1fec327719326d5d0791edd37f16caff6493c01003689fb03315ad7" url: "https://pub.dev" source: hosted - version: "9.2.2" + version: "9.3.0+1" auto_route_generator: dependency: "direct dev" description: @@ -82,66 +82,66 @@ packages: dependency: "direct main" description: name: background_downloader - sha256: "6a945db1a1c7727a4bc9c1d7c882cfb1a819f873b77e01d5e5dd6a3fb231cb28" + sha256: ed64a215cd24c83a478f602364a3ca86a6dafd178ad783188cc32c6956d5e529 url: "https://pub.dev" source: hosted - version: "8.5.5" + version: "8.9.4" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" build: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.4" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" url: "https://pub.dev" source: hosted - version: "2.4.11" + version: "2.4.15" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" url: "https://pub.dev" source: hosted - version: "7.3.1" + version: "8.0.0" built_collection: dependency: transitive description: @@ -154,34 +154,34 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.9.5" cached_network_image: dependency: "direct main" description: name: cached_network_image - sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.1" cached_network_image_platform_interface: dependency: transitive description: name: cached_network_image_platform_interface - sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.1.1" cached_network_image_web: dependency: transitive description: name: cached_network_image_web - sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.1" cancellation_token: dependency: transitive description: @@ -194,18 +194,18 @@ packages: dependency: "direct main" description: name: cancellation_token_http - sha256: "37ad2a20dba02aeb1f0a4d845e7a57eebacdb709e1186e0491e7cd81c559c4ff" + sha256: "0fff478fe5153700396b3472ddf93303c219f1cb8d8e779e65b014cb9c7f0213" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -226,42 +226,42 @@ packages: dependency: transitive description: name: cli_util - sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "0.4.2" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: name: code_builder - sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" url: "https://pub.dev" source: hosted - version: "4.10.0" + version: "4.10.1" collection: dependency: "direct main" description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" connectivity_plus: dependency: "direct main" description: name: connectivity_plus - sha256: "2056db5241f96cdc0126bd94459fc4cdc13876753768fc7a31c425e50a7177d0" + sha256: "04bf81bb0b77de31557b58d052b24b3eee33f09a6e7a8c68a3e247c7df19ec27" url: "https://pub.dev" source: hosted - version: "6.0.5" + version: "6.1.3" connectivity_plus_platform_interface: dependency: transitive description: @@ -274,18 +274,18 @@ packages: dependency: transitive description: name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" crop_image: dependency: "direct main" description: name: crop_image - sha256: "6cf20655ecbfba99c369d43ec7adcfa49bf135af88fb75642173d6224a95d3f1" + sha256: "4fdebd00d0c7d1a6e3abeb1e3843efbc202204b867f3e377fcebcf77aaf69a17" url: "https://pub.dev" source: hosted - version: "1.0.13" + version: "1.0.16" cross_file: dependency: transitive description: @@ -298,50 +298,50 @@ packages: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.6" csslib: dependency: transitive description: name: csslib - sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.2" custom_lint: dependency: "direct dev" description: name: custom_lint - sha256: "7c0aec12df22f9082146c354692056677f1e70bc43471644d1fdb36c6fdda799" + sha256: "4500e88854e7581ee43586abeaf4443cb22375d6d289241a87b1aadf678d5545" url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.10" custom_lint_builder: dependency: transitive description: name: custom_lint_builder - sha256: d7dc41e709dde223806660268678be7993559e523eb3164e2a1425fd6f7615a9 + sha256: "5a95eff100da256fbf086b329c17c8b49058c261cdf56d3a4157d3c31c511d78" url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.10" custom_lint_core: dependency: transitive description: name: custom_lint_core - sha256: a85e8f78f4c52f6c63cdaf8c872eb573db0231dcdf3c3a5906d493c1f8bc20e6 + sha256: "76a4046cc71d976222a078a8fd4a65e198b70545a8d690a75196dd14f08510f6" url: "https://pub.dev" source: hosted - version: "0.6.3" + version: "0.6.10" dart_style: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.8" dartx: dependency: transitive description: @@ -354,26 +354,34 @@ packages: dependency: transitive description: name: dbus - sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" + desktop_webview_window: + dependency: transitive + description: + name: desktop_webview_window + sha256: "57cf20d81689d5cbb1adfd0017e96b669398a669d927906073b0e42fc64111c0" + url: "https://pub.dev" + source: hosted + version: "0.2.3" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: f545ffbadee826f26f2e1a0f0cbd667ae9a6011cc0f77c0f8f00a969655e6e95 + sha256: "306b78788d1bb569edb7c55d622953c2414ca12445b41c9117963e03afc5c513" url: "https://pub.dev" source: hosted - version: "11.1.1" + version: "11.3.3" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.0.2" dynamic_color: dependency: "direct main" description: @@ -394,10 +402,10 @@ packages: dependency: "direct main" description: name: easy_localization - sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201 + sha256: "0f5239c7b8ab06c66440cfb0e9aa4b4640429c6668d5a42fe389c5de42220b12" url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.0.7+1" easy_logger: dependency: transitive description: @@ -410,50 +418,50 @@ packages: dependency: "direct dev" description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" ffi: dependency: transitive description: name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" file: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" file_picker: dependency: "direct main" description: name: file_picker - sha256: aac85f20436608e01a6ffd1fdd4e746a7f33c93a2c83752e626bdfaea139b877 + sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810 url: "https://pub.dev" source: hosted - version: "8.1.3" + version: "8.3.7" file_selector_linux: dependency: transitive description: name: file_selector_linux - sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" url: "https://pub.dev" source: hosted - version: "0.9.2+1" + version: "0.9.3+2" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" url: "https://pub.dev" source: hosted - version: "0.9.4" + version: "0.9.4+2" file_selector_platform_interface: dependency: transitive description: @@ -466,18 +474,18 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" url: "https://pub.dev" source: hosted - version: "0.9.3+2" + version: "0.9.3+4" fixnum: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -487,10 +495,10 @@ packages: dependency: "direct main" description: name: flutter_cache_manager - sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.1" flutter_displaymode: dependency: "direct main" description: @@ -508,18 +516,18 @@ packages: dependency: "direct main" description: name: flutter_hooks - sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 + sha256: b772e710d16d7a20c0740c4f855095026b31c7eb5ba3ab67d2bd52021cd9461d url: "https://pub.dev" source: hosted - version: "0.20.5" + version: "0.21.2" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: "619817c4b65b322b5104b6bb6dfe6cda62d9729bd7ad4303ecc8b4e690a67a77" + sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c url: "https://pub.dev" source: hosted - version: "0.14.1" + version: "0.14.3" flutter_lints: dependency: "direct dev" description: @@ -532,10 +540,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: dd6676d8c2926537eccdf9f72128bbb2a9d0814689527b17f92c248ff192eaf3 + sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35" url: "https://pub.dev" source: hosted - version: "17.2.1+2" + version: "17.2.4" flutter_local_notifications_linux: dependency: transitive description: @@ -561,34 +569,34 @@ packages: dependency: "direct dev" description: name: flutter_native_splash - sha256: aa06fec78de2190f3db4319dd60fdc8d12b2626e93ef9828633928c2dcaea840 + sha256: edb09c35ee9230c4b03f13dd45bb3a276d0801865f0a4650b7e2a3bba61a803a url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.5" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de" + sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3" url: "https://pub.dev" source: hosted - version: "2.0.21" + version: "2.0.27" flutter_riverpod: dependency: transitive description: name: flutter_riverpod - sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d" + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.6.1" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c + sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.0.17" flutter_test: dependency: "direct dev" description: flutter @@ -598,18 +606,26 @@ packages: dependency: "direct main" description: name: flutter_udid - sha256: "63384bd96203aaefccfd7137fab642edda18afede12b0e9e1a2c96fe2589fd07" + sha256: be464dc5b1fb7ee894f6a32d65c086ca5e177fdcf9375ac08d77495b98150f84 url: "https://pub.dev" source: hosted - version: "3.0.0" - flutter_web_auth: + version: "3.0.1" + flutter_web_auth_2: dependency: "direct main" description: - name: flutter_web_auth - sha256: "95e4856e24fb6ac1678f5ff334743b63f782d839ab324543d29ccbd295176209" + name: flutter_web_auth_2 + sha256: "561c32d32ed537853de43852c35849cf1d37f3482f41f22b718ab6112f96b333" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "5.0.0-alpha.0" + flutter_web_auth_2_platform_interface: + dependency: transitive + description: + name: flutter_web_auth_2_platform_interface + sha256: "45927587ebb2364cd273675ec95f6f67b81725754b416cef2b65cdc63fd3e853" + url: "https://pub.dev" + source: hosted + version: "5.0.0-alpha.0" flutter_web_plugins: dependency: transitive description: flutter @@ -619,10 +635,10 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "95f349437aeebe524ef7d6c9bde3e6b4772717cf46a0eb6a3ceaddc740b297cc" + sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" url: "https://pub.dev" source: hosted - version: "8.2.8" + version: "8.2.12" freezed_annotation: dependency: transitive description: @@ -664,10 +680,10 @@ packages: dependency: transitive description: name: geolocator_apple - sha256: bc2aca02423ad429cb0556121f56e60360a2b7d694c8570301d06ea0c00732fd + sha256: c4ecead17985ede9634f21500072edfcb3dba0ef7b97f8d7bc556d2d722b3ba3 url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "2.3.9" geolocator_platform_interface: dependency: transitive description: @@ -696,10 +712,10 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" graphs: dependency: transitive description: @@ -712,58 +728,58 @@ packages: dependency: "direct main" description: name: hooks_riverpod - sha256: "45b2030a18bcd6dbd680c2c91bc3b33e3fe7c323e3acb5ecec93a613e2fbaa8a" + sha256: "70bba33cfc5670c84b796e6929c54b8bc5be7d0fe15bb28c2560500b9ad06966" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.6.1" hotreloader: dependency: transitive description: name: hotreloader - sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.3.0" html: dependency: transitive description: name: html - sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" url: "https://pub.dev" source: hosted - version: "0.15.4" + version: "0.15.5" http: dependency: "direct main" description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" http_multi_server: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: - dependency: "direct main" + dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" image: dependency: transitive description: name: image - sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" + sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3" url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.5.3" image_picker: dependency: "direct main" description: @@ -776,26 +792,26 @@ packages: dependency: transitive description: name: image_picker_android - sha256: c0e72ecd170b00a5590bb71238d57dc8ad22ee14c60c6b0d1a4e05cafbc5db4b + sha256: "8bd392ba8b0c8957a157ae0dc9fcf48c58e6c20908d5880aea1d79734df090e9" url: "https://pub.dev" source: hosted - version: "0.8.12+11" + version: "0.8.12+22" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50" + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" url: "https://pub.dev" source: hosted - version: "0.8.12" + version: "0.8.12+2" image_picker_linux: dependency: transitive description: @@ -808,18 +824,18 @@ packages: dependency: transitive description: name: image_picker_macos - sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.1+2" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.10.1" image_picker_windows: dependency: transitive description: @@ -852,10 +868,10 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" isar: dependency: "direct main" description: @@ -900,18 +916,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -924,58 +940,58 @@ packages: dependency: transitive description: name: lints - sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.1" logging: dependency: "direct main" description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" macros: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" maplibre_gl: dependency: "direct main" description: name: maplibre_gl - sha256: "9dd9eebee52f42a45aaa9cdb912afa47845c37007b26a799aa482ecd368804c8" + sha256: cd0adf2da87149cab556ac70977783d6dcb3bd73b17a5583cc8366a5aafa46f8 url: "https://pub.dev" source: hosted - version: "0.19.0+2" + version: "0.21.0" maplibre_gl_platform_interface: dependency: transitive description: name: maplibre_gl_platform_interface - sha256: a95fa38a3532253f32dfe181389adfe9f402773e58ac902d9c4efad3209e0903 + sha256: "6db8234705e58c09b6fd5a43747a817ba1e6e91a76deb3ed057a36a994d86f22" url: "https://pub.dev" source: hosted - version: "0.19.0+2" + version: "0.21.0" maplibre_gl_web: dependency: transitive description: name: maplibre_gl_web - sha256: "7f1540b384f16f3c9bc8b4ebdfca96fb07f6dab5d9ef4dd0e102985dba238691" + sha256: e1cbe04594fdb0d76de7cd448c0048290df8dc69dc37a85d23307dd595779141 url: "https://pub.dev" source: hosted - version: "0.19.0+2" + version: "0.21.0" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -988,18 +1004,18 @@ packages: dependency: "direct overridden" description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mime: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "2.0.0" mocktail: dependency: "direct dev" description: @@ -1021,18 +1037,18 @@ packages: dependency: "direct main" description: name: network_info_plus - sha256: bf9e39e523e9951d741868dc33ac386b0bc24301e9b7c8a7d60dbc34879150a8 + sha256: "08f4166bbb77da9e407edef6322a33f87b18c0ca46483fb25606cb3d2bfcdd2a" url: "https://pub.dev" source: hosted - version: "6.1.1" + version: "6.1.3" network_info_plus_platform_interface: dependency: transitive description: name: network_info_plus_platform_interface - sha256: b7f35f4a7baef511159e524499f3c15464a49faa5ec10e92ee0bce265e664906 + sha256: "7e7496a8a9d8136859b8881affc613c4a21304afeb6c324bcefc4bd0aff6b94b" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" nm: dependency: transitive description: @@ -1060,74 +1076,66 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: da8d9ac8c4b1df253d1a328b7bf01ae77ef132833479ab40763334db13b91cce + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" url: "https://pub.dev" source: hosted - version: "8.1.1" + version: "8.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.2.0" path: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: name: path_parsing - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" path_provider: dependency: "direct main" description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb" + sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" url: "https://pub.dev" source: hosted - version: "2.2.9" + version: "2.2.16" path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 - url: "https://pub.dev" - source: hosted - version: "2.4.0" - path_provider_ios: dependency: "direct main" description: - name: path_provider_ios - sha256: "03d639406f5343478352433f00d3c4394d52dac8df3d847869c5e2333e0bbce8" + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -1156,42 +1164,42 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" + sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" url: "https://pub.dev" source: hosted - version: "11.3.1" + version: "11.4.0" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: eaf2a1ec4472775451e88ca6a7b86559ef2f1d1ed903942ed135e38ea0097dca + sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc url: "https://pub.dev" source: hosted - version: "12.0.8" + version: "12.1.0" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 + sha256: f84a188e79a35c687c132a0a0556c254747a08561e99ab933f12f6ca71ef3c98 url: "https://pub.dev" source: hosted - version: "9.4.5" + version: "9.4.6" permission_handler_html: dependency: transitive description: name: permission_handler_html - sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851 + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" url: "https://pub.dev" source: hosted - version: "0.1.3+2" + version: "0.1.3+5" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: fe0ffe274d665be8e34f9c59705441a7d248edebbe5d9e3ec2665f88b79358ea + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.3.0" permission_handler_windows: dependency: transitive description: @@ -1204,18 +1212,18 @@ packages: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.1.0" photo_manager: dependency: "direct main" description: name: photo_manager - sha256: f5ef2618870e9a50d8bfeb81a02c242d580ae8614bd5ea9e1b80dbb7e49d4260 + sha256: "0bc7548fd3111eb93a3b0abf1c57364e40aeda32512c100085a48dade60e574f" url: "https://pub.dev" source: hosted - version: "3.6.1" + version: "3.6.4" photo_manager_image_provider: dependency: "direct main" description: @@ -1228,10 +1236,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -1248,78 +1256,86 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + posix: + dependency: transitive + description: + name: posix + sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + url: "https://pub.dev" + source: hosted + version: "6.0.1" process: dependency: transitive description: name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.3" pub_semver: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.0" riverpod: dependency: transitive description: name: riverpod - sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d + sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.6.1" riverpod_analyzer_utils: dependency: transitive description: name: riverpod_analyzer_utils - sha256: ee72770090078e6841d51355292335f1bc254907c6694283389dcb8156d99a4d + sha256: "0dcb0af32d561f8fa000c6a6d95633c9fb08ea8a8df46e3f9daca59f11218167" url: "https://pub.dev" source: hosted - version: "0.5.3" + version: "0.5.6" riverpod_annotation: dependency: "direct main" description: name: riverpod_annotation - sha256: e5e796c0eba4030c704e9dae1b834a6541814963292839dcf9638d53eba84f5c + sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8 url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.6.1" riverpod_generator: dependency: "direct dev" description: name: riverpod_generator - sha256: "1ad626afbd8b01d168870b13c0b036f8a5bdb57c14cd426dc5b4595466bd6e2f" + sha256: "851aedac7ad52693d12af3bf6d92b1626d516ed6b764eb61bf19e968b5e0b931" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.6.1" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: b95a8cdc6102397f7d51037131c25ce7e51be900be021af4bf0c2d6f1b8f7aa7 + sha256: "0684c21a9a4582c28c897d55c7b611fa59a351579061b43f8c92c005804e63a8" url: "https://pub.dev" source: hosted - version: "2.3.12" + version: "2.6.1" rxdart: dependency: transitive description: name: rxdart - sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" url: "https://pub.dev" source: hosted - version: "0.27.7" + version: "0.28.0" scrollable_positioned_list: dependency: "direct main" description: @@ -1364,50 +1380,50 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "9c9bafd4060728d7cdb2464c341743adbd79d327cb067ec7afb64583540b47c8" + sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da url: "https://pub.dev" source: hosted - version: "10.1.2" + version: "10.1.4" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: c57c0bbfec7142e3a0f55633be504b796af72e60e3c791b44d5a017b985f7a48 + sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.0.2" shared_preferences: dependency: transitive description: name: shared_preferences - sha256: c272f9cabca5a81adc9b0894381e9c1def363e980f960fa903c604c471b22f68 + sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.5.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294" + sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.8" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1" + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: @@ -1420,39 +1436,39 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "59dc807b94d29d52ddbb1b3c0d3b9d0a67fc535a64e62a5542c8db0513fcb6c2" + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.3" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2" + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" shelf: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" socket_io_client: dependency: "direct main" description: @@ -1481,10 +1497,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" sprintf: dependency: transitive description: @@ -1497,26 +1513,50 @@ packages: dependency: transitive description: name: sqflite - sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 url: "https://pub.dev" source: hosted - version: "2.3.3+1" + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.5" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" state_notifier: dependency: transitive description: @@ -1529,26 +1569,26 @@ packages: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" sync_http: dependency: transitive description: @@ -1561,26 +1601,26 @@ packages: dependency: transitive description: name: synchronized - sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" url: "https://pub.dev" source: hosted - version: "3.1.0+1" + version: "3.3.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.4" thumbhash: dependency: "direct main" description: @@ -1593,10 +1633,10 @@ packages: dependency: transitive description: name: time - sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221 + sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" timezone: dependency: "direct main" description: @@ -1609,18 +1649,18 @@ packages: dependency: transitive description: name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" typed_data: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" universal_io: dependency: transitive description: @@ -1633,42 +1673,42 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9" + sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" url: "https://pub.dev" source: hosted - version: "6.3.8" + version: "6.3.15" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -1681,50 +1721,50 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.4" uuid: dependency: transitive description: name: uuid - sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff url: "https://pub.dev" source: hosted - version: "4.4.2" + version: "4.5.1" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "4ac59808bbfca6da38c99f415ff2d3a5d7ca0a6b4809c71d9cf30fba5daf9752" + sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" url: "https://pub.dev" source: hosted - version: "1.1.10+1" + version: "1.1.18" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: f3247e7ab0ec77dc759263e68394990edc608fb2b480b80db8aa86ed09279e33 + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" url: "https://pub.dev" source: hosted - version: "1.1.10+1" + version: "1.1.13" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "18489bdd8850de3dd7ca8a34e0c446f719ec63e2bab2e7a8cc66a9028dd76c5a" + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" url: "https://pub.dev" source: hosted - version: "1.1.10+1" + version: "1.1.16" vector_math: dependency: transitive description: @@ -1737,42 +1777,42 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.1" wakelock_plus: dependency: "direct main" description: name: wakelock_plus - sha256: bf4ee6f17a2fa373ed3753ad0e602b7603f8c75af006d5b9bdade263928c0484 + sha256: "36c88af0b930121941345306d259ec4cc4ecca3b151c02e3a9e71aede83c615e" url: "https://pub.dev" source: hosted - version: "1.2.8" + version: "1.2.10" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16" + sha256: "70e780bc99796e1db82fe764b1e7dcb89a86f1e5b3afb1db354de50f2e41eb7a" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" watcher: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web_socket: dependency: transitive description: @@ -1785,42 +1825,50 @@ packages: dependency: transitive description: name: web_socket_channel - sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" webdriver: dependency: transitive description: name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" win32: dependency: transitive description: name: win32 - sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9" + sha256: b89e6e24d1454e149ab20fbb225af58660f0c0bf4475544650700d8e2da54aef url: "https://pub.dev" source: hosted - version: "5.5.3" + version: "5.11.0" win32_registry: dependency: transitive description: name: win32_registry - sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" + sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "2.1.0" + window_to_front: + dependency: transitive + description: + name: window_to_front + sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee" + url: "https://pub.dev" + source: hosted + version: "0.0.3" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" xml: dependency: transitive description: @@ -1833,18 +1881,18 @@ packages: dependency: transitive description: name: xxh3 - sha256: a92b30944a9aeb4e3d4f3c3d4ddb3c7816ca73475cd603682c4f8149690f56d7 + sha256: "399a0438f5d426785723c99da6b16e136f4953fb1e9db0bf270bd41dd4619916" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.2.0" yaml: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" sdks: - dart: ">=3.5.3 <4.0.0" - flutter: ">=3.24.5" + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.29.1" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index f9263ac155..6d214ef40b 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -2,97 +2,82 @@ name: immich_mobile description: Immich - selfhosted backup media file on mobile phone publish_to: 'none' -version: 1.128.0+186 +version: 1.129.0+187 environment: sdk: '>=3.3.0 <4.0.0' - flutter: 3.24.5 + flutter: 3.29.1 -isar_version: &isar_version 3.1.8 # define the version to be used +isar_version: &isar_version 3.1.8 dependencies: flutter: sdk: flutter - path_provider_ios: - photo_manager: ^3.6.1 - photo_manager_image_provider: ^2.2.0 - flutter_hooks: ^0.20.4 - hooks_riverpod: ^2.4.9 - riverpod_annotation: ^2.3.3 - cached_network_image: ^3.3.1 - flutter_cache_manager: ^3.3.1 - intl: ^0.19.0 + async: ^2.11.0 auto_route: ^9.2.0 - fluttertoast: ^8.2.4 - socket_io_client: ^2.0.3+1 - maplibre_gl: 0.19.0+2 - geolocator: ^11.0.0 # used to move to current location in map view - flutter_udid: ^3.0.0 - flutter_svg: ^2.0.9 - package_info_plus: ^8.0.1 - url_launcher: ^6.2.4 - http: ^1.1.0 - cancellation_token_http: ^2.0.0 - easy_localization: ^3.0.3 - share_plus: ^10.0.0 - flutter_displaymode: ^0.6.0 - scrollable_positioned_list: ^0.3.8 - path: ^1.8.3 - path_provider: ^2.1.2 + background_downloader: ^8.5.5 + cached_network_image: ^3.4.1 + cancellation_token_http: ^2.1.0 collection: ^1.18.0 - http_parser: ^4.0.2 - flutter_web_auth: 0.6.0 - easy_image_viewer: ^1.4.0 + connectivity_plus: ^6.1.3 + crop_image: ^1.0.16 + device_info_plus: ^11.3.3 + dynamic_color: ^1.7.0 + easy_image_viewer: ^1.5.1 + easy_localization: ^3.0.7+1 + file_picker: ^8.0.0+1 + flutter_cache_manager: ^3.4.1 + flutter_displaymode: ^0.6.0 + flutter_hooks: ^0.21.2 + flutter_local_notifications: ^17.2.1+2 + flutter_svg: ^2.0.17 + flutter_udid: ^3.0.0 + flutter_web_auth_2: ^5.0.0-alpha.0 + fluttertoast: ^8.2.12 + geolocator: ^11.0.0 + hooks_riverpod: ^2.6.1 + http: ^1.3.0 + image_picker: ^1.1.2 + intl: ^0.19.0 + logging: ^1.3.0 + maplibre_gl: ^0.21.0 + network_info_plus: ^6.1.3 + octo_image: ^2.1.0 + package_info_plus: ^8.3.0 + path: ^1.9.1 + path_provider: ^2.1.5 + path_provider_foundation: ^2.4.1 + permission_handler: ^11.4.0 + photo_manager: ^3.6.4 + photo_manager_image_provider: ^2.2.0 + riverpod_annotation: ^2.6.1 + scrollable_positioned_list: ^0.3.8 + share_handler: ^0.0.22 + share_plus: ^10.1.4 + socket_io_client: ^2.0.3+1 + thumbhash: 0.1.0+1 + timezone: ^0.9.4 + url_launcher: ^6.3.1 + wakelock_plus: ^1.2.10 + + native_video_player: + git: + url: https://github.com/immich-app/native_video_player + ref: '5459d54' + openapi: + path: openapi isar: version: *isar_version hosted: https://pub.isar-community.dev/ isar_flutter_libs: # contains Isar Core version: *isar_version hosted: https://pub.isar-community.dev/ - permission_handler: ^11.2.0 - device_info_plus: ^11.0.0 - connectivity_plus: ^6.0.0 - wakelock_plus: ^1.1.4 - flutter_local_notifications: ^17.2.1+2 - timezone: ^0.9.2 - octo_image: ^2.0.0 - thumbhash: 0.1.0+1 - async: ^2.11.0 - dynamic_color: ^1.7.0 #package to apply system theme - background_downloader: ^8.5.5 - network_info_plus: ^6.1.1 - native_video_player: - git: - url: https://github.com/immich-app/native_video_player - ref: '5459d54' - #image editing packages - crop_image: ^1.0.13 - - openapi: - path: openapi - - # easy to remove packages: - image_picker: ^1.0.7 # only used to select user profile image from system gallery -> we can simply select an image from within immich? - logging: ^1.2.0 - file_picker: ^8.0.0+1 - share_handler: ^0.0.22 - -# This is uncommented in F-Droid build script -# Taken from https://github.com/Myzel394/locus/blob/445013d22ec1d759027d4303bd65b30c5c8588c8/pubspec.yaml#L105 dependency_overrides: - # TODO: remove once Isar is updated - analyzer: ^6.3.0 - # TODO: remove once analyzer override is removed + analyzer: ^6.0.0 meta: ^1.11.0 - # TODO: remove once analyzer override is removed analyzer_plugin: ^0.11.3 -#f geolocator_android: -#f git: -#f url: https://github.com/Zverik/flutter-geolocator.git -#f ref: floss -#f path: geolocator_android dev_dependencies: flutter_test: @@ -100,17 +85,17 @@ dev_dependencies: flutter_lints: ^5.0.0 build_runner: ^2.4.8 auto_route_generator: ^9.0.0 - flutter_launcher_icons: ^0.14.0 - flutter_native_splash: ^2.3.9 + flutter_launcher_icons: ^0.14.3 + flutter_native_splash: ^2.4.5 isar_generator: version: *isar_version hosted: https://pub.isar-community.dev/ integration_test: sdk: flutter custom_lint: ^0.6.4 - riverpod_lint: ^2.3.7 - riverpod_generator: ^2.3.9 - mocktail: ^1.0.3 + riverpod_lint: ^2.6.1 + riverpod_generator: ^2.6.1 + mocktail: ^1.0.4 immich_mobile_immich_lint: path: './immich_lint' fake_async: ^1.3.1 @@ -145,8 +130,8 @@ flutter_launcher_icons: adaptive_icon_background: '#ffffff' adaptive_icon_foreground: 'assets/immich-logo-android-adaptive-icon.png' image_path_ios: 'assets/immich-logo-w-bg.png' - android: 'ic_launcher' # can specify file name here e.g. "ic_launcher" - ios: false # can specify file name here e.g. "My-Launcher-Icon + android: 'ic_launcher' + ios: false remove_alpha_ios: true analyzer: diff --git a/mobile/test/domain/service.mock.dart b/mobile/test/domain/service.mock.dart new file mode 100644 index 0000000000..53a173fc28 --- /dev/null +++ b/mobile/test/domain/service.mock.dart @@ -0,0 +1,7 @@ +import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockStoreService extends Mock implements StoreService {} + +class MockUserService extends Mock implements UserService {} diff --git a/mobile/test/domain/services/user_service_test.dart b/mobile/test/domain/services/user_service_test.dart new file mode 100644 index 0000000000..512f5cb4a2 --- /dev/null +++ b/mobile/test/domain/services/user_service_test.dart @@ -0,0 +1,133 @@ +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/interfaces/user.interface.dart'; +import 'package:immich_mobile/domain/interfaces/user_api.repository.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../fixtures/user.stub.dart'; +import '../../infrastructure/repository.mock.dart'; +import '../service.mock.dart'; + +void main() { + late UserService sut; + late IUserRepository mockUserRepo; + late IUserApiRepository mockUserApiRepo; + late StoreService mockStoreService; + + setUp(() { + mockUserRepo = MockUserRepository(); + mockUserApiRepo = MockUserApiRepository(); + mockStoreService = MockStoreService(); + sut = UserService( + userRepository: mockUserRepo, + userApiRepository: mockUserApiRepo, + storeService: mockStoreService, + ); + }); + + group('getMyUser', () { + test('should return user from store', () { + when(() => mockStoreService.get(StoreKey.currentUser)) + .thenReturn(UserStub.admin); + final result = sut.getMyUser(); + expect(result, UserStub.admin); + }); + + test('should handle user not found scenario', () { + when(() => mockStoreService.get(StoreKey.currentUser)) + .thenThrow(Exception('User not found')); + + expect(() => sut.getMyUser(), throwsA(isA())); + }); + }); + + group('tryGetMyUser', () { + test('should return user from store', () { + when(() => mockStoreService.tryGet(StoreKey.currentUser)) + .thenReturn(UserStub.admin); + final result = sut.tryGetMyUser(); + expect(result, UserStub.admin); + }); + + test('should return null if user not found', () { + when(() => mockStoreService.tryGet(StoreKey.currentUser)) + .thenReturn(null); + final result = sut.tryGetMyUser(); + expect(result, isNull); + }); + }); + + group('watchMyUser', () { + test('should return user stream from store', () { + when(() => mockStoreService.watch(StoreKey.currentUser)) + .thenAnswer((_) => Stream.value(UserStub.admin)); + final result = sut.watchMyUser(); + expect(result, emits(UserStub.admin)); + }); + + test('should return an empty stream if user not found', () { + when(() => mockStoreService.watch(StoreKey.currentUser)) + .thenAnswer((_) => const Stream.empty()); + final result = sut.watchMyUser(); + expect(result, emitsInOrder([])); + }); + }); + + group('refreshMyUser', () { + test('should return user from api and store it', () async { + when(() => mockUserApiRepo.getMyUser()) + .thenAnswer((_) async => UserStub.admin); + when(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin)) + .thenAnswer((_) async => true); + when(() => mockUserRepo.update(UserStub.admin)) + .thenAnswer((_) async => UserStub.admin); + + final result = await sut.refreshMyUser(); + verify(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin)) + .called(1); + verify(() => mockUserRepo.update(UserStub.admin)).called(1); + expect(result, UserStub.admin); + }); + + test('should return null if user not found', () async { + when(() => mockUserApiRepo.getMyUser()).thenAnswer((_) async => null); + + final result = await sut.refreshMyUser(); + verifyNever( + () => mockStoreService.put(StoreKey.currentUser, UserStub.admin), + ); + verifyNever(() => mockUserRepo.update(UserStub.admin)); + expect(result, isNull); + }); + }); + + group('createProfileImage', () { + test('should return profile image path', () async { + when( + () => mockUserApiRepo.createProfileImage( + name: 'profile.jpg', + data: Uint8List(0), + ), + ).thenAnswer((_) async => 'profile.jpg'); + + final result = await sut.createProfileImage('profile.jpg', Uint8List(0)); + expect(result, 'profile.jpg'); + }); + + test('should return null if profile image creation fails', () async { + when( + () => mockUserApiRepo.createProfileImage( + name: 'profile.jpg', + data: Uint8List(0), + ), + ).thenThrow(Exception('Failed to create profile image')); + + final result = await sut.createProfileImage('profile.jpg', Uint8List(0)); + expect(result, isNull); + }); + }); +} diff --git a/mobile/test/fixtures/album.stub.dart b/mobile/test/fixtures/album.stub.dart index e820f193d5..c6ea199c0f 100644 --- a/mobile/test/fixtures/album.stub.dart +++ b/mobile/test/fixtures/album.stub.dart @@ -1,4 +1,5 @@ import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'asset.stub.dart'; import 'user.stub.dart'; @@ -26,7 +27,7 @@ final class AlbumStub { shared: true, activityEnabled: false, endDate: DateTime(2020), - )..sharedUsers.addAll([UserStub.admin]); + )..sharedUsers.addAll([User.fromDto(UserStub.admin)]); static final oneAsset = Album( name: "album-with-single-asset", @@ -53,7 +54,7 @@ final class AlbumStub { ) ..assets.addAll([AssetStub.image1, AssetStub.image2]) ..activityEnabled = true - ..owner.value = UserStub.admin; + ..owner.value = User.fromDto(UserStub.admin); static final create2020end2020Album = Album( name: "create2020update2020Album", diff --git a/mobile/test/fixtures/exif.stub.dart b/mobile/test/fixtures/exif.stub.dart new file mode 100644 index 0000000000..5ad9a41761 --- /dev/null +++ b/mobile/test/fixtures/exif.stub.dart @@ -0,0 +1,18 @@ +import 'package:immich_mobile/domain/models/exif.model.dart'; + +abstract final class ExifStub { + static final size = const ExifInfo(assetId: 1, fileSize: 1000); + + static final gps = const ExifInfo( + assetId: 2, + latitude: 20, + longitude: 20, + city: 'city', + state: 'state', + country: 'country', + ); + + static final rotated90CW = const ExifInfo(assetId: 3, orientation: "90"); + + static final rotated270CW = const ExifInfo(assetId: 4, orientation: "-90"); +} diff --git a/mobile/test/fixtures/user.stub.dart b/mobile/test/fixtures/user.stub.dart index 38524f782c..88b14dc02e 100644 --- a/mobile/test/fixtures/user.stub.dart +++ b/mobile/test/fixtures/user.stub.dart @@ -1,35 +1,35 @@ -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; -final class UserStub { +abstract final class UserStub { const UserStub._(); - static final admin = User( - id: "admin", - updatedAt: DateTime(2021), + static final admin = UserDto( + uid: "admin", email: "admin@test.com", name: "admin", - avatarColor: AvatarColorEnum.green, - profileImagePath: '', isAdmin: true, + updatedAt: DateTime(2021), + profileImagePath: null, + avatarColor: AvatarColor.green, ); - static final user1 = User( - id: "user1", - updatedAt: DateTime(2022), + static final user1 = UserDto( + uid: "user1", email: "user1@test.com", name: "user1", - avatarColor: AvatarColorEnum.red, - profileImagePath: '', isAdmin: false, + updatedAt: DateTime(2022), + profileImagePath: null, + avatarColor: AvatarColor.red, ); - static final user2 = User( - id: "user2", - updatedAt: DateTime(2023), + static final user2 = UserDto( + uid: "user2", email: "user2@test.com", name: "user2", - avatarColor: AvatarColorEnum.primary, - profileImagePath: '', isAdmin: false, + updatedAt: DateTime(2023), + profileImagePath: null, + avatarColor: AvatarColor.primary, ); } diff --git a/mobile/test/infrastructure/repositories/exif_repository_test.dart b/mobile/test/infrastructure/repositories/exif_repository_test.dart new file mode 100644 index 0000000000..e267d2dac4 --- /dev/null +++ b/mobile/test/infrastructure/repositories/exif_repository_test.dart @@ -0,0 +1,50 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; +import 'package:isar/isar.dart'; + +import '../../fixtures/exif.stub.dart'; +import '../../test_utils.dart'; + +Future _populateExifTable(Isar db) async { + await db.writeTxn(() async { + await db.exifInfos.putAll([ + ExifInfo.fromDto(ExifStub.size), + ExifInfo.fromDto(ExifStub.gps), + ExifInfo.fromDto(ExifStub.rotated90CW), + ExifInfo.fromDto(ExifStub.rotated270CW), + ]); + }); +} + +void main() { + late Isar db; + late IExifInfoRepository sut; + + setUp(() async { + db = await TestUtils.initIsar(); + sut = IsarExifRepository(db); + }); + + group("Return with proper orientation", () { + setUp(() async { + await _populateExifTable(db); + }); + + test("isFlipped true for 90CW", () async { + final exif = await sut.get(ExifStub.rotated90CW.assetId!); + expect(exif!.isFlipped, true); + }); + + test("isFlipped true for 270CW", () async { + final exif = await sut.get(ExifStub.rotated270CW.assetId!); + expect(exif!.isFlipped, true); + }); + + test("isFlipped false for the original non-rotated image", () async { + final exif = await sut.get(ExifStub.size.assetId!); + expect(exif!.isFlipped, false); + }); + }); +} diff --git a/mobile/test/infrastructure/repositories/store_repository_test.dart b/mobile/test/infrastructure/repositories/store_repository_test.dart index 6fd3d3963a..16e0632d83 100644 --- a/mobile/test/infrastructure/repositories/store_repository_test.dart +++ b/mobile/test/infrastructure/repositories/store_repository_test.dart @@ -3,7 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/domain/interfaces/store.interface.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:isar/isar.dart'; @@ -86,7 +86,7 @@ void main() { }); test('converts user', () async { - User? user = await sut.tryGet(StoreKey.currentUser); + UserDto? user = await sut.tryGet(StoreKey.currentUser); expect(user, isNull); await sut.insert(StoreKey.currentUser, _kTestUser); user = await sut.tryGet(StoreKey.currentUser); diff --git a/mobile/test/infrastructure/repository.mock.dart b/mobile/test/infrastructure/repository.mock.dart index 3e33fdac0a..ccae209e4a 100644 --- a/mobile/test/infrastructure/repository.mock.dart +++ b/mobile/test/infrastructure/repository.mock.dart @@ -1,7 +1,14 @@ import 'package:immich_mobile/domain/interfaces/log.interface.dart'; import 'package:immich_mobile/domain/interfaces/store.interface.dart'; +import 'package:immich_mobile/domain/interfaces/user.interface.dart'; +import 'package:immich_mobile/domain/interfaces/user_api.repository.dart'; import 'package:mocktail/mocktail.dart'; class MockStoreRepository extends Mock implements IStoreRepository {} class MockLogRepository extends Mock implements ILogRepository {} + +class MockUserRepository extends Mock implements IUserRepository {} + +// API Repos +class MockUserApiRepository extends Mock implements IUserApiRepository {} diff --git a/mobile/test/modules/activity/activities_page_test.dart b/mobile/test/modules/activity/activities_page_test.dart index 6b20692bcd..cf9238d205 100644 --- a/mobile/test/modules/activity/activities_page_test.dart +++ b/mobile/test/modules/activity/activities_page_test.dart @@ -9,7 +9,7 @@ import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/models/activities/activity.model.dart'; import 'package:immich_mobile/pages/common/activities.page.dart'; @@ -96,7 +96,7 @@ void main() { await db.writeTxn(() async { await db.clear(); // Save all assets - await db.users.put(UserStub.admin); + await db.users.put(User.fromDto(UserStub.admin)); await db.assets.putAll([AssetStub.image1, AssetStub.image2]); await db.albums.put(AlbumStub.twoAsset); await AlbumStub.twoAsset.owner.save(); diff --git a/mobile/test/modules/extensions/asset_extensions_test.dart b/mobile/test/modules/extensions/asset_extensions_test.dart index d2b9b93d62..dd334c7b9d 100644 --- a/mobile/test/modules/extensions/asset_extensions_test.dart +++ b/mobile/test/modules/extensions/asset_extensions_test.dart @@ -1,7 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/extensions/asset_extensions.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; +import 'package:immich_mobile/extensions/asset_extensions.dart'; import 'package:timezone/data/latest.dart'; import 'package:timezone/timezone.dart'; diff --git a/mobile/test/modules/shared/shared_mocks.dart b/mobile/test/modules/shared/shared_mocks.dart index 013232da3e..f50fde7040 100644 --- a/mobile/test/modules/shared/shared_mocks.dart +++ b/mobile/test/modules/shared/shared_mocks.dart @@ -1,13 +1,13 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:mocktail/mocktail.dart'; -class MockCurrentUserProvider extends StateNotifier +class MockCurrentUserProvider extends StateNotifier with Mock implements CurrentUserProvider { MockCurrentUserProvider() : super(null); @override - set state(User? user) => super.state = user; + set state(UserDto? user) => super.state = user; } diff --git a/mobile/test/modules/shared/sync_service_test.dart b/mobile/test/modules/shared/sync_service_test.dart index a58de21613..f74c815997 100644 --- a/mobile/test/modules/shared/sync_service_test.dart +++ b/mobile/test/modules/shared/sync_service_test.dart @@ -1,19 +1,21 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/interfaces/user.interface.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; -import 'package:immich_mobile/interfaces/user.interface.dart'; +import 'package:immich_mobile/interfaces/partner_api.interface.dart'; import 'package:immich_mobile/services/sync.service.dart'; import 'package:mocktail/mocktail.dart'; +import '../../infrastructure/repository.mock.dart'; import '../../repository.mocks.dart'; import '../../service.mocks.dart'; import '../../test_utils.dart'; @@ -56,8 +58,13 @@ void main() { final MockAlbumMediaRepository albumMediaRepository = MockAlbumMediaRepository(); final MockAlbumApiRepository albumApiRepository = MockAlbumApiRepository(); - final owner = User( - id: "1", + final MockPartnerApiRepository partnerApiRepository = + MockPartnerApiRepository(); + final MockUserApiRepository userApiRepository = MockUserApiRepository(); + final MockPartnerRepository partnerRepository = MockPartnerRepository(); + + final owner = UserDto( + uid: "1", updatedAt: DateTime.now(), email: "a@b.c", name: "first last", @@ -92,21 +99,24 @@ void main() { albumRepository, assetRepository, exifInfoRepository, + partnerRepository, userRepository, + StoreService.I, eTagRepository, + partnerApiRepository, + userApiRepository, ); - when(() => eTagRepository.get(owner.isarId)) - .thenAnswer((_) async => ETag(id: owner.id, time: DateTime.now())); + when(() => eTagRepository.get(owner.id)) + .thenAnswer((_) async => ETag(id: owner.uid, time: DateTime.now())); when(() => eTagRepository.deleteByIds(["1"])).thenAnswer((_) async {}); when(() => eTagRepository.upsertAll(any())).thenAnswer((_) async {}); - when(() => userRepository.me()).thenAnswer((_) async => owner); - when(() => userRepository.getAll(sortBy: UserSort.id)) - .thenAnswer((_) async => [owner]); - when(() => userRepository.getAllAccessible()) + when(() => partnerRepository.getSharedWith()).thenAnswer((_) async => []); + when(() => userRepository.getAll(sortBy: SortUserBy.id)) .thenAnswer((_) async => [owner]); + when(() => userRepository.getAll()).thenAnswer((_) async => [owner]); when( () => assetRepository.getAll( - ownerId: owner.isarId, + ownerId: owner.id, sortBy: AssetSort.checksum, ), ).thenAnswer((_) async => initialAssets); @@ -122,6 +132,10 @@ void main() { when(() => assetRepository.transaction(any())).thenAnswer( (call) => (call.positionalArguments.first as Function).call(), ); + when(() => userApiRepository.getAll()).thenAnswer((_) async => [owner]); + registerFallbackValue(Direction.sharedByMe); + when(() => partnerApiRepository.getAll(any())) + .thenAnswer((_) async => []); }); test('test inserting existing assets', () async { final List remoteAssets = [ @@ -133,7 +147,6 @@ void main() { users: [owner], getChangedAssets: _failDiff, loadAssets: (u, d) => remoteAssets, - refreshUsers: () => [owner], ); expect(c1, isFalse); verifyNever(() => assetRepository.updateAll(any())); @@ -152,7 +165,6 @@ void main() { users: [owner], getChangedAssets: _failDiff, loadAssets: (u, d) => remoteAssets, - refreshUsers: () => [owner], ); expect(c1, isTrue); final updatedAsset = initialAssets[3].updatedCopy(remoteAssets[3]); @@ -175,12 +187,11 @@ void main() { users: [owner], getChangedAssets: _failDiff, loadAssets: (u, d) => remoteAssets, - refreshUsers: () => [owner], ); expect(c1, isTrue); when( () => assetRepository.getAll( - ownerId: owner.isarId, + ownerId: owner.id, sortBy: AssetSort.checksum, ), ).thenAnswer((_) async => remoteAssets); @@ -188,13 +199,12 @@ void main() { users: [owner], getChangedAssets: _failDiff, loadAssets: (u, d) => remoteAssets, - refreshUsers: () => [owner], ); expect(c2, isFalse); final currentState = [...remoteAssets]; when( () => assetRepository.getAll( - ownerId: owner.isarId, + ownerId: owner.id, sortBy: AssetSort.checksum, ), ).thenAnswer((_) async => currentState); @@ -203,7 +213,6 @@ void main() { users: [owner], getChangedAssets: _failDiff, loadAssets: (u, d) => remoteAssets, - refreshUsers: () => [owner], ); expect(c3, isTrue); remoteAssets.add(makeAsset(checksum: "k", remoteId: "2-1e")); @@ -212,7 +221,6 @@ void main() { users: [owner], getChangedAssets: _failDiff, loadAssets: (u, d) => remoteAssets, - refreshUsers: () => [owner], ); expect(c4, isTrue); }); @@ -243,7 +251,6 @@ void main() { users: [owner], getChangedAssets: (user, since) async => (toUpsert, toDelete), loadAssets: (user, date) => throw Exception(), - refreshUsers: () => throw Exception(), ); expect(c, isTrue); verify(() => assetRepository.updateAll(expected)); @@ -252,7 +259,7 @@ void main() { } Future<(List?, List?)> _failDiff( - List user, + List user, DateTime time, ) => Future.value((null, null)); diff --git a/mobile/test/repository.mocks.dart b/mobile/test/repository.mocks.dart index bad7d3ebab..e4e99ffcb8 100644 --- a/mobile/test/repository.mocks.dart +++ b/mobile/test/repository.mocks.dart @@ -1,3 +1,4 @@ +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/interfaces/album_api.interface.dart'; import 'package:immich_mobile/interfaces/album_media.interface.dart'; @@ -7,17 +8,15 @@ import 'package:immich_mobile/interfaces/auth.interface.dart'; import 'package:immich_mobile/interfaces/auth_api.interface.dart'; import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/interfaces/etag.interface.dart'; -import 'package:immich_mobile/interfaces/exif_info.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart'; -import 'package:immich_mobile/interfaces/user.interface.dart'; +import 'package:immich_mobile/interfaces/partner_api.interface.dart'; +import 'package:immich_mobile/interfaces/partner.interface.dart'; import 'package:mocktail/mocktail.dart'; class MockAlbumRepository extends Mock implements IAlbumRepository {} class MockAssetRepository extends Mock implements IAssetRepository {} -class MockUserRepository extends Mock implements IUserRepository {} - class MockBackupRepository extends Mock implements IBackupAlbumRepository {} class MockExifInfoRepository extends Mock implements IExifInfoRepository {} @@ -35,3 +34,7 @@ class MockAlbumApiRepository extends Mock implements IAlbumApiRepository {} class MockAuthApiRepository extends Mock implements IAuthApiRepository {} class MockAuthRepository extends Mock implements IAuthRepository {} + +class MockPartnerApiRepository extends Mock implements IPartnerApiRepository {} + +class MockPartnerRepository extends Mock implements IPartnerRepository {} diff --git a/mobile/test/service.mocks.dart b/mobile/test/service.mocks.dart index cc9d657e9e..33c325b105 100644 --- a/mobile/test/service.mocks.dart +++ b/mobile/test/service.mocks.dart @@ -3,14 +3,11 @@ import 'package:immich_mobile/services/entity.service.dart'; import 'package:immich_mobile/services/hash.service.dart'; import 'package:immich_mobile/services/network.service.dart'; import 'package:immich_mobile/services/sync.service.dart'; -import 'package:immich_mobile/services/user.service.dart'; import 'package:mocktail/mocktail.dart'; import 'package:openapi/api.dart'; class MockApiService extends Mock implements ApiService {} -class MockUserService extends Mock implements UserService {} - class MockSyncService extends Mock implements SyncService {} class MockHashService extends Mock implements HashService {} diff --git a/mobile/test/services/album.service_test.dart b/mobile/test/services/album.service_test.dart index 983b355dcb..993456ad99 100644 --- a/mobile/test/services/album.service_test.dart +++ b/mobile/test/services/album.service_test.dart @@ -3,6 +3,7 @@ import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/services/album.service.dart'; import 'package:mocktail/mocktail.dart'; +import '../domain/service.mock.dart'; import '../fixtures/album.stub.dart'; import '../fixtures/asset.stub.dart'; import '../fixtures/user.stub.dart'; @@ -38,7 +39,6 @@ void main() { ); sut = AlbumService( - userService, syncService, entityService, albumRepository, @@ -84,7 +84,7 @@ void main() { group('refreshRemoteAlbums', () { test('is working', () async { - when(() => userService.getUsersFromServer()).thenAnswer((_) async => []); + when(() => syncService.getUsersFromServer()).thenAnswer((_) async => []); when(() => syncService.syncUsersFromServer(any())) .thenAnswer((_) async => true); when(() => albumApiRepository.getAll(shared: true)) @@ -102,7 +102,7 @@ void main() { ).thenAnswer((_) async => true); final result = await sut.refreshRemoteAlbums(); expect(result, true); - verify(() => userService.getUsersFromServer()).called(1); + verify(() => syncService.getUsersFromServer()).called(1); verify(() => syncService.syncUsersFromServer([])).called(1); verify(() => albumApiRepository.getAll(shared: true)).called(1); verify(() => albumApiRepository.getAll(shared: null)).called(1); @@ -146,7 +146,7 @@ void main() { () => albumApiRepository.create( "name", assetIds: [AssetStub.image1.remoteId!], - sharedUserIds: [UserStub.user1.id], + sharedUserIds: [UserStub.user1.uid], ), ).called(1); verify( @@ -204,7 +204,7 @@ void main() { when( () => albumRepository.addUsers( AlbumStub.emptyAlbum, - AlbumStub.emptyAlbum.sharedUsers.toList(), + AlbumStub.emptyAlbum.sharedUsers.map((u) => u.toDto()).toList(), ), ).thenAnswer((_) async => AlbumStub.emptyAlbum); @@ -214,7 +214,7 @@ void main() { final result = await sut.addUsers( AlbumStub.emptyAlbum, - [UserStub.user2.id], + [UserStub.user2.uid], ); expect(result, true); diff --git a/mobile/test/services/entity.service_test.dart b/mobile/test/services/entity.service_test.dart index 8c8b49a7e0..fd99a4faee 100644 --- a/mobile/test/services/entity.service_test.dart +++ b/mobile/test/services/entity.service_test.dart @@ -1,9 +1,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/services/entity.service.dart'; import 'package:mocktail/mocktail.dart'; + import '../fixtures/asset.stub.dart'; import '../fixtures/user.stub.dart'; +import '../infrastructure/repository.mock.dart'; import '../repository.mocks.dart'; void main() { @@ -33,25 +36,32 @@ void main() { ) ..remoteThumbnailAssetId = AssetStub.image1.remoteId ..assets.addAll([AssetStub.image1, AssetStub.image1]) - ..owner.value = UserStub.user1 - ..sharedUsers.addAll([UserStub.admin, UserStub.admin]); + ..owner.value = User.fromDto(UserStub.user1) + ..sharedUsers.addAll( + [User.fromDto(UserStub.admin), User.fromDto(UserStub.admin)], + ); - when(() => userRepository.get(album.ownerId!)) + when(() => userRepository.get(any())) + .thenAnswer((_) async => UserStub.admin); + when(() => userRepository.getByUserId(any())) .thenAnswer((_) async => UserStub.admin); when(() => assetRepository.getByRemoteId(AssetStub.image1.remoteId!)) .thenAnswer((_) async => AssetStub.image1); - when(() => userRepository.getByIds(any())) + when(() => userRepository.getByUserIds(any())) .thenAnswer((_) async => [UserStub.user1, UserStub.user2]); when(() => assetRepository.getAllByRemoteId(any())) .thenAnswer((_) async => [AssetStub.image1, AssetStub.image2]); await sut.fillAlbumWithDatabaseEntities(album); - expect(album.owner.value, UserStub.admin); + expect(album.owner.value?.toDto(), UserStub.admin); expect(album.thumbnail.value, AssetStub.image1); - expect(album.remoteUsers.toSet(), {UserStub.user1, UserStub.user2}); + expect( + album.remoteUsers.map((u) => u.toDto()).toSet(), + {UserStub.user1, UserStub.user2}, + ); expect(album.remoteAssets.toSet(), {AssetStub.image1, AssetStub.image2}); }); diff --git a/mobile/test/test_utils.dart b/mobile/test/test_utils.dart index 825d77190b..a5a89a2440 100644 --- a/mobile/test/test_utils.dart +++ b/mobile/test/test_utils.dart @@ -11,11 +11,11 @@ import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; -import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; import 'package:immich_mobile/infrastructure/entities/log.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:isar/isar.dart'; import 'package:mocktail/mocktail.dart'; diff --git a/open-api/bin/generate-open-api.sh b/open-api/bin/generate-open-api.sh index bf8b24b557..e2badc6dff 100755 --- a/open-api/bin/generate-open-api.sh +++ b/open-api/bin/generate-open-api.sh @@ -9,7 +9,11 @@ function dart { wget -O native_class.mustache https://raw.githubusercontent.com/OpenAPITools/openapi-generator/$OPENAPI_GENERATOR_VERSION/modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache patch --no-backup-if-mismatch -u native_class.mustache dart_constructor}} +{{#vars}} + {{#description}} + /// {{{.}}} + {{/description}} + {{^isEnum}} + {{#minimum}} + {{#description}} + /// + {{/description}} + /// Minimum value: {{{.}}} + {{/minimum}} + {{#maximum}} + {{#description}} + {{^minimum}} + /// + {{/minimum}} + {{/description}} + /// Maximum value: {{{.}}} + {{/maximum}} + {{^isNullable}} + {{^required}} + {{^defaultValue}} + /// + /// 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. + /// + {{/defaultValue}} + {{/required}} + {{/isNullable}} + {{/isEnum}} + {{{datatypeWithEnum}}}{{#isNullable}}?{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}?{{/defaultValue}}{{/required}}{{/isNullable}} {{{name}}}; + +{{/vars}} + @override + bool operator ==(Object other) => identical(this, other) || other is {{{classname}}} && + {{#vars}} + {{#isMap}}_deepEquality.equals(other.{{{name}}}, {{{name}}}){{/isMap}}{{^isMap}}{{#isArray}}_deepEquality.equals(other.{{{name}}}, {{{name}}}){{/isArray}}{{^isArray}}other.{{{name}}} == {{{name}}}{{/isArray}}{{/isMap}}{{^-last}} &&{{/-last}}{{#-last}};{{/-last}} + {{/vars}} + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + {{#vars}} + ({{#isNullable}}{{{name}}} == null ? 0 : {{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}{{{name}}} == null ? 0 : {{/defaultValue}}{{/required}}{{/isNullable}}{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.hashCode){{^-last}} +{{/-last}}{{#-last}};{{/-last}} + {{/vars}} + + @override + String toString() => '{{{classname}}}[{{#vars}}{{{name}}}=${{{name}}}{{^-last}}, {{/-last}}{{/vars}}]'; + + Map toJson() { + final json = {}; + {{#vars}} + {{#isNullable}} + if (this.{{{name}}} != null) { + {{/isNullable}} + {{^isNullable}} + {{^required}} + {{^defaultValue}} + if (this.{{{name}}} != null) { + {{/defaultValue}} + {{/required}} + {{/isNullable}} + {{#isDateTime}} + {{#pattern}} + json[r'{{{baseName}}}'] = _isEpochMarker(r'{{{pattern}}}') + ? this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.millisecondsSinceEpoch + : this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.toUtc().toIso8601String(); + {{/pattern}} + {{^pattern}} + json[r'{{{baseName}}}'] = this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.toUtc().toIso8601String(); + {{/pattern}} + {{/isDateTime}} + {{#isDate}} + {{#pattern}} + json[r'{{{baseName}}}'] = _isEpochMarker(r'{{{pattern}}}') + ? this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.millisecondsSinceEpoch + : _dateFormatter.format(this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.toUtc()); + {{/pattern}} + {{^pattern}} + json[r'{{{baseName}}}'] = _dateFormatter.format(this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.toUtc()); + {{/pattern}} + {{/isDate}} + {{^isDateTime}} + {{^isDate}} + json[r'{{{baseName}}}'] = this.{{{name}}}{{#isArray}}{{#uniqueItems}}{{#isNullable}}!{{/isNullable}}.toList(growable: false){{/uniqueItems}}{{/isArray}}; + {{/isDate}} + {{/isDateTime}} + {{#isNullable}} + } else { + json[r'{{{baseName}}}'] = null; + } + {{/isNullable}} + {{^isNullable}} + {{^required}} + {{^defaultValue}} + } else { + json[r'{{{baseName}}}'] = null; + } + {{/defaultValue}} + {{/required}} + {{/isNullable}} + {{/vars}} + return json; + } + + /// Returns a new [{{{classname}}}] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static {{{classname}}}? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + // Ensure that the map contains the required keys. + // Note 1: the values aren't checked for validity beyond being non-null. + // Note 2: this code is stripped in release mode! + assert(() { + requiredKeys.forEach((key) { + assert(json.containsKey(key), 'Required key "{{{classname}}}[$key]" is missing from JSON.'); + assert(json[key] != null, 'Required key "{{{classname}}}[$key]" has a null value in JSON.'); + }); + return true; + }()); + + return {{{classname}}}( + {{#vars}} + {{#isDateTime}} + {{{name}}}: mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/isDateTime}} + {{#isDate}} + {{{name}}}: mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/isDate}} + {{^isDateTime}} + {{^isDate}} + {{#complexType}} + {{#isArray}} + {{#items.isArray}} + {{{name}}}: json[r'{{{baseName}}}'] is List + ? (json[r'{{{baseName}}}'] as List).map((e) => + {{#items.complexType}} + {{items.complexType}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}} + {{/items.complexType}} + {{^items.complexType}} + e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}>[]{{/items.isNullable}} : (e as List).cast<{{items.items.dataType}}>() + {{/items.complexType}} + ).toList() + : {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}}, + {{/items.isArray}} + {{^items.isArray}} + {{{name}}}: {{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}, + {{/items.isArray}} + {{/isArray}} + {{^isArray}} + {{#isMap}} + {{#items.isArray}} + {{{name}}}: json[r'{{{baseName}}}'] == null + ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} + {{#items.complexType}} + : {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}']), + {{/items.complexType}} + {{^items.complexType}} + : mapCastOfType(json, r'{{{baseName}}}'), + {{/items.complexType}} + {{/items.isArray}} + {{^items.isArray}} + {{#items.isMap}} + {{#items.complexType}} + {{{name}}}: {{items.complexType}}.mapFromJson(json[r'{{{baseName}}}']), + {{/items.complexType}} + {{^items.complexType}} + {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/items.complexType}} + {{/items.isMap}} + {{^items.isMap}} + {{#items.complexType}} + {{{name}}}: {{{items.complexType}}}.mapFromJson(json[r'{{{baseName}}}']), + {{/items.complexType}} + {{^items.complexType}} + {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/items.complexType}} + {{/items.isMap}} + {{/items.isArray}} + {{/isMap}} + {{^isMap}} + {{#isBinary}} + {{{name}}}: null, // No support for decoding binary content from JSON + {{/isBinary}} + {{^isBinary}} + {{{name}}}: {{{complexType}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/isBinary}} + {{/isMap}} + {{/isArray}} + {{/complexType}} + {{^complexType}} + {{#isArray}} + {{#isEnum}} + {{{name}}}: {{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}, + {{/isEnum}} + {{^isEnum}} + {{{name}}}: json[r'{{{baseName}}}'] is Iterable + ? (json[r'{{{baseName}}}'] as Iterable).cast<{{{items.datatype}}}>().{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}} + : {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}, + {{/isEnum}} + {{/isArray}} + {{^isArray}} + {{#isMap}} + {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/isMap}} + {{^isMap}} + {{#isNumber}} + {{{name}}}: {{#isNullable}}json[r'{{{baseName}}}'] == null + ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} + : {{/isNullable}}{{{datatypeWithEnum}}}.parse('${json[r'{{{baseName}}}']}'), + {{/isNumber}} + {{^isNumber}} + {{^isEnum}} + {{{name}}}: mapValueOfType<{{{datatypeWithEnum}}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/isEnum}} + {{#isEnum}} + {{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/isEnum}} + {{/isNumber}} + {{/isMap}} + {{/isArray}} + {{/complexType}} + {{/isDate}} + {{/isDateTime}} + {{/vars}} + ); + } + return null; + } + + static List<{{{classname}}}> listFromJson(dynamic json, {bool growable = false,}) { + final result = <{{{classname}}}>[]; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = {{{classname}}}.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 = {{{classname}}}.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of {{{classname}}}-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] = {{{classname}}}.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { +{{#vars}} + {{#required}} + '{{{baseName}}}', + {{/required}} +{{/vars}} + }; +} +{{#vars}} + {{^isModel}} + {{#isEnum}} + {{^isContainer}} + +{{>serialization/native/native_enum_inline}} + {{/isContainer}} + {{#isContainer}} + {{#mostInnerItems}} + +{{>serialization/native/native_enum_inline}} + {{/mostInnerItems}} + {{/isContainer}} + {{/isEnum}} + {{/isModel}} +{{/vars}} diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 7212d3b7f7..dec1dbb7cd 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -7041,6 +7041,147 @@ ] } }, + "/timeline/liteBucket": { + "get": { + "operationId": "getLiteTimeBucket", + "parameters": [ + { + "name": "albumId", + "required": false, + "in": "query", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "name": "isArchived", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isFavorite", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isTrashed", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "key", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "order", + "required": false, + "in": "query", + "schema": { + "$ref": "#/components/schemas/AssetOrder" + } + }, + { + "name": "personId", + "required": false, + "in": "query", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "name": "size", + "required": true, + "in": "query", + "schema": { + "$ref": "#/components/schemas/TimeBucketSize" + } + }, + { + "name": "tagId", + "required": false, + "in": "query", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "name": "timeBucket", + "required": true, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "required": false, + "in": "query", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "name": "withPartners", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "withStacked", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LiteTimeBucketResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Timeline" + ] + } + }, "/trash/empty": { "post": { "operationId": "emptyTrash", @@ -7655,7 +7796,7 @@ "info": { "title": "Immich", "description": "Immich API", - "version": "1.128.0", + "version": "1.129.0", "contact": {} }, "tags": [], @@ -9833,6 +9974,64 @@ ], "type": "object" }, + "LiteTimeBucketAssetResponseDto": { + "properties": { + "height": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "isArchived": { + "type": "boolean" + }, + "isFavorite": { + "type": "boolean" + }, + "isTrashed": { + "type": "boolean" + }, + "localDateTime": { + "format": "date-time", + "type": "string" + }, + "thumbhash": { + "nullable": true, + "type": "string" + }, + "width": { + "type": "integer" + } + }, + "required": [ + "height", + "id", + "isArchived", + "isFavorite", + "isTrashed", + "localDateTime", + "width" + ], + "type": "object" + }, + "LiteTimeBucketResponseDto": { + "properties": { + "hasNextPage": { + "type": "boolean" + }, + "liteAssets": { + "items": { + "$ref": "#/components/schemas/LiteTimeBucketAssetResponseDto" + }, + "type": "array" + } + }, + "required": [ + "hasNextPage", + "liteAssets" + ], + "type": "object" + }, "LogLevel": { "enum": [ "verbose", @@ -9918,7 +10117,8 @@ "tag-cleanup", "user-cleanup", "memory-cleanup", - "memory-create" + "memory-create", + "backup-database" ], "type": "string" }, @@ -12049,12 +12249,228 @@ ], "type": "object" }, + "SyncAssetDeleteV1": { + "properties": { + "assetId": { + "type": "string" + } + }, + "required": [ + "assetId" + ], + "type": "object" + }, + "SyncAssetExifV1": { + "properties": { + "assetId": { + "type": "string" + }, + "city": { + "nullable": true, + "type": "string" + }, + "country": { + "nullable": true, + "type": "string" + }, + "dateTimeOriginal": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "description": { + "nullable": true, + "type": "string" + }, + "exifImageHeight": { + "nullable": true, + "type": "integer" + }, + "exifImageWidth": { + "nullable": true, + "type": "integer" + }, + "exposureTime": { + "nullable": true, + "type": "string" + }, + "fNumber": { + "nullable": true, + "type": "integer" + }, + "fileSizeInByte": { + "nullable": true, + "type": "integer" + }, + "focalLength": { + "nullable": true, + "type": "integer" + }, + "fps": { + "nullable": true, + "type": "integer" + }, + "iso": { + "nullable": true, + "type": "integer" + }, + "latitude": { + "nullable": true, + "type": "integer" + }, + "lensModel": { + "nullable": true, + "type": "string" + }, + "longitude": { + "nullable": true, + "type": "integer" + }, + "make": { + "nullable": true, + "type": "string" + }, + "model": { + "nullable": true, + "type": "string" + }, + "modifyDate": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "orientation": { + "nullable": true, + "type": "string" + }, + "profileDescription": { + "nullable": true, + "type": "string" + }, + "projectionType": { + "nullable": true, + "type": "string" + }, + "rating": { + "nullable": true, + "type": "integer" + }, + "state": { + "nullable": true, + "type": "string" + }, + "timeZone": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "assetId", + "city", + "country", + "dateTimeOriginal", + "description", + "exifImageHeight", + "exifImageWidth", + "exposureTime", + "fNumber", + "fileSizeInByte", + "focalLength", + "fps", + "iso", + "latitude", + "lensModel", + "longitude", + "make", + "model", + "modifyDate", + "orientation", + "profileDescription", + "projectionType", + "rating", + "state", + "timeZone" + ], + "type": "object" + }, + "SyncAssetV1": { + "properties": { + "checksum": { + "type": "string" + }, + "deletedAt": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "fileCreatedAt": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "fileModifiedAt": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "id": { + "type": "string" + }, + "isFavorite": { + "type": "boolean" + }, + "isVisible": { + "type": "boolean" + }, + "localDateTime": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "thumbhash": { + "nullable": true, + "type": "string" + }, + "type": { + "enum": [ + "IMAGE", + "VIDEO", + "AUDIO", + "OTHER" + ], + "type": "string" + } + }, + "required": [ + "checksum", + "deletedAt", + "fileCreatedAt", + "fileModifiedAt", + "id", + "isFavorite", + "isVisible", + "localDateTime", + "ownerId", + "thumbhash", + "type" + ], + "type": "object" + }, "SyncEntityType": { "enum": [ "UserV1", "UserDeleteV1", "PartnerV1", - "PartnerDeleteV1" + "PartnerDeleteV1", + "AssetV1", + "AssetDeleteV1", + "AssetExifV1", + "PartnerAssetV1", + "PartnerAssetDeleteV1", + "PartnerAssetExifV1" ], "type": "string" }, @@ -12095,7 +12511,11 @@ "SyncRequestType": { "enum": [ "UsersV1", - "PartnersV1" + "PartnersV1", + "AssetsV1", + "AssetExifsV1", + "PartnerAssetsV1", + "PartnerAssetExifsV1" ], "type": "string" }, diff --git a/open-api/templates/mobile/api.mustache b/open-api/templates/mobile/api.mustache new file mode 100644 index 0000000000..ac32571123 --- /dev/null +++ b/open-api/templates/mobile/api.mustache @@ -0,0 +1,194 @@ +{{>header}} +{{>part_of}} +{{#operations}} + +class {{{classname}}} { + {{{classname}}}([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + {{#operation}} + + {{#summary}} + /// {{{.}}} + {{/summary}} + {{#notes}} + {{#summary}} + /// + {{/summary}} + /// {{{notes}}} + /// + /// Note: This method returns the HTTP [Response]. + {{/notes}} + {{^notes}} + {{#summary}} + /// + /// Note: This method returns the HTTP [Response]. + {{/summary}} + {{^summary}} + /// Performs an HTTP '{{{httpMethod}}} {{{path}}}' operation and returns the [Response]. + {{/summary}} + {{/notes}} + {{#hasParams}} + {{#summary}} + /// + {{/summary}} + {{^summary}} + {{#notes}} + /// + {{/notes}} + {{/summary}} + /// Parameters: + /// + {{/hasParams}} + {{#allParams}} + /// * [{{{dataType}}}] {{{paramName}}}{{#required}} (required){{/required}}{{#optional}} (optional){{/optional}}: + {{#description}} + /// {{{.}}} + {{/description}} + {{^-last}} + /// + {{/-last}} + {{/allParams}} + Future {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async { + // ignore: prefer_const_declarations + final apiPath = r'{{{path}}}'{{#pathParams}} + .replaceAll({{=<% %>=}}'{<% baseName %>}'<%={{ }}=%>, {{{paramName}}}{{^isString}}.toString(){{/isString}}){{/pathParams}}; + + // ignore: prefer_final_locals + Object? postBody{{#bodyParam}} = {{{paramName}}}{{/bodyParam}}; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + {{#hasQueryParams}} + + {{#queryParams}} + {{^required}} + if ({{{paramName}}} != null) { + {{/required}} + queryParams.addAll(_queryParams('{{{collectionFormat}}}', '{{{baseName}}}', {{{paramName}}})); + {{^required}} + } + {{/required}} + {{/queryParams}} + {{/hasQueryParams}} + {{#hasHeaderParams}} + + {{#headerParams}} + {{#required}} + headerParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}}); + {{/required}} + {{^required}} + if ({{{paramName}}} != null) { + headerParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}}); + } + {{/required}} + {{/headerParams}} + {{/hasHeaderParams}} + + const contentTypes = [{{#prioritizedContentTypes}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/prioritizedContentTypes}}]; + + {{#isMultipart}} + bool hasFields = false; + final mp = MultipartRequest('{{{httpMethod}}}', Uri.parse(apiPath)); + {{#formParams}} + {{^isFile}} + if ({{{paramName}}} != null) { + hasFields = true; + mp.fields[r'{{{baseName}}}'] = parameterToString({{{paramName}}}); + } + {{/isFile}} + {{#isFile}} + if ({{{paramName}}} != null) { + hasFields = true; + mp.fields[r'{{{baseName}}}'] = {{{paramName}}}.field; + mp.files.add({{{paramName}}}); + } + {{/isFile}} + {{/formParams}} + if (hasFields) { + postBody = mp; + } + {{/isMultipart}} + {{^isMultipart}} + {{#formParams}} + {{^isFile}} + if ({{{paramName}}} != null) { + formParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}}); + } + {{/isFile}} + {{/formParams}} + {{/isMultipart}} + + return apiClient.invokeAPI( + apiPath, + '{{{httpMethod}}}', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + {{#summary}} + /// {{{.}}} + {{/summary}} + {{#notes}} + {{#summary}} + /// + {{/summary}} + /// {{{notes}}} + {{/notes}} + {{#hasParams}} + {{#summary}} + /// + {{/summary}} + {{^summary}} + {{#notes}} + /// + {{/notes}} + {{/summary}} + /// Parameters: + /// + {{/hasParams}} + {{#allParams}} + /// * [{{{dataType}}}] {{{paramName}}}{{#required}} (required){{/required}}{{#optional}} (optional){{/optional}}: + {{#description}} + /// {{{.}}} + {{/description}} + {{^-last}} + /// + {{/-last}} + {{/allParams}} + Future<{{#returnType}}{{{.}}}?{{/returnType}}{{^returnType}}void{{/returnType}}> {{{nickname}}}({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async { + final response = await {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}} {{#allParams}}{{^required}}{{{paramName}}}: {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} {{/hasOptionalParams}}); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + {{#returnType}} + // 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) { + {{#native_serialization}} + {{#isArray}} + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, '{{{returnType}}}') as List) + .cast<{{{returnBaseType}}}>() + .{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}}; + {{/isArray}} + {{^isArray}} + {{#isMap}} + return {{{returnType}}}.from(await apiClient.deserializeAsync(await _decodeBodyBytes(response), '{{{returnType}}}'),); + {{/isMap}} + {{^isMap}} + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), '{{{returnType}}}',) as {{{returnType}}}; + {{/isMap}}{{/isArray}}{{/native_serialization}} + } + return null; + {{/returnType}} + } + {{/operation}} +} +{{/operations}} diff --git a/open-api/templates/mobile/api.mustache.patch b/open-api/templates/mobile/api.mustache.patch new file mode 100644 index 0000000000..e3f888d6d7 --- /dev/null +++ b/open-api/templates/mobile/api.mustache.patch @@ -0,0 +1,29 @@ +--- api.mustache 2025-01-22 05:50:25 ++++ api.mustache.modified 2025-01-22 05:52:23 +@@ -51,7 +51,7 @@ + {{/allParams}} + Future {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async { + // ignore: prefer_const_declarations +- final path = r'{{{path}}}'{{#pathParams}} ++ final apiPath = r'{{{path}}}'{{#pathParams}} + .replaceAll({{=<% %>=}}'{<% baseName %>}'<%={{ }}=%>, {{{paramName}}}{{^isString}}.toString(){{/isString}}){{/pathParams}}; + + // ignore: prefer_final_locals +@@ -90,7 +90,7 @@ + + {{#isMultipart}} + bool hasFields = false; +- final mp = MultipartRequest('{{{httpMethod}}}', Uri.parse(path)); ++ final mp = MultipartRequest('{{{httpMethod}}}', Uri.parse(apiPath)); + {{#formParams}} + {{^isFile}} + if ({{{paramName}}} != null) { +@@ -121,7 +121,7 @@ + {{/isMultipart}} + + return apiClient.invokeAPI( +- path, ++ apiPath, + '{{{httpMethod}}}', + queryParams, + postBody, diff --git a/open-api/typescript-sdk/package-lock.json b/open-api/typescript-sdk/package-lock.json index c0d0319738..149c1ed2ce 100644 --- a/open-api/typescript-sdk/package-lock.json +++ b/open-api/typescript-sdk/package-lock.json @@ -1,18 +1,18 @@ { "name": "@immich/sdk", - "version": "1.128.0", + "version": "1.129.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@immich/sdk", - "version": "1.128.0", + "version": "1.129.0", "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "typescript": "^5.3.3" } }, @@ -23,9 +23,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.13.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.8.tgz", - "integrity": "sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==", + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", "dev": true, "license": "MIT", "dependencies": { @@ -33,9 +33,9 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/open-api/typescript-sdk/package.json b/open-api/typescript-sdk/package.json index b4b45576f8..b3fef2a87e 100644 --- a/open-api/typescript-sdk/package.json +++ b/open-api/typescript-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@immich/sdk", - "version": "1.128.0", + "version": "1.129.0", "description": "Auto-generated TypeScript SDK for the Immich API", "type": "module", "main": "./build/index.js", @@ -19,7 +19,7 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "typescript": "^5.3.3" }, "repository": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 8aecbe9816..d0465f11f5 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -1,6 +1,6 @@ /** * Immich - * 1.128.0 + * 1.129.0 * DO NOT MODIFY - This file has been generated using oazapfts. * See https://www.npmjs.com/package/oazapfts */ @@ -1373,6 +1373,20 @@ export type TimeBucketResponseDto = { count: number; timeBucket: string; }; +export type LiteTimeBucketAssetResponseDto = { + height: number; + id: string; + isArchived: boolean; + isFavorite: boolean; + isTrashed: boolean; + localDateTime: string; + thumbhash?: string | null; + width: number; +}; +export type LiteTimeBucketResponseDto = { + hasNextPage: boolean; + liteAssets: LiteTimeBucketAssetResponseDto[]; +}; export type TrashResponseDto = { count: number; }; @@ -3260,6 +3274,42 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key ...opts })); } +export function getLiteTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, tagId, timeBucket, userId, withPartners, withStacked }: { + albumId?: string; + isArchived?: boolean; + isFavorite?: boolean; + isTrashed?: boolean; + key?: string; + order?: AssetOrder; + personId?: string; + size: TimeBucketSize; + tagId?: string; + timeBucket: string; + userId?: string; + withPartners?: boolean; + withStacked?: boolean; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: LiteTimeBucketResponseDto; + }>(`/timeline/liteBucket${QS.query(QS.explode({ + albumId, + isArchived, + isFavorite, + isTrashed, + key, + order, + personId, + size, + tagId, + timeBucket, + userId, + withPartners, + withStacked + }))}`, { + ...opts + })); +} export function emptyTrash(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -3580,7 +3630,8 @@ export enum ManualJobName { TagCleanup = "tag-cleanup", UserCleanup = "user-cleanup", MemoryCleanup = "memory-cleanup", - MemoryCreate = "memory-create" + MemoryCreate = "memory-create", + BackupDatabase = "backup-database" } export enum JobName { ThumbnailGeneration = "thumbnailGeneration", @@ -3647,11 +3698,21 @@ export enum SyncEntityType { UserV1 = "UserV1", UserDeleteV1 = "UserDeleteV1", PartnerV1 = "PartnerV1", - PartnerDeleteV1 = "PartnerDeleteV1" + PartnerDeleteV1 = "PartnerDeleteV1", + AssetV1 = "AssetV1", + AssetDeleteV1 = "AssetDeleteV1", + AssetExifV1 = "AssetExifV1", + PartnerAssetV1 = "PartnerAssetV1", + PartnerAssetDeleteV1 = "PartnerAssetDeleteV1", + PartnerAssetExifV1 = "PartnerAssetExifV1" } export enum SyncRequestType { UsersV1 = "UsersV1", - PartnersV1 = "PartnersV1" + PartnersV1 = "PartnersV1", + AssetsV1 = "AssetsV1", + AssetExifsV1 = "AssetExifsV1", + PartnerAssetsV1 = "PartnerAssetsV1", + PartnerAssetExifsV1 = "PartnerAssetExifsV1" } export enum TranscodeHWAccel { Nvenc = "nvenc", diff --git a/openapi/package-lock.json b/openapi/package-lock.json new file mode 100644 index 0000000000..8c9d398e45 --- /dev/null +++ b/openapi/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "openapi", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/readme_i18n/README_ar_JO.md b/readme_i18n/README_ar_JO.md index 32b3dbb9ec..90936cdefa 100644 --- a/readme_i18n/README_ar_JO.md +++ b/readme_i18n/README_ar_JO.md @@ -65,7 +65,7 @@ https://immich.app https://demo.immich.app بالنسبة لتطبيق الهاتف المحمول، يمكنك استخدام -`https://demo.immich.app/api` +`https://demo.immich.app` ل `نقطة نهاية الخادم` ```bash title="Demo Credential" diff --git a/readme_i18n/README_ca_ES.md b/readme_i18n/README_ca_ES.md index 3edfb951b1..5daa12c477 100644 --- a/readme_i18n/README_ca_ES.md +++ b/readme_i18n/README_ca_ES.md @@ -60,7 +60,7 @@ Podeu trobar la documentació principal, incloent les guies d'instal·lació, a Podeu accedir a la demostració web a https://demo.immich.app -Per a l'aplicació mòbil, podeu utilitzar `https://demo.immich.app/api` com a "URL de punt final del servidor". +Per a l'aplicació mòbil, podeu utilitzar `https://demo.immich.app` com a "URL de punt final del servidor". ```bash title="Credencials de la demo" Les credencials diff --git a/readme_i18n/README_de_DE.md b/readme_i18n/README_de_DE.md index 54ba3cc86e..3df41ad28f 100644 --- a/readme_i18n/README_de_DE.md +++ b/readme_i18n/README_de_DE.md @@ -64,7 +64,7 @@ Die Web-Demo kannst Du unter https://demo.immich.app finden. Die Demo läuft auf einer Free Tier Oracle VM in Amsterdam mit einer 2.4Ghz Quad-Core ARM64 CPU und 24GB RAM. -Für die Handy-App kannst Du `https://demo.immich.app/api` als `Server Endpoint URL` angeben. +Für die Handy-App kannst Du `https://demo.immich.app` als `Server Endpoint URL` angeben. ### Login Daten diff --git a/readme_i18n/README_es_ES.md b/readme_i18n/README_es_ES.md index fbc7eae16f..73e9a2a14b 100644 --- a/readme_i18n/README_es_ES.md +++ b/readme_i18n/README_es_ES.md @@ -61,7 +61,7 @@ Puedes encontrar la documentación oficial, incluidas las guías de instalación Puedes acceder a la demostración web en -Para la aplicación móvil, puedes usar `https://demo.immich.app/api` en la `URL del servidor`. +Para la aplicación móvil, puedes usar `https://demo.immich.app` en la `URL del servidor`. ```bash title="Credenciales de la demo" Credenciales diff --git a/readme_i18n/README_fr_FR.md b/readme_i18n/README_fr_FR.md index 1c6cc4e2c5..100bc219cb 100644 --- a/readme_i18n/README_fr_FR.md +++ b/readme_i18n/README_fr_FR.md @@ -61,7 +61,7 @@ Vous pouvez trouver la documentation principale ainsi que les guides d'installat Vous pouvez accéder à la démo en ligne sur https://demo.immich.app -Pour l'application mobile, vous pouvez utiliser `https://demo.immich.app/api` dans le champ `URL du point d'accès au serveur` +Pour l'application mobile, vous pouvez utiliser `https://demo.immich.app` dans le champ `URL du point d'accès au serveur` ```bash title="Identifiants pour la démo" Les identifiants diff --git a/readme_i18n/README_it_IT.md b/readme_i18n/README_it_IT.md index 0c34f22d4a..b1aab7ea95 100644 --- a/readme_i18n/README_it_IT.md +++ b/readme_i18n/README_it_IT.md @@ -61,7 +61,7 @@ La documentazione ufficiale, inclusa la guida all'installazione, è disponibile Prova la demo del progetto https://demo.immich.app -Sull'app mobile, imposta `https://demo.immich.app/api` come `Server Endpoint URL` +Sull'app mobile, imposta `https://demo.immich.app` come `Server Endpoint URL` ```bash title="Demo Credential" Credenziali di accesso diff --git a/readme_i18n/README_ja_JP.md b/readme_i18n/README_ja_JP.md index 828afa9812..328cba431c 100644 --- a/readme_i18n/README_ja_JP.md +++ b/readme_i18n/README_ja_JP.md @@ -60,7 +60,7 @@ web デモは https://demo.immich.app からアクセスできます -モバイルアプリの場合、`Server Endpoint URL` には `https://demo.immich.app/api` を使用することができます +モバイルアプリの場合、`Server Endpoint URL` には `https://demo.immich.app` を使用することができます ```bash title="Demo Credential" The credential diff --git a/readme_i18n/README_ko_KR.md b/readme_i18n/README_ko_KR.md index 844923a9bb..b4b0841ed5 100644 --- a/readme_i18n/README_ko_KR.md +++ b/readme_i18n/README_ko_KR.md @@ -63,7 +63,7 @@ [이곳](https://demo.immich.app)에서 데모를 체험해보세요. 데모 서버는 2.4Ghz 쿼드 코어 ARM64 CPU 및 24GB 램으로 구성된 Oracle Free-tier VM 암스테르담 리전에서 구동됩니다. -모바일 앱의 경우, `서버 엔드포인트 URL`에 `https://demo.immich.app/api`를 입력하세요. +모바일 앱의 경우, `서버 엔드포인트 URL`에 `https://demo.immich.app`를 입력하세요. ### 로그인 정보 diff --git a/readme_i18n/README_nl_NL.md b/readme_i18n/README_nl_NL.md index 53f611f848..b67b66aa7d 100644 --- a/readme_i18n/README_nl_NL.md +++ b/readme_i18n/README_nl_NL.md @@ -61,7 +61,7 @@ De belangrijkste documentatie, inclusief installatie handleidingen, zijn te vind Je kunt de demo [hier](https://demo.immich.app/) bekijken. De demo server is actief op een Free-tier Oracle VM in Amsterdam met een 2.4GHz quad-core ARM64 CPU en 24GB RAM. -Voor de mobiele app kun je gebruik maken van `https://demo.immich.app/api` voor de `Server Endpoint URL` +Voor de mobiele app kun je gebruik maken van `https://demo.immich.app` voor de `Server Endpoint URL` ### Login gegevens diff --git a/readme_i18n/README_pt_BR.md b/readme_i18n/README_pt_BR.md index 2bb8995603..7df3af91e4 100644 --- a/readme_i18n/README_pt_BR.md +++ b/readme_i18n/README_pt_BR.md @@ -71,7 +71,7 @@ hospedada no Nível Gratuito da Oracle VM em Amsterdam com um processador 2.4Ghz quad-core ARM64 e 24GB de RAM. No aplicativo para dispositivos móveis, você pode usar -`https://demo.immich.app/api` no campo `Server Endpoint URL` +`https://demo.immich.app` no campo `Server Endpoint URL` ### Credenciais de login diff --git a/readme_i18n/README_ru_RU.md b/readme_i18n/README_ru_RU.md index 36ea03a3be..11f1dfdc4b 100644 --- a/readme_i18n/README_ru_RU.md +++ b/readme_i18n/README_ru_RU.md @@ -64,7 +64,7 @@ Вы можете опробовать [Web демонстрационную версию](https://demo.immich.app/) -В мобильном приложении укажите `https://demo.immich.app/api` в поле `URL-адрес сервера` +В мобильном приложении укажите `https://demo.immich.app` в поле `URL-адрес сервера` ### Данные для входа diff --git a/readme_i18n/README_sv_SE.md b/readme_i18n/README_sv_SE.md index 69f23fc482..634de591a6 100644 --- a/readme_i18n/README_sv_SE.md +++ b/readme_i18n/README_sv_SE.md @@ -62,7 +62,7 @@ Dokumentation och installationsguider hittas på https://imiich.app/. Ett webb-demo finns att testa på https://demo.immich.app -Använd `https://demo.immich.app/api` i mobilappen som `Server Endpoint URL` +Använd `https://demo.immich.app` i mobilappen som `Server Endpoint URL` ```bash title="Inloggningsuppgifter För Demo" Inloggsningsuppgifter diff --git a/readme_i18n/README_th_TH.md b/readme_i18n/README_th_TH.md index 7735c3c854..20a82a5d81 100644 --- a/readme_i18n/README_th_TH.md +++ b/readme_i18n/README_th_TH.md @@ -65,7 +65,7 @@ เข้าถึงการสาธิตได้ [ที่นี่](https://demo.immich.app) โดยการสาธิตนี้ทำงานบน Oracle VM Free-tier ตั้งอยู่ที่อัมสเตอร์ดัม ใช้ซีพียู ARM64 quad-core 2.4Ghz และแรม 24GB -สำหรับแอปมือถือ คุณสามารถใช้ `https://demo.immich.app/api` เป็น `Server Endpoint URL` +สำหรับแอปมือถือ คุณสามารถใช้ `https://demo.immich.app` เป็น `Server Endpoint URL` ### ข้อมูลการเข้าสู่ระบบ diff --git a/readme_i18n/README_tr_TR.md b/readme_i18n/README_tr_TR.md index 88c2cea11c..fd52a4425c 100644 --- a/readme_i18n/README_tr_TR.md +++ b/readme_i18n/README_tr_TR.md @@ -60,7 +60,7 @@ Kurulum dahil olmak üzere resmi belgeleri https://immich.app/ adresinde bulabil Web demo adresi: https://demo.immich.app -Mobil uygulama için `Server Endpoint URL` olarak `https://demo.immich.app/api` adresini kullanabilirsiniz. +Mobil uygulama için `Server Endpoint URL` olarak `https://demo.immich.app` adresini kullanabilirsiniz. ```bash title="Demo Bilgileri" Giriş bilgileri: diff --git a/readme_i18n/README_uk_UA.md b/readme_i18n/README_uk_UA.md index 9a5c808863..13cafd5ad3 100644 --- a/readme_i18n/README_uk_UA.md +++ b/readme_i18n/README_uk_UA.md @@ -63,7 +63,7 @@ Доступ до демо-версії [тут](https://demo.immich.app). Демоверсія працює на безкоштовному Oracle VM у Амстердамі з чотириядерним ARM64 процесором (2.4 ГГц) і 24 ГБ оперативної пам’яті. -Для мобільного додатку ви можете використовувати `https://demo.immich.app/api` в якості `Server Endpoint URL`. +Для мобільного додатку ви можете використовувати `https://demo.immich.app` в якості `Server Endpoint URL`. ### Облікові дані для входу diff --git a/readme_i18n/README_vi_VN.md b/readme_i18n/README_vi_VN.md index 83ccdcafd8..c25ed4c92a 100644 --- a/readme_i18n/README_vi_VN.md +++ b/readme_i18n/README_vi_VN.md @@ -65,7 +65,7 @@ Truy cập bản demo [tại đây](https://demo.immich.app). Bản demo đang chạy trên máy ảo Oracle Free-tier ở Amsterdam với CPU ARM64 lõi tứ 2,4 GHz và RAM 24 GB. -Đối với ứng dụng di động, bạn có thể sử dụng `https://demo.immich.app/api` cho `Server Endpoint URL` +Đối với ứng dụng di động, bạn có thể sử dụng `https://demo.immich.app` cho `Server Endpoint URL` ### Thông tin đăng nhập diff --git a/readme_i18n/README_zh_CN.md b/readme_i18n/README_zh_CN.md index 8cb1f0c5d1..83fbe59a68 100644 --- a/readme_i18n/README_zh_CN.md +++ b/readme_i18n/README_zh_CN.md @@ -67,7 +67,7 @@ 您可以在[此处](https://demo.immich.app)访问在线演示网站。该示例网站运行的机器配置为:甲骨文免费虚拟机套餐——阿姆斯特丹 4核 2.4Ghz ARM64 CPU,24GB RAM。 -在移动端,您可以使用 `https://demo.immich.app/api` 作为 `服务终端链接` +在移动端,您可以使用 `https://demo.immich.app` 作为 `服务终端链接` ### 登录认证信息 diff --git a/server/Dockerfile b/server/Dockerfile index f46ea8d0e3..83e750a3f9 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,5 +1,5 @@ # dev build -FROM ghcr.io/immich-app/base-server-dev:20250304@sha256:bc8d0c8d5c6d00625d01c84785435383651a9d57cb6cd1b1430cf0bcb58e4a80 AS dev +FROM ghcr.io/immich-app/base-server-dev:20250311@sha256:dd270c9fb535ea4d216241d6984e20ef4b4bc3514658a045e5a1c3821aa44507 AS dev RUN apt-get install --no-install-recommends -yqq tini WORKDIR /usr/src/app @@ -42,7 +42,7 @@ RUN npm run build # prod build -FROM ghcr.io/immich-app/base-server-prod:20250218@sha256:c5980c358dc94b62cac2086edaa1fd1ae855340a0d8e386fd92bc527d8aaadaa +FROM ghcr.io/immich-app/base-server-prod:20250311@sha256:8c96b3df2161806c9dd6032356ca04c28d374057462b3964c8e8d3cc35c035ee WORKDIR /usr/src/app ENV NODE_ENV=production \ diff --git a/server/eslint.config.mjs b/server/eslint.config.mjs index d29b6f7238..5fe62b9651 100644 --- a/server/eslint.config.mjs +++ b/server/eslint.config.mjs @@ -57,6 +57,7 @@ export default [ 'unicorn/no-thenable': 'off', 'unicorn/import-style': 'off', 'unicorn/prefer-structured-clone': 'off', + 'unicorn/no-for-loop': 'off', '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/no-misused-promises': 'error', 'require-await': 'off', diff --git a/server/package-lock.json b/server/package-lock.json index fbdc24e427..fbb997da59 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "immich", - "version": "1.128.0", + "version": "1.129.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "immich", - "version": "1.128.0", + "version": "1.129.0", "license": "GNU Affero General Public License version 3", "dependencies": { "@nestjs/bullmq": "^11.0.1", @@ -17,7 +17,6 @@ "@nestjs/platform-socket.io": "^11.0.4", "@nestjs/schedule": "^5.0.0", "@nestjs/swagger": "^11.0.2", - "@nestjs/typeorm": "^11.0.0", "@nestjs/websockets": "^11.0.4", "@opentelemetry/auto-instrumentations-node": "^0.56.0", "@opentelemetry/context-async-hooks": "^1.24.0", @@ -32,7 +31,8 @@ "chokidar": "^3.5.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", - "cookie-parser": "^1.4.6", + "cookie": "^1.0.2", + "cookie-parser": "^1.4.7", "exiftool-vendored": "^28.3.1", "fast-glob": "^3.3.2", "fluent-ffmpeg": "^2.1.2", @@ -81,14 +81,14 @@ "@types/archiver": "^6.0.0", "@types/async-lock": "^1.4.2", "@types/bcrypt": "^5.0.0", - "@types/cookie-parser": "^1.4.3", + "@types/cookie-parser": "^1.4.8", "@types/express": "^4.17.17", "@types/fluent-ffmpeg": "^2.1.21", "@types/js-yaml": "^4.0.9", "@types/lodash": "^4.14.197", "@types/mock-fs": "^4.13.1", "@types/multer": "^1.4.7", - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "@types/nodemailer": "^6.4.14", "@types/picomatch": "^3.0.0", "@types/pngjs": "^6.0.5", @@ -105,7 +105,7 @@ "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-unicorn": "^56.0.1", "globals": "^16.0.0", - "kysely-codegen": "^0.17.0", + "kysely-codegen": "^0.18.0", "mock-fs": "^5.2.0", "node-addon-api": "^8.3.0", "pngjs": "^7.0.0", @@ -261,24 +261,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@angular-devkit/schematics-cli/node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, "node_modules/@angular-devkit/schematics-cli/node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -330,6 +312,16 @@ "tslib": "^2.1.0" } }, + "node_modules/@angular-devkit/schematics-cli/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { "version": "19.1.8", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.1.8.tgz", @@ -375,24 +367,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@angular-devkit/schematics/node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, "node_modules/@angular-devkit/schematics/node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -444,6 +418,16 @@ "tslib": "^2.1.0" } }, + "node_modules/@angular-devkit/schematics/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -459,9 +443,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", - "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -507,13 +491,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", - "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", + "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.3", - "@babel/types": "^7.26.3", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -523,12 +507,12 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", - "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.9", + "@babel/compat-data": "^7.26.5", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -620,25 +604,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", + "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", - "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", + "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.3" + "@babel/types": "^7.26.9" }, "bin": { "parser": "bin/babel-parser.js" @@ -648,30 +632,30 @@ } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", - "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", + "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.3", - "@babel/parser": "^7.26.3", - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.3", + "@babel/generator": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -689,9 +673,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", - "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", + "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -1011,6 +995,23 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", @@ -1275,9 +1276,9 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.12.4", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.4.tgz", - "integrity": "sha512-NBhrxEWnFh0FxeA0d//YP95lRFsSx2TNLEUQg4/W+5f/BMxcCjgOOIT24iD+ZB/tZw057j44DaIxja7w4XMrhg==", + "version": "1.12.6", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.6.tgz", + "integrity": "sha512-JXUj6PI0oqqzTGvKtzOkxtpsyPRNsrmhh41TtIz/zEB6J+AUiZZ0dxWzcMwO9Ns5rmSPuMdghlTbUuqIM48d3Q==", "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.7.13", @@ -1822,26 +1823,6 @@ } } }, - "node_modules/@inquirer/core/node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/@inquirer/core/node_modules/mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/@inquirer/editor": { "version": "4.2.7", "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.7.tgz", @@ -2108,18 +2089,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", @@ -2155,21 +2124,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -2535,34 +2489,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@nestjs/cli/node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/@nestjs/cli/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/@nestjs/cli/node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2579,46 +2505,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nestjs/cli/node_modules/glob": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", - "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@nestjs/cli/node_modules/jackspeak": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", - "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@nestjs/cli/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -2626,49 +2512,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@nestjs/cli/node_modules/lru-cache": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", - "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@nestjs/cli/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@nestjs/cli/node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@nestjs/cli/node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -2693,10 +2536,34 @@ "tslib": "^2.1.0" } }, + "node_modules/@nestjs/cli/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nestjs/cli/node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@nestjs/common": { - "version": "11.0.10", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.0.10.tgz", - "integrity": "sha512-pzGXp14KF2Q4CDZGQgPK4l8zEg7i6cNkb+10yc8ZA5K41cLe3ZbWW1YxtY2e/glHauOJwTLSVjH4tiRVtOTizg==", + "version": "11.0.11", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.0.11.tgz", + "integrity": "sha512-b3zYiho5/XGCnLa7W2hHv5ecSBR1huQrXCHu6pxd+g2HY2B7sKP5CXHMv4gHYqpIqu4ClOb7Q4tLKXMp9LyLUg==", "license": "MIT", "dependencies": { "iterare": "1.2.1", @@ -2723,9 +2590,9 @@ } }, "node_modules/@nestjs/core": { - "version": "11.0.10", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.0.10.tgz", - "integrity": "sha512-f0qB8ztNWZeAD4E4fUdHConmNYCa/A78U7WJu5mX9OLYfOAs3ESYCDfsH9MRUvkA4Ft4Y1uMmyJo5L4fg4+beg==", + "version": "11.0.11", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.0.11.tgz", + "integrity": "sha512-jMH3jrjrPiaGrkQ5hANNcgDWN+j+hcM5GMQ3jSs4vOWNs3lmKHTVR11wJ9y5tTNnwKydzMogeju0VTUdfXDI5Q==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -2797,9 +2664,9 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "11.0.10", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.0.10.tgz", - "integrity": "sha512-UVSf0yaWFBC2Zn2FOWABXxCnyG8XNIXrNnvTFpbUFqaJu1YDdwJ7wQBBqxq9CtJT7ILqSmfhOU7HS0d/0EAxpw==", + "version": "11.0.11", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.0.11.tgz", + "integrity": "sha512-iv6nH66i/RuRQufg5UUboQ4jQX4NuuePrYQpHB3ueiEIhJm2yLhhNYM6Y2l/76y9woW2eckbiqbzmW/JajAgeQ==", "license": "MIT", "dependencies": { "cors": "2.8.5", @@ -2818,9 +2685,9 @@ } }, "node_modules/@nestjs/platform-socket.io": { - "version": "11.0.10", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.0.10.tgz", - "integrity": "sha512-39lAjq0+kZRiMuscDcugoG+onPDciM4jhuf8ZDjVcuSwtib1OGwrFtErSzp/KJsmHPSStgapbNev7eFi32uWQA==", + "version": "11.0.11", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.0.11.tgz", + "integrity": "sha512-+bLrtTSPDX6AxrL9PbR5lEgcEnn6oFzkGpLUcm3Xs9x5OBejzJh1tiWgBGJRQIh3l9iIG8/mQ8hNwufAt8SIcA==", "license": "MIT", "dependencies": { "socket.io": "4.8.1", @@ -2850,14 +2717,14 @@ } }, "node_modules/@nestjs/schematics": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.1.tgz", - "integrity": "sha512-PHPAUk4sXkfCxiMacD1JFC+vEyzXjZJRCu1KT2MmG2hrTiMDMk5KtMprro148JUefNuWbVyN0uLTJVSmWVzhoA==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.2.tgz", + "integrity": "sha512-C4KM3BHBG6tRX8t5UrHdUq8Y49asEfJUora/fBXge3UTAnxKGlXc20p5s2Q0Q1+l+1YaXqTrKGSIbYXdPX8r9g==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.1.7", - "@angular-devkit/schematics": "19.1.7", + "@angular-devkit/core": "19.2.0", + "@angular-devkit/schematics": "19.2.0", "comment-json": "4.2.5", "jsonc-parser": "3.3.1", "pluralize": "8.0.0" @@ -2867,9 +2734,9 @@ } }, "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": { - "version": "19.1.7", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.1.7.tgz", - "integrity": "sha512-q0I6L9KTqyQ7D5M8H+fWLT+yjapvMNb7SRdfU6GzmexO66Dpo83q4HDzuDKIPDF29Yl0ELs9ICJqe9yUXh6yDQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.0.tgz", + "integrity": "sha512-qd2nYoHZOYWRsu4MjXG8KiDtfM9ZDRR2rDGa+rDZ3CYAsngCrPmqOebun10dncUjwAidX49P4S2U2elOmX3VYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2895,13 +2762,13 @@ } }, "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics": { - "version": "19.1.7", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.1.7.tgz", - "integrity": "sha512-AP6FvhMybCYs3gs+vzEAzSU1K//AFT3SVTRFv+C3WMO5dLeAHeGzM8I2dxD5EHQQtqIE/8apP6CxGrnpA5YlFg==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.0.tgz", + "integrity": "sha512-cGGqUGqBXIGJkeL65l70y0BflDAu/0Zi/ohbYat3hvadFfumRJnVElVfJ59JtWO7FfKQjxcwCVTyuQ/tevX/9A==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.1.7", + "@angular-devkit/core": "19.2.0", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "5.4.1", @@ -2930,24 +2797,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@nestjs/schematics/node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, "node_modules/@nestjs/schematics/node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2999,10 +2848,20 @@ "tslib": "^2.1.0" } }, + "node_modules/@nestjs/schematics/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/@nestjs/swagger": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.0.5.tgz", - "integrity": "sha512-3z4rl7FgbLPBvJwR45nBDju4QFH7vufs9Ums8sCoc6T1O1dqpNpxz0sKfXiP5QA6AUljks1jARfOHeHeJ4zWBA==", + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.0.6.tgz", + "integrity": "sha512-W/0aQWiEfEcXKd/dYO0DbVpYhlKNVMAhO4haahUyrYe20eXaaDY0T5exA2U8IsCcXZePWZuodRUiiXo8jcMYbA==", "license": "MIT", "dependencies": { "@microsoft/tsdoc": "0.15.1", @@ -3033,9 +2892,9 @@ } }, "node_modules/@nestjs/testing": { - "version": "11.0.10", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.0.10.tgz", - "integrity": "sha512-uZcdnvmHXWnvozYOAwZi1elpRRfqIfYqHglCavjhjcj3cH1MVZkwoTqntW3XOPQlT4lf96InjP1exGaW4B9wUg==", + "version": "11.0.11", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.0.11.tgz", + "integrity": "sha512-SoMIrhRpElV53btmGnEwpIQmXn2Xcztb9ae3lv+eVVnPHQuyB2zlgDIQVNjicbj7+3jdycX52KctOoj2eXEo1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3060,23 +2919,10 @@ } } }, - "node_modules/@nestjs/typeorm": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", - "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==", - "license": "MIT", - "peerDependencies": { - "@nestjs/common": "^10.0.0 || ^11.0.0", - "@nestjs/core": "^10.0.0 || ^11.0.0", - "reflect-metadata": "^0.1.13 || ^0.2.0", - "rxjs": "^7.2.0", - "typeorm": "^0.3.0" - } - }, "node_modules/@nestjs/websockets": { - "version": "11.0.10", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.0.10.tgz", - "integrity": "sha512-GPIEfqJyAkTHrHGK9w2OU8LJaZAZKW8WpWcTplThLxMelRq7mBkYOaGvc6dpr7fE1wWzWkwY0ZjQEnwnVmmxSg==", + "version": "11.0.11", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.0.11.tgz", + "integrity": "sha512-9sNNT/kYA534iaFyZ9MrOXKwQFuJArsMXhT6ywVxaWKQ84lVbV/sDmdmJUe9mzUGLPiHMn+m3oDUO9MiLTEKPA==", "license": "MIT", "dependencies": { "iterare": "1.2.1", @@ -3291,9 +3137,9 @@ } }, "node_modules/@opentelemetry/api-logs": { - "version": "0.57.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.1.tgz", - "integrity": "sha512-I4PHczeujhQAQv6ZBzqHYEUiggZL4IdSMixtVD3EYqbdrjujE7kRfI5QohjlPoJm8BvenoW5YaTMWRrbpot6tg==", + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", + "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.3.0" @@ -3429,18 +3275,6 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/api-logs": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", - "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/exporter-logs-otlp-proto": { "version": "0.57.2", "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.57.2.tgz", @@ -3462,18 +3296,6 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/api-logs": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", - "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { "version": "0.57.2", "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.57.2.tgz", @@ -3630,13 +3452,12 @@ } }, "node_modules/@opentelemetry/host-metrics": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/host-metrics/-/host-metrics-0.35.4.tgz", - "integrity": "sha512-3nPElbfYZ2oKNoMw2CkXkHxQryebqACcSgMbbKcn+GnGKp+h7MeOHyg21NmmTt9xgCvRHYiHNkWGkB4laP0oUw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@opentelemetry/host-metrics/-/host-metrics-0.35.5.tgz", + "integrity": "sha512-Zf9Cjl7H6JalspnK5KD1+LLKSVecSinouVctNmUxRy+WP+20KwHq+qg4hADllkEmJ99MZByLLmEmzrr7s92V6g==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/sdk-metrics": "^1.8.0", - "systeminformation": "5.22.9" + "systeminformation": "5.23.8" }, "engines": { "node": ">=14" @@ -3646,12 +3467,12 @@ } }, "node_modules/@opentelemetry/instrumentation": { - "version": "0.57.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.1.tgz", - "integrity": "sha512-SgHEKXoVxOjc20ZYusPG3Fh+RLIZTSa4x8QtD3NfgAUDyqdFFS9W1F2ZVbZkqDCdyMcQG02Ok4duUGLHJXHgbA==", + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", + "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.57.1", + "@opentelemetry/api-logs": "0.57.2", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", @@ -3895,12 +3716,12 @@ } }, "node_modules/@opentelemetry/instrumentation-grpc": { - "version": "0.57.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.57.1.tgz", - "integrity": "sha512-tZ0LO6hxLCnQfSS03BpYWc+kZpqFJJUbYb+GfEr5YJ1/YrOtRP8lCpC8AC1QIVmqGn+Vlxjkn3tSifNHsk9enw==", + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.57.2.tgz", + "integrity": "sha512-TR6YQA67cLSZzdxbf2SrbADJy2Y8eBW1+9mF15P0VK2MYcpdoUSmQTF1oMkBwa3B9NwqDFA2fq7wYTTutFQqaQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "0.57.1", + "@opentelemetry/instrumentation": "0.57.2", "@opentelemetry/semantic-conventions": "1.28.0" }, "engines": { @@ -3928,13 +3749,13 @@ } }, "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.57.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.57.1.tgz", - "integrity": "sha512-ThLmzAQDs7b/tdKI3BV2+yawuF09jF111OFsovqT1Qj3D8vjwKBwhi/rDE5xethwn4tSXtZcJ9hBsVAlWFQZ7g==", + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.57.2.tgz", + "integrity": "sha512-1Uz5iJ9ZAlFOiPuwYg29Bf7bJJc/GeoeJIFKJYQf67nTVKFe8RHbEtxgkOmK4UGZNHKXcpW4P8cWBYzBn1USpg==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "1.30.1", - "@opentelemetry/instrumentation": "0.57.1", + "@opentelemetry/instrumentation": "0.57.2", "@opentelemetry/semantic-conventions": "1.28.0", "forwarded-parse": "2.1.2", "semver": "^7.5.2" @@ -4367,18 +4188,6 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/api-logs": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", - "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/propagation-utils": { "version": "0.30.16", "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.16.tgz", @@ -4549,18 +4358,6 @@ "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/api-logs": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", - "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/sdk-metrics": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", @@ -4611,38 +4408,6 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/api-logs": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", - "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/instrumentation": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", - "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.57.2", - "@types/shimmer": "^1.2.0", - "import-in-the-middle": "^1.8.1", - "require-in-the-middle": "^7.1.1", - "semver": "^7.5.2", - "shimmer": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, "node_modules/@opentelemetry/sdk-trace-base": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", @@ -4705,9 +4470,9 @@ } }, "node_modules/@photostructure/tz-lookup": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@photostructure/tz-lookup/-/tz-lookup-11.0.0.tgz", - "integrity": "sha512-QMV5/dWtY/MdVPXZs/EApqzyhnqDq1keYEqpS+Xj2uidyaqw2Nk/fWcsszdruIXjdqp1VoWNzsgrO6bUHU1mFw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@photostructure/tz-lookup/-/tz-lookup-11.1.0.tgz", + "integrity": "sha512-UywyhMwUdVU2aH5ls7EweTEyPpXbDkgC//Nnsm/lWfpae8WX3N33Yy0/aBmb/Pd9+qEtgcFMYTtN/Htb+cd0ZA==", "license": "CC0-1.0" }, "node_modules/@pkgjs/parseargs": { @@ -5122,9 +4887,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", - "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz", + "integrity": "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==", "cpu": [ "arm" ], @@ -5136,9 +4901,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz", - "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.9.tgz", + "integrity": "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==", "cpu": [ "arm64" ], @@ -5150,9 +4915,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz", - "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", + "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", "cpu": [ "arm64" ], @@ -5164,9 +4929,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz", - "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", + "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", "cpu": [ "x64" ], @@ -5178,9 +4943,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz", - "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.9.tgz", + "integrity": "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==", "cpu": [ "arm64" ], @@ -5192,9 +4957,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz", - "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.9.tgz", + "integrity": "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==", "cpu": [ "x64" ], @@ -5206,9 +4971,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz", - "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.9.tgz", + "integrity": "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==", "cpu": [ "arm" ], @@ -5220,9 +4985,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz", - "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.9.tgz", + "integrity": "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==", "cpu": [ "arm" ], @@ -5234,9 +4999,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz", - "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", + "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", "cpu": [ "arm64" ], @@ -5248,9 +5013,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz", - "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", + "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", "cpu": [ "arm64" ], @@ -5262,9 +5027,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz", - "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.9.tgz", + "integrity": "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==", "cpu": [ "loong64" ], @@ -5276,9 +5041,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz", - "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.9.tgz", + "integrity": "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==", "cpu": [ "ppc64" ], @@ -5290,9 +5055,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz", - "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.9.tgz", + "integrity": "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==", "cpu": [ "riscv64" ], @@ -5304,9 +5069,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz", - "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.9.tgz", + "integrity": "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==", "cpu": [ "s390x" ], @@ -5318,9 +5083,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz", - "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", "cpu": [ "x64" ], @@ -5332,9 +5097,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz", - "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", + "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", "cpu": [ "x64" ], @@ -5346,9 +5111,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz", - "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", + "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", "cpu": [ "arm64" ], @@ -5360,9 +5125,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz", - "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.9.tgz", + "integrity": "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==", "cpu": [ "ia32" ], @@ -5374,9 +5139,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz", - "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", + "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", "cpu": [ "x64" ], @@ -5475,9 +5240,9 @@ "license": "MIT" }, "node_modules/@swc/core": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.4.tgz", - "integrity": "sha512-EHl6eNod/914xDRK4nu7gr78riK2cfi4DkAMvJt6COdaNGOnbR5eKrLe3SnRizyzzrPcxUMhflDL5hrcXS8rAQ==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.8.tgz", + "integrity": "sha512-UAL+EULxrc0J73flwYHfu29mO8CONpDJiQv1QPDXsyCvDUcEhqAqUROVTgC+wtJCFFqMQdyr4stAA5/s0KSOmA==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -5493,16 +5258,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.11.4", - "@swc/core-darwin-x64": "1.11.4", - "@swc/core-linux-arm-gnueabihf": "1.11.4", - "@swc/core-linux-arm64-gnu": "1.11.4", - "@swc/core-linux-arm64-musl": "1.11.4", - "@swc/core-linux-x64-gnu": "1.11.4", - "@swc/core-linux-x64-musl": "1.11.4", - "@swc/core-win32-arm64-msvc": "1.11.4", - "@swc/core-win32-ia32-msvc": "1.11.4", - "@swc/core-win32-x64-msvc": "1.11.4" + "@swc/core-darwin-arm64": "1.11.8", + "@swc/core-darwin-x64": "1.11.8", + "@swc/core-linux-arm-gnueabihf": "1.11.8", + "@swc/core-linux-arm64-gnu": "1.11.8", + "@swc/core-linux-arm64-musl": "1.11.8", + "@swc/core-linux-x64-gnu": "1.11.8", + "@swc/core-linux-x64-musl": "1.11.8", + "@swc/core-win32-arm64-msvc": "1.11.8", + "@swc/core-win32-ia32-msvc": "1.11.8", + "@swc/core-win32-x64-msvc": "1.11.8" }, "peerDependencies": { "@swc/helpers": "*" @@ -5514,9 +5279,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.4.tgz", - "integrity": "sha512-Oi4lt4wqjpp80pcCh+vzvpsESJ8XXozYCE5EM/dDpr+9m2oRpkseds7Gq4ulzgdbUDPo1jJ1PonjjrKpfKY+sQ==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.8.tgz", + "integrity": "sha512-rrSsunyJWpHN+5V1zumndwSSifmIeFQBK9i2RMQQp15PgbgUNxHK5qoET1n20pcUrmZeT6jmJaEWlQchkV//Og==", "cpu": [ "arm64" ], @@ -5531,9 +5296,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.4.tgz", - "integrity": "sha512-Tb7ez94DXxhX5iJ5slnAlT2gwJinQk3pMnQ46Npi6adKr3ZXM5Bdk0jpRUp8XjEcgNXkQRV1DtrySgCz6YlEnQ==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.8.tgz", + "integrity": "sha512-44goLqQuuo0HgWnG8qC+ZFw/qnjCVVeqffhzFr9WAXXotogVaxM8ze6egE58VWrfEc8me8yCcxOYL9RbtjhS/Q==", "cpu": [ "x64" ], @@ -5548,9 +5313,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.4.tgz", - "integrity": "sha512-p1uV+6Mi+0M+1kL7qL206ZaohomYMW7yroXSLDTJXbIylx7wG2xrUQL6AFtz2DwqDoX/E8jMNBjp+GcEy8r8Ig==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.8.tgz", + "integrity": "sha512-Mzo8umKlhTWwF1v8SLuTM1z2A+P43UVhf4R8RZDhzIRBuB2NkeyE+c0gexIOJBuGSIATryuAF4O4luDu727D1w==", "cpu": [ "arm" ], @@ -5565,9 +5330,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.4.tgz", - "integrity": "sha512-4ijX4bWf9oc7kWkT6xUhugVGzEJ7U9c7CHNmt/xhI/yWsQdfM11+HECqWh7ay3m+aaEoVdvTeU5gykeF5jSxDA==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.8.tgz", + "integrity": "sha512-EyhO6U+QdoGYC1MeHOR0pyaaSaKYyNuT4FQNZ1eZIbnuueXpuICC7iNmLIOfr3LE5bVWcZ7NKGVPlM2StJEcgA==", "cpu": [ "arm64" ], @@ -5582,9 +5347,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.4.tgz", - "integrity": "sha512-XI+gOgcuSanejbAC5QXKTjNA3GUJi7bzHmeJbNhKpX9d349RdVwan0k9okHmhMBY7BywAg3LK0ovF9PmOLgMHg==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.8.tgz", + "integrity": "sha512-QU6wOkZnS6/QuBN1MHD6G2BgFxB0AclvTVGbqYkRA7MsVkcC29PffESqzTXnypzB252/XkhQjoB2JIt9rPYf6A==", "cpu": [ "arm64" ], @@ -5599,9 +5364,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.4.tgz", - "integrity": "sha512-wyD6noaCPFayKOvl9mTxuiQoEULAagGuO0od2VkW7h4HvlgpOAZNekZYX73WEP/b+WuePNHurZ9KGpom43IzmA==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.8.tgz", + "integrity": "sha512-r72onUEIU1iJi9EUws3R28pztQ/eM3EshNpsPRBfuLwKy+qn3et55vXOyDhIjGCUph5Eg2Yn8H3h6MTxDdLd+w==", "cpu": [ "x64" ], @@ -5616,9 +5381,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.4.tgz", - "integrity": "sha512-e2vG9gUF1BRX0BWqSEHop6u14l5BtV3VS2Pmr+oquc0Ycs/zj81xhYc3ML4ByK5OxDkAaKBWryAOKTLaJA/DVg==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.8.tgz", + "integrity": "sha512-294k8cLpO103++f4ZUEDr3vnBeUfPitW6G0a3qeVZuoXFhFgaW7ANZIWknUc14WiLOMfMecphJAEiy9C8OeYSw==", "cpu": [ "x64" ], @@ -5633,9 +5398,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.4.tgz", - "integrity": "sha512-rm51iljNqjCA/41gxYameuyjX1ENaTlvdxmaoPPYeUDt6hfypG93IxMJJCewaeHN9XfNxqZU7d4cupNqk+8nng==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.8.tgz", + "integrity": "sha512-EbjOzQ+B85rumHyeesBYxZ+hq3ZQn+YAAT1ZNE9xW1/8SuLoBmHy/K9YniRGVDq/2NRmp5kI5+5h5TX0asIS9A==", "cpu": [ "arm64" ], @@ -5650,9 +5415,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.4.tgz", - "integrity": "sha512-PHy3N6zlyU8te7Umi0ggXNbcx2VUkwpE59PW9FQQy9MBZM1Qn+OEGnO/4KLWjGFABw+9CwIeaRYgq6uCi1ry6A==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.8.tgz", + "integrity": "sha512-Z+FF5kgLHfQWIZ1KPdeInToXLzbY0sMAashjd/igKeP1Lz0qKXVAK+rpn6ASJi85Fn8wTftCGCyQUkRVn0bTDg==", "cpu": [ "ia32" ], @@ -5667,9 +5432,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.4.tgz", - "integrity": "sha512-0TiriDGl7Dr4ObfMBk07PS4Ql5hgQH0QnU3E8I+fbs45hqfwC5OrN47HOsXx4ZbEw8XYxp2NM8SGnVoTIm4J8w==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.8.tgz", + "integrity": "sha512-j6B6N0hChCeAISS6xp/hh6zR5CSCr037BAjCxNLsT8TGe5D+gYZ57heswUWXRH8eMKiRDGiLCYpPB2pkTqxCSw==", "cpu": [ "x64" ], @@ -5729,43 +5494,43 @@ } }, "node_modules/@turf/boolean-point-in-polygon": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-7.1.0.tgz", - "integrity": "sha512-mprVsyIQ+ijWTZwbnO4Jhxu94ZW2M2CheqLiRTsGJy0Ooay9v6Av5/Nl3/Gst7ZVXxPqMeMaFYkSzcTc87AKew==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-7.2.0.tgz", + "integrity": "sha512-lvEOjxeXIp+wPXgl9kJA97dqzMfNexjqHou+XHVcfxQgolctoJiRYmcVCWGpiZ9CBf/CJha1KmD1qQoRIsjLaA==", "license": "MIT", "dependencies": { - "@turf/helpers": "^7.1.0", - "@turf/invariant": "^7.1.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", "@types/geojson": "^7946.0.10", "point-in-polygon-hao": "^1.1.0", - "tslib": "^2.6.2" + "tslib": "^2.8.1" }, "funding": { "url": "https://opencollective.com/turf" } }, "node_modules/@turf/helpers": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-7.1.0.tgz", - "integrity": "sha512-dTeILEUVeNbaEeoZUOhxH5auv7WWlOShbx7QSd4s0T4Z0/iz90z9yaVCtZOLbU89umKotwKaJQltBNO9CzVgaQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-7.2.0.tgz", + "integrity": "sha512-cXo7bKNZoa7aC7ydLmUR02oB3IgDe7MxiPuRz3cCtYQHn+BJ6h1tihmamYDWWUlPHgSNF0i3ATc4WmDECZafKw==", "license": "MIT", "dependencies": { "@types/geojson": "^7946.0.10", - "tslib": "^2.6.2" + "tslib": "^2.8.1" }, "funding": { "url": "https://opencollective.com/turf" } }, "node_modules/@turf/invariant": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-7.1.0.tgz", - "integrity": "sha512-OCLNqkItBYIP1nE9lJGuIUatWGtQ4rhBKAyTfFu0z8npVzGEYzvguEeof8/6LkKmTTEHW53tCjoEhSSzdRh08Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-7.2.0.tgz", + "integrity": "sha512-kV4u8e7Gkpq+kPbAKNC21CmyrXzlbBgFjO1PhrHPgEdNqXqDawoZ3i6ivE3ULJj2rSesCjduUaC/wyvH/sNr2Q==", "license": "MIT", "dependencies": { - "@turf/helpers": "^7.1.0", + "@turf/helpers": "^7.2.0", "@types/geojson": "^7946.0.10", - "tslib": "^2.6.2" + "tslib": "^2.8.1" }, "funding": { "url": "https://opencollective.com/turf" @@ -5833,12 +5598,6 @@ "@types/node": "*" } }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "license": "MIT" - }, "node_modules/@types/cookie-parser": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.8.tgz", @@ -5877,9 +5636,9 @@ } }, "node_modules/@types/dockerode": { - "version": "3.3.32", - "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.32.tgz", - "integrity": "sha512-xxcG0g5AWKtNyh7I7wswLdFvym4Mlqks5ZlKzxEUrGHS0r0PUOfxm2T0mspwu10mHQqu3Ck3MI3V2HqvLWE1fg==", + "version": "3.3.35", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.35.tgz", + "integrity": "sha512-P+DCMASlsH+QaKkDpekKrP5pLls767PPs+/LrlVbKnEnY5tMpEUa2C6U4gRsdFZengOqxdCIqy16R22Q3pLB6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5954,9 +5713,9 @@ } }, "node_modules/@types/geojson": { - "version": "7946.0.15", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.15.tgz", - "integrity": "sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA==", + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, "node_modules/@types/http-errors": { @@ -5992,9 +5751,9 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==", + "version": "4.17.16", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz", + "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==", "dev": true, "license": "MIT" }, @@ -6057,9 +5816,9 @@ } }, "node_modules/@types/node": { - "version": "22.13.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.8.tgz", - "integrity": "sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==", + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -6130,9 +5889,9 @@ } }, "node_modules/@types/qs": { - "version": "6.9.17", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", - "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", "dev": true, "license": "MIT" }, @@ -6210,9 +5969,9 @@ "license": "MIT" }, "node_modules/@types/ssh2": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.1.tgz", - "integrity": "sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.4.tgz", + "integrity": "sha512-9JTQgVBWSgq6mAen6PVnrAmty1lqgCMvpfN+1Ck5WRUsyMYPa6qd50/vMJ0y1zkGpOEgLzm8m8Dx/Y5vRouLaA==", "dev": true, "license": "MIT", "dependencies": { @@ -6230,9 +5989,9 @@ } }, "node_modules/@types/ssh2/node_modules/@types/node": { - "version": "18.19.68", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.68.tgz", - "integrity": "sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw==", + "version": "18.19.79", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.79.tgz", + "integrity": "sha512-90K8Oayimbctc5zTPHPfZloc/lGVs7f3phUAAMcTgEPtg8kKquGZDERC8K4vkBYkQQh48msiYUslYtxTWvqcAg==", "dev": true, "license": "MIT", "dependencies": { @@ -6303,17 +6062,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz", - "integrity": "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz", + "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/type-utils": "8.25.0", - "@typescript-eslint/utils": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/type-utils": "8.26.0", + "@typescript-eslint/utils": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -6329,20 +6088,20 @@ "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.25.0.tgz", - "integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz", + "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4" }, "engines": { @@ -6354,18 +6113,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz", - "integrity": "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", + "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0" + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6376,14 +6135,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz", - "integrity": "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz", + "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/utils": "8.25.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/utils": "8.26.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -6396,13 +6155,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.25.0.tgz", - "integrity": "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", + "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", "dev": true, "license": "MIT", "engines": { @@ -6414,14 +6173,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz", - "integrity": "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", + "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -6437,7 +6196,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -6467,16 +6226,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.25.0.tgz", - "integrity": "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz", + "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0" + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6487,17 +6246,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz", - "integrity": "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", + "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/types": "8.26.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -6522,9 +6281,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.7.tgz", - "integrity": "sha512-Av8WgBJLTrfLOer0uy3CxjlVuWK4CzcLBndW1Nm2vI+3hZ2ozHututkfc7Blu1u6waeQ7J8gzPK/AsBRnWA5mQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.8.tgz", + "integrity": "sha512-y7SAKsQirsEJ2F8bulBck4DoluhI2EEgTimHd6EEUgJBGKy9tC25cpywh1MH4FvDGoG2Unt7+asVd1kj4qOSAw==", "dev": true, "license": "MIT", "dependencies": { @@ -6545,8 +6304,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.0.7", - "vitest": "3.0.7" + "@vitest/browser": "3.0.8", + "vitest": "3.0.8" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -6555,14 +6314,14 @@ } }, "node_modules/@vitest/expect": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.7.tgz", - "integrity": "sha512-QP25f+YJhzPfHrHfYHtvRn+uvkCFCqFtW9CktfBxmB+25QqWsx7VB2As6f4GmwllHLDhXNHvqedwhvMmSnNmjw==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.8.tgz", + "integrity": "sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.7", - "@vitest/utils": "3.0.7", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -6571,13 +6330,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.7.tgz", - "integrity": "sha512-qui+3BLz9Eonx4EAuR/i+QlCX6AUZ35taDQgwGkK/Tw6/WgwodSrjN1X2xf69IA/643ZX5zNKIn2svvtZDrs4w==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.8.tgz", + "integrity": "sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.7", + "@vitest/spy": "3.0.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -6608,9 +6367,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.7.tgz", - "integrity": "sha512-CiRY0BViD/V8uwuEzz9Yapyao+M9M008/9oMOSQydwbwb+CMokEq3XVaF3XK/VWaOK0Jm9z7ENhybg70Gtxsmg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", + "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", "dev": true, "license": "MIT", "dependencies": { @@ -6621,13 +6380,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.7.tgz", - "integrity": "sha512-WeEl38Z0S2ZcuRTeyYqaZtm4e26tq6ZFqh5y8YD9YxfWuu0OFiGFUbnxNynwLjNRHPsXyee2M9tV7YxOTPZl2g==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.8.tgz", + "integrity": "sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.0.7", + "@vitest/utils": "3.0.8", "pathe": "^2.0.3" }, "funding": { @@ -6635,13 +6394,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.7.tgz", - "integrity": "sha512-eqTUryJWQN0Rtf5yqCGTQWsCFOQe4eNz5Twsu21xYEcnFJtMU5XvmG0vgebhdLlrHQTSq5p8vWHJIeJQV8ovsA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.8.tgz", + "integrity": "sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.7", + "@vitest/pretty-format": "3.0.8", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -6650,9 +6409,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.7.tgz", - "integrity": "sha512-4T4WcsibB0B6hrKdAZTM37ekuyFZt2cGbEGd2+L0P8ov15J1/HUsUaqkXEQPNAWr4BtPPe1gI+FYfMHhEKfR8w==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.8.tgz", + "integrity": "sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6663,13 +6422,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.7.tgz", - "integrity": "sha512-xePVpCRfooFX3rANQjwoditoXgWb1MaFbzmGuPP59MK6i13mrnDw/yEIyJudLeW6/38mCNcwCiJIGmpDPibAIg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.8.tgz", + "integrity": "sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.7", + "@vitest/pretty-format": "3.0.8", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -6871,22 +6630,22 @@ } }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -6944,9 +6703,9 @@ } }, "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7021,12 +6780,15 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { @@ -7048,7 +6810,6 @@ "version": "3.16.0", "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.16.0.tgz", "integrity": "sha512-sU7d/tfZiYrsIAXbdL/CNZld5bCkruzwT5KmqmadCJYxuLxHAOBjidxD5+iLmN/6xEfjcQq1l7OpsiCBlc4LzA==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -7058,7 +6819,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/anymatch": { "version": "3.1.3", @@ -7142,6 +6904,87 @@ "node": ">= 14" } }, + "node_modules/archiver-utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/archiver-utils/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/are-we-there-yet": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", @@ -7183,12 +7026,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, - "node_modules/array-flatten": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", - "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==", - "license": "MIT" - }, "node_modules/array-source": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/array-source/-/array-source-0.0.4.tgz", @@ -7253,53 +7090,71 @@ "license": "MIT" }, "node_modules/bare-events": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", - "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", "license": "Apache-2.0", "optional": true }, "node_modules/bare-fs": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", - "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.0.1.tgz", + "integrity": "sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "bare-events": "^2.0.0", - "bare-path": "^2.0.0", + "bare-path": "^3.0.0", "bare-stream": "^2.0.0" + }, + "engines": { + "bare": ">=1.7.0" } }, "node_modules/bare-os": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", - "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.5.1.tgz", + "integrity": "sha512-LvfVNDcWLw2AnIw5f2mWUgumW3I3N/WYGiWeimhQC1Ybt71n2FjlS9GJKeCnFeg1MKZHxzIFmpFnBXDI+sBeFg==", "dev": true, "license": "Apache-2.0", - "optional": true + "optional": true, + "engines": { + "bare": ">=1.14.0" + } }, "node_modules/bare-path": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", - "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { - "bare-os": "^2.1.0" + "bare-os": "^3.0.1" } }, "node_modules/bare-stream": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.1.tgz", - "integrity": "sha512-eVZbtKM+4uehzrsj49KtCy3Pbg7kO1pJ3SKZ1SFrIH/0pnj9scuGGgUlNDf/7qS8WKtGdiJY5Kyhs/ivYPTB/g==", + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } } }, "node_modules/base64-js": { @@ -7417,73 +7272,38 @@ } }, "node_modules/body-parser": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.2.tgz", - "integrity": "sha512-SNMk0OONlQ01uk8EPeiBvTW7W4ovpL5b1O3t1sjpPgfxOQ6BqQJ6XjxinDPR79Z6HdcD5zBBwr5ssiTlgdNztQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz", + "integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "3.1.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.5.2", - "on-finished": "2.4.1", - "qs": "6.13.0", + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.5.2", + "on-finished": "^2.4.1", + "qs": "^6.14.0", "raw-body": "^3.0.0", - "type-is": "~1.6.18" + "type-is": "^2.0.0" }, "engines": { "node": ">=18" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "license": "MIT", + "node_modules/body-parser/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", - "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "side-channel": "^1.1.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/body-parser/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/body-parser/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "node": ">=0.6" }, - "engines": { - "node": ">= 0.6" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/brace-expansion": { @@ -7509,9 +7329,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", - "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "funding": [ { "type": "opencollective", @@ -7660,19 +7480,6 @@ "node": ">=10" } }, - "node_modules/bullmq/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -7714,9 +7521,9 @@ } }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -7727,13 +7534,13 @@ } }, "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -7762,9 +7569,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001689", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz", - "integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==", + "version": "1.0.30001702", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001702.tgz", + "integrity": "sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==", "funding": [ { "type": "opencollective", @@ -7890,9 +7697,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", - "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "license": "MIT" }, "node_modules/class-transformer": { @@ -7947,82 +7754,6 @@ "node": ">=8" } }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "license": "ISC", - "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/cli-highlight/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/cli-highlight/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/cli-highlight/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cli-highlight/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/cli-spinners": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", @@ -8052,12 +7783,13 @@ } }, "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, "license": "ISC", "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/client-only": { @@ -8080,6 +7812,27 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -8297,12 +8050,12 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" } }, "node_modules/cookie-parser": { @@ -8318,6 +8071,15 @@ "node": ">= 0.8.0" } }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -8325,13 +8087,13 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", - "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz", + "integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.24.2" + "browserslist": "^4.24.4" }, "funding": { "type": "opencollective", @@ -8637,6 +8399,16 @@ "license": "Apache-2.0", "peer": true }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/discontinuous-range": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", @@ -8804,9 +8576,9 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", @@ -8830,9 +8602,9 @@ } }, "node_modules/dotenv-expand": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", - "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz", + "integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -8872,9 +8644,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.74", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz", - "integrity": "sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==", + "version": "1.5.113", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.113.tgz", + "integrity": "sha512-wjT2O4hX+wdWPJ76gWSkMhcHAV2PTMX+QetUCPYEdCIe+cxmgzzSSiGRCKW8nuh4mwKZlpv0xvoW7OF2X+wmHg==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -8903,12 +8675,11 @@ } }, "node_modules/engine.io": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", - "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", "license": "MIT", "dependencies": { - "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", @@ -8932,6 +8703,28 @@ "node": ">=10.0.0" } }, + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/engine.io/node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -8949,10 +8742,40 @@ } } }, + "node_modules/engine.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", "dev": true, "license": "MIT", "dependencies": { @@ -8975,6 +8798,16 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -9021,6 +8854,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", @@ -9148,13 +8996,13 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.2.tgz", - "integrity": "sha512-1105/17ZIMjmCOJOPNfVdbXafLCLj3hPmkmB7dLgt7XsQ/zkxSuDerE/xgO3RxoHysR1N1whmquY0lSn2O0VLg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz", + "integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==", "dev": true, "license": "MIT", "bin": { - "eslint-config-prettier": "build/bin/cli.js" + "eslint-config-prettier": "bin/cli.js" }, "peerDependencies": { "eslint": ">=7.0.0" @@ -9463,9 +9311,9 @@ ] }, "node_modules/expect-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", - "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.0.tgz", + "integrity": "sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -9515,19 +9363,6 @@ "node": ">= 18" } }, - "node_modules/express/node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/cookie": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", @@ -9563,42 +9398,12 @@ } } }, - "node_modules/express/node_modules/mime-db": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", - "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/mime-types": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", - "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.53.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "license": "MIT" }, - "node_modules/express/node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -9619,6 +9424,18 @@ "node": ">=4" } }, + "node_modules/external-editor/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -9676,16 +9493,26 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", - "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "BSD-3-Clause" }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -9750,47 +9577,22 @@ } }, "node_modules/finalhandler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.0.0.tgz", - "integrity": "sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -9823,9 +9625,9 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, @@ -9860,12 +9662,12 @@ } }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -9905,19 +9707,41 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" }, "engines": { "node": ">= 6" } }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -10045,12 +9869,33 @@ "node": ">=10" } }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/gauge/node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/gaxios": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", @@ -10089,26 +9934,14 @@ "node": ">= 14" } }, - "node_modules/gaxios/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/gcp-metadata": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", - "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", "license": "Apache-2.0", "dependencies": { - "gaxios": "^6.0.0", + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" }, "engines": { @@ -10168,17 +10001,17 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", - "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "get-proto": "^1.0.0", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", @@ -10235,6 +10068,7 @@ "resolved": "https://registry.npmjs.org/git-diff/-/git-diff-2.0.6.tgz", "integrity": "sha512-/Iu4prUrydE3Pb3lCBMbcSNIf81tgGt0W1ZwknnyF62t3tHmtiJTRj0f+1ZIhp3+Rh0ktz1pJVoa7ZXUCskivA==", "dev": true, + "license": "ISC", "dependencies": { "chalk": "^2.3.2", "diff": "^3.5.0", @@ -10251,6 +10085,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -10263,6 +10098,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -10277,6 +10113,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -10285,22 +10122,15 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/git-diff/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true, - "engines": { - "node": ">=0.3.1" - } + "license": "MIT" }, "node_modules/git-diff/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -10310,6 +10140,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -10319,6 +10150,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -10327,21 +10159,25 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", + "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, + "engines": { + "node": "20 || >=22" + }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -10369,21 +10205,23 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -10409,6 +10247,15 @@ "dev": true, "license": "MIT" }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -10455,15 +10302,6 @@ "uglify-js": "^3.1.4" } }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -10495,6 +10333,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -10522,15 +10375,6 @@ "he": "bin/he" } }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -10622,9 +10466,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", + "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -10664,9 +10508,9 @@ } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -10680,12 +10524,12 @@ } }, "node_modules/import-in-the-middle": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.12.0.tgz", - "integrity": "sha512-yAgSE7GmtRcu4ZUSFX/4v69UGXwugFFSdIQJ14LHPOPPQrWv8Y7O9PHsw8Ovk7bKCLe4sjXMbZFqGFcLHpZ89w==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.13.1.tgz", + "integrity": "sha512-k2V9wNm9B+ysuelDTHjI9d5KPc4l8zAZTGqj+pcynvWkypZd857ryzN8jNC7Pg2YZXNMJcHRPpaDyCBbNyVRpA==", "license": "Apache-2.0", "dependencies": { - "acorn": "^8.8.2", + "acorn": "^8.14.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^1.2.2", "module-details-from-path": "^1.0.3" @@ -10754,19 +10598,56 @@ "node": ">=12.0.0" } }, + "node_modules/inquirer/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/inquirer/node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "license": "ISC" + }, + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/ioredis": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.5.0.tgz", - "integrity": "sha512-7CutT89g23FfSa8MDoIFs2GYYa0PaNiW/OrT+nRyjRXHDZd17HmIgy+reOQ/yhh72NznNjGuS8kbCAcA4Ro4mw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.0.tgz", + "integrity": "sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==", "license": "MIT", "dependencies": { "@ioredis/commands": "^1.1.1", @@ -10831,9 +10712,9 @@ } }, "node_modules/is-core-module": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", - "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -11044,18 +10925,19 @@ } }, "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", + "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, + "engines": { + "node": "20 || >=22" + }, "funding": { "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jest-worker": { @@ -11090,9 +10972,9 @@ } }, "node_modules/jiti": { - "version": "1.21.6", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", - "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", "peer": true, "bin": { @@ -11230,34 +11112,36 @@ } }, "node_modules/kysely": { - "version": "0.27.5", - "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.5.tgz", - "integrity": "sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==", + "version": "0.27.6", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.6.tgz", + "integrity": "sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==", "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/kysely-codegen": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/kysely-codegen/-/kysely-codegen-0.17.0.tgz", - "integrity": "sha512-C36g6epial8cIOSBEWGI9sRfkKSsEzTcivhjPivtYFQnhMdXnrVFaUe7UMZHeSdXaHiWDqDOkReJgWLD8nPKdg==", + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/kysely-codegen/-/kysely-codegen-0.18.2.tgz", + "integrity": "sha512-EuhLJaofXQ2u7j8LdEBafbOKV8+Xdjgr+ywmaTF037SbAYUKmkst1dmDauzxtGp7KyYwoIDZmz7QVOsyYbb6Iw==", "dev": true, "license": "MIT", "dependencies": { "chalk": "4.1.2", - "dotenv": "^16.4.5", - "dotenv-expand": "^11.0.6", + "cosmiconfig": "^9.0.0", + "dotenv": "^16.4.7", + "dotenv-expand": "^12.0.1", "git-diff": "^2.0.6", "micromatch": "^4.0.8", "minimist": "^1.2.8", - "pluralize": "^8.0.0" + "pluralize": "^8.0.0", + "zod": "^3.24.2" }, "bin": { "kysely-codegen": "dist/cli/bin.js" }, "peerDependencies": { - "@libsql/kysely-libsql": "^0.3.0", + "@libsql/kysely-libsql": "^0.3.0 || ^0.4.1", "@tediousjs/connection-string": "^0.5.0", "better-sqlite3": ">=7.6.2", "kysely": "^0.27.0", @@ -11301,10 +11185,38 @@ } } }, + "node_modules/kysely-codegen/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/kysely-postgres-js": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kysely-postgres-js/-/kysely-postgres-js-2.0.0.tgz", "integrity": "sha512-R1tWx6/x3tSatWvsmbHJxpBZYhNNxcnMw52QzZaHKg7ZOWtHib4iZyEaw4gb2hNKVctWQ3jfMxZT/ZaEMK6kBQ==", + "license": "MIT", "peerDependencies": { "kysely": ">= 0.24.0 < 1", "postgres": ">= 3.4.0 < 4" @@ -11376,9 +11288,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.11.17", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.17.tgz", - "integrity": "sha512-Jr6v8thd5qRlOlc6CslSTzGzzQW03uiscab7KHQZX1Dfo4R6n6FDhZ0Hri6/X7edLIDv9gl4VMZXhxTjLnl0VQ==", + "version": "1.12.5", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.5.tgz", + "integrity": "sha512-DOjiaVjjSmap12ztyb4QgoFmUe/GbgnEXHu+R7iowk0lzDIjScvPAm8cK9RYTEobbRb0OPlwlZUGTTJPJg13Kw==", "license": "MIT" }, "node_modules/lilconfig": { @@ -11484,10 +11396,11 @@ } }, "node_modules/loglevel": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", - "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6.0" }, @@ -11497,9 +11410,9 @@ } }, "node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz", + "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==", "license": "Apache-2.0" }, "node_modules/loupe": { @@ -11694,21 +11607,21 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.53.0" }, "engines": { "node": ">= 0.6" @@ -11831,9 +11744,9 @@ "license": "BSD-3-Clause" }, "node_modules/mrmime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", - "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "license": "MIT", "engines": { "node": ">=10" @@ -11918,6 +11831,27 @@ "node": ">= 0.6" } }, + "node_modules/multer/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/multer/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -11962,16 +11896,21 @@ } }, "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "license": "MIT", + "peer": true, "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -11979,17 +11918,17 @@ } }, "node_modules/nan": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", - "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", + "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", "dev": true, "license": "MIT", "optional": true }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz", + "integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==", "funding": [ { "type": "github", @@ -12042,9 +11981,9 @@ "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -12057,9 +11996,9 @@ "license": "MIT" }, "node_modules/nest-commander": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/nest-commander/-/nest-commander-3.16.0.tgz", - "integrity": "sha512-sBNPKZY6RItDaQE1fncAgTxWdZD63dMfCjnndsRpH8OHuJP9cRO2xEsVkzbhPut/vAU5QmtTJ5w9W3IVhR5hVw==", + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/nest-commander/-/nest-commander-3.16.1.tgz", + "integrity": "sha512-04AqAVqMKtXKvJzkjSM3fERPSY8V78zvi5pWzXyegXdmovHGnJB+/l/YUNWa1EZc0ER6tqa6eI5tYFtS4ramjQ==", "license": "MIT", "dependencies": { "@fig/complete-commander": "^3.0.0", @@ -12123,18 +12062,18 @@ } }, "node_modules/nestjs-otel": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/nestjs-otel/-/nestjs-otel-6.1.2.tgz", - "integrity": "sha512-Xs/6/3ypf8ujijM0h7lO/3kGjev1cP19FcdOw4gUc71owBu3ktf0k3BXvo0lxb/vP+37j9Ljl6lf2cJtwJ9h5A==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/nestjs-otel/-/nestjs-otel-6.2.0.tgz", + "integrity": "sha512-F15GnWNrmHxDRdn0o2/cDx65gR7+s3xou1mEJ5vVONfOOYeneIJi1Mkf6h/Qu6NfO4SHPFPKGMXoovvTX1D8Iw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/host-metrics": "^0.35.4", + "@opentelemetry/host-metrics": "^0.35.5", "response-time": "^2.3.3" }, "peerDependencies": { - "@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0", - "@nestjs/core": "^9.0.0 || ^10.0.0 || ^11.0.0" + "@nestjs/common": ">= 10 < 12", + "@nestjs/core": ">= 10 < 12" } }, "node_modules/next": { @@ -12352,9 +12291,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -12364,9 +12303,9 @@ } }, "node_modules/oidc-token-hash": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", - "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz", + "integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==", "license": "MIT", "engines": { "node": "^10.13.0 || >=12.0.0" @@ -12482,6 +12421,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -12575,27 +12535,6 @@ "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", "license": "MIT" }, - "node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "license": "MIT" - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "license": "MIT", - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "license": "MIT" - }, "node_modules/parseley": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", @@ -12653,26 +12592,31 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, "node_modules/path-source": { "version": "0.1.3", @@ -12889,9 +12833,9 @@ } }, "node_modules/point-in-polygon-hao": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/point-in-polygon-hao/-/point-in-polygon-hao-1.2.3.tgz", - "integrity": "sha512-uZsWylGd8nthIYS8F7aSyM7Pot+4L/bgXheJcCNdRr4eLpsM/rMb3hIi5SqNxAVjUoDDao3QzCtdaVDzmeF9Cw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/point-in-polygon-hao/-/point-in-polygon-hao-1.2.4.tgz", + "integrity": "sha512-x2pcvXeqhRHlNRdhLs/tgFapAbSSe86wa/eqmj1G6pWftbEs5aVRJhRGM6FYSUERKu0PjekJzMq0gsI2XyiclQ==", "license": "MIT", "dependencies": { "robust-predicates": "^3.0.2" @@ -13047,9 +12991,10 @@ "peer": true }, "node_modules/postgres": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.4.tgz", - "integrity": "sha512-IbyN+9KslkqcXa8AO9fxpk97PA4pzewvpi2B3Dwy9u4zpV32QicaEdgmF3eSQUzdRk7ttDHQejNgAEr4XoeH4A==", + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.5.tgz", + "integrity": "sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==", + "license": "Unlicense", "peer": true, "engines": { "node": ">=12" @@ -13109,9 +13054,9 @@ } }, "node_modules/prettier": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", - "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -13325,12 +13270,6 @@ ], "license": "MIT" }, - "node_modules/queue-tick": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", - "license": "MIT" - }, "node_modules/railroad-diagrams": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", @@ -13533,6 +13472,33 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/react-email/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/react-email/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/react-email/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/react-email/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -13548,6 +13514,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/react-email/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/react-email/node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -13697,9 +13679,9 @@ } }, "node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "license": "MIT", "dependencies": { "abort-controller": "^3.0.0", @@ -13891,9 +13873,9 @@ } }, "node_modules/require-in-the-middle": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.4.0.tgz", - "integrity": "sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", "license": "MIT", "dependencies": { "debug": "^4.3.5", @@ -13905,9 +13887,9 @@ } }, "node_modules/resolve": { - "version": "1.22.9", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz", - "integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "license": "MIT", "dependencies": { "is-core-module": "^2.16.0", @@ -13917,6 +13899,9 @@ "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -13992,9 +13977,9 @@ } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -14021,99 +14006,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", - "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/jackspeak": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", - "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/lru-cache": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", - "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/robust-predicates": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", @@ -14121,9 +14013,9 @@ "license": "Unlicense" }, "node_modules/rollup": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", - "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz", + "integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14137,44 +14029,40 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.28.1", - "@rollup/rollup-android-arm64": "4.28.1", - "@rollup/rollup-darwin-arm64": "4.28.1", - "@rollup/rollup-darwin-x64": "4.28.1", - "@rollup/rollup-freebsd-arm64": "4.28.1", - "@rollup/rollup-freebsd-x64": "4.28.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", - "@rollup/rollup-linux-arm-musleabihf": "4.28.1", - "@rollup/rollup-linux-arm64-gnu": "4.28.1", - "@rollup/rollup-linux-arm64-musl": "4.28.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", - "@rollup/rollup-linux-riscv64-gnu": "4.28.1", - "@rollup/rollup-linux-s390x-gnu": "4.28.1", - "@rollup/rollup-linux-x64-gnu": "4.28.1", - "@rollup/rollup-linux-x64-musl": "4.28.1", - "@rollup/rollup-win32-arm64-msvc": "4.28.1", - "@rollup/rollup-win32-ia32-msvc": "4.28.1", - "@rollup/rollup-win32-x64-msvc": "4.28.1", + "@rollup/rollup-android-arm-eabi": "4.34.9", + "@rollup/rollup-android-arm64": "4.34.9", + "@rollup/rollup-darwin-arm64": "4.34.9", + "@rollup/rollup-darwin-x64": "4.34.9", + "@rollup/rollup-freebsd-arm64": "4.34.9", + "@rollup/rollup-freebsd-x64": "4.34.9", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.9", + "@rollup/rollup-linux-arm-musleabihf": "4.34.9", + "@rollup/rollup-linux-arm64-gnu": "4.34.9", + "@rollup/rollup-linux-arm64-musl": "4.34.9", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.9", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.9", + "@rollup/rollup-linux-riscv64-gnu": "4.34.9", + "@rollup/rollup-linux-s390x-gnu": "4.34.9", + "@rollup/rollup-linux-x64-gnu": "4.34.9", + "@rollup/rollup-linux-x64-musl": "4.34.9", + "@rollup/rollup-win32-arm64-msvc": "4.34.9", + "@rollup/rollup-win32-ia32-msvc": "4.34.9", + "@rollup/rollup-win32-x64-msvc": "4.34.9", "fsevents": "~2.3.2" } }, "node_modules/router": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.0.0.tgz", - "integrity": "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz", + "integrity": "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==", "license": "MIT", "dependencies": { - "array-flatten": "3.0.0", - "is-promise": "4.0.0", - "methods": "~1.1.2", - "parseurl": "~1.3.3", - "path-to-regexp": "^8.0.0", - "setprototypeof": "1.2.0", - "utils-merge": "1.0.1" + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 18" } }, "node_modules/run-async": { @@ -14349,6 +14237,27 @@ "node": ">= 0.6" } }, + "node_modules/send/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -14488,6 +14397,7 @@ "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -14505,6 +14415,7 @@ "resolved": "https://registry.npmjs.org/shelljs.exec/-/shelljs.exec-1.1.8.tgz", "integrity": "sha512-vFILCw+lzUtiwBAHV8/Ex8JsFjelFMdhONIsgKNLgTzeRckp2AOYRQtHJE/9LhNvdMmE27AGtzWx0+DHpwIwSw==", "dev": true, + "license": "ISC", "engines": { "node": ">= 4.0.0" } @@ -14515,6 +14426,7 @@ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -14737,6 +14649,19 @@ } } }, + "node_modules/socket.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/socket.io/node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -14754,14 +14679,43 @@ } } }, + "node_modules/socket.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", "engines": { - "node": ">= 8" + "node": ">=0.10.0" } }, "node_modules/source-map-js": { @@ -14784,16 +14738,6 @@ "source-map": "^0.6.0" } }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -14824,9 +14768,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.20", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", - "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", "dev": true, "license": "CC0-1.0" }, @@ -14861,6 +14805,22 @@ "sql-formatter": "bin/sql-formatter-cli.cjs" } }, + "node_modules/sql-highlight": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.0.0.tgz", + "integrity": "sha512-+fLpbAbWkQ+d0JEchJT/NrRRXbYRNbG15gFpANx73EwxQB1PRjj+k/OI0GTU0J63g8ikGkJECQp9z8XEJZvPRw==", + "funding": [ + "https://github.com/scriptcoded/sql-highlight?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/scriptcoded" + } + ], + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/ssh-remote-port-forward": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz", @@ -14924,9 +14884,9 @@ } }, "node_modules/std-env": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", - "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz", + "integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==", "dev": true, "license": "MIT" }, @@ -14945,13 +14905,12 @@ } }, "node_modules/streamx": { - "version": "2.21.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.21.1.tgz", - "integrity": "sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==", + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", + "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", "license": "MIT", "dependencies": { "fast-fifo": "^1.3.2", - "queue-tick": "^1.0.1", "text-decoder": "^1.1.0" }, "optionalDependencies": { @@ -14996,7 +14955,16 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -15008,6 +14976,42 @@ "node": ">=8" } }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", @@ -15021,6 +15025,15 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -15103,6 +15116,93 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "peer": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/sucrase/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC", + "peer": true + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -15164,9 +15264,9 @@ } }, "node_modules/systeminformation": { - "version": "5.22.9", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.22.9.tgz", - "integrity": "sha512-qUWJhQ9JSBhdjzNUQywpvc0icxUAjMY3sZqUoS0GOtaJV9Ijq8s9zEP8Gaqmymn1dOefcICyPXK1L3kgKxlUpg==", + "version": "5.23.8", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.23.8.tgz", + "integrity": "sha512-Osd24mNKe6jr/YoXLLK3k8TMdzaxDffhpCxgkfgBHcapykIkd50HXThM3TCEuHO2pPuCsSx2ms/SunqhU5MmsQ==", "license": "MIT", "os": [ "darwin", @@ -15228,27 +15328,27 @@ } }, "node_modules/tailwindcss-email-variants": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tailwindcss-email-variants/-/tailwindcss-email-variants-3.0.3.tgz", - "integrity": "sha512-gchBYFNprLfRtmxnrglF4tayxFbv+hBV+3obXQycrBcluLj5CQF8uJsZH6ir0aIGQXfh5ukMdIkEgKOBzrBYxA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tailwindcss-email-variants/-/tailwindcss-email-variants-3.0.4.tgz", + "integrity": "sha512-ohtLSifyWQDAtddJnfbcxkIDCIyXp6Yb83hXRprrS+/2dSyme4OlUZAP+TDwQc0K8D0LAw80eKI6psgejxys8A==", "license": "MIT", "engines": { "node": ">=18" }, "peerDependencies": { - "tailwindcss": ">=3.4.0" + "tailwindcss": ">=3.4.0 < 4" } }, "node_modules/tailwindcss-mso": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tailwindcss-mso/-/tailwindcss-mso-2.0.1.tgz", - "integrity": "sha512-77QvlGNqduGCtwTjLJog+PLD5YMNRR6FdbBTS6DcfbmO+9q0rSLgy/0y70wZ/jbDx152g6i5w3noFpHq8hzYPw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tailwindcss-mso/-/tailwindcss-mso-2.0.2.tgz", + "integrity": "sha512-GaR8RW/Kan+YWEQ9Y9Ah6AYy7R2wEQ3X++YK4ffJVWycCTd6ryMLezqmyhi7KWHqsgQOb4nhjJYayI+JF44BXw==", "license": "MIT", "engines": { "node": ">=18.20" }, "peerDependencies": { - "tailwindcss": ">=3.4.0" + "tailwindcss": ">=3.4.0 < 4" } }, "node_modules/tailwindcss-preset-email": { @@ -15278,9 +15378,9 @@ } }, "node_modules/tailwindcss/node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "funding": [ { "type": "opencollective", @@ -15298,7 +15398,7 @@ "license": "MIT", "peer": true, "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -15334,9 +15434,9 @@ } }, "node_modules/tar-fs": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", - "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz", + "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==", "dev": true, "license": "MIT", "dependencies": { @@ -15344,8 +15444,8 @@ "tar-stream": "^3.1.5" }, "optionalDependencies": { - "bare-fs": "^2.1.1", - "bare-path": "^2.1.0" + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" } }, "node_modules/tar-stream": { @@ -15381,9 +15481,9 @@ } }, "node_modules/terser": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", - "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -15400,9 +15500,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", - "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "dev": true, "license": "MIT", "dependencies": { @@ -15451,6 +15551,24 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/terser-webpack-plugin/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -15523,6 +15641,50 @@ "balanced-match": "^1.0.0" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/test-exclude/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/test-exclude/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -15539,6 +15701,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/test-exclude/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/testcontainers": { "version": "10.18.0", "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.18.0.tgz", @@ -15594,6 +15773,7 @@ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "license": "MIT", + "peer": true, "dependencies": { "any-promise": "^1.0.0" } @@ -15603,6 +15783,7 @@ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "license": "MIT", + "peer": true, "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -15754,9 +15935,9 @@ "peer": true }, "node_modules/tsconfck": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.4.tgz", - "integrity": "sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz", + "integrity": "sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==", "dev": true, "license": "MIT", "bin": { @@ -15857,27 +16038,6 @@ "node": ">= 0.6" } }, - "node_modules/type-is/node_modules/mime-db": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", - "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-types": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", - "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.53.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -15885,25 +16045,23 @@ "license": "MIT" }, "node_modules/typeorm": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.20.tgz", - "integrity": "sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==", + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.21.tgz", + "integrity": "sha512-lh4rUWl1liZGjyPTWpwcK8RNI5x4ekln+/JJOox1wCd7xbucYDOXWD+1cSzTN3L0wbTGxxOtloM5JlxbOxEufA==", "license": "MIT", "dependencies": { "@sqltools/formatter": "^1.2.5", + "ansis": "^3.9.0", "app-root-path": "^3.1.0", "buffer": "^6.0.3", - "chalk": "^4.1.2", - "cli-highlight": "^2.1.11", "dayjs": "^1.11.9", "debug": "^4.3.4", "dotenv": "^16.0.3", - "glob": "^10.3.10", - "mkdirp": "^2.1.3", - "reflect-metadata": "^0.2.1", + "glob": "^10.4.5", "sha.js": "^2.4.11", + "sql-highlight": "^6.0.0", "tslib": "^2.5.0", - "uuid": "^9.0.0", + "uuid": "^11.0.5", "yargs": "^17.6.2" }, "bin": { @@ -15920,17 +16078,18 @@ "peerDependencies": { "@google-cloud/spanner": "^5.18.0", "@sap/hana-client": "^2.12.25", - "better-sqlite3": "^7.1.2 || ^8.0.0 || ^9.0.0", + "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", "hdb-pool": "^0.1.6", "ioredis": "^5.0.4", "mongodb": "^5.8.0", - "mssql": "^9.1.1 || ^10.0.1", + "mssql": "^9.1.1 || ^10.0.1 || ^11.0.1", "mysql2": "^2.2.5 || ^3.0.1", "oracledb": "^6.3.0", "pg": "^8.5.1", "pg-native": "^3.0.0", "pg-query-stream": "^4.0.0", "redis": "^3.1.1 || ^4.0.0", + "reflect-metadata": "^0.1.14 || ^0.2.0", "sql.js": "^1.4.0", "sqlite3": "^5.0.3", "ts-node": "^10.7.0", @@ -15990,6 +16149,15 @@ } } }, + "node_modules/typeorm/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/typeorm/node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -16014,38 +16182,95 @@ "ieee754": "^1.2.1" } }, - "node_modules/typeorm/node_modules/mkdirp": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", - "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", - "license": "MIT", + "node_modules/typeorm/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, "bin": { - "mkdirp": "dist/cjs/src/bin.js" + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/typeorm/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/typeorm/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/typeorm/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -16182,9 +16407,9 @@ } }, "node_modules/unplugin": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.0.tgz", - "integrity": "sha512-5liCNPuJW8dqh3+DM6uNM2EI3MLLpCKp/KY+9pB5M2S2SR2qvvDHhKgBOaTWEbZTAws3CXfB0rKTIolWKL05VQ==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", "dev": true, "license": "MIT", "dependencies": { @@ -16211,9 +16436,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "funding": [ { "type": "opencollective", @@ -16231,7 +16456,7 @@ "license": "MIT", "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -16293,6 +16518,19 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -16323,21 +16561,21 @@ } }, "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz", + "integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -16346,19 +16584,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -16379,13 +16623,19 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, "node_modules/vite-node": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.7.tgz", - "integrity": "sha512-2fX0QwX4GkkkpULXdT1Pf4q0tC1i1lFOyseKoonavXUNlQ77KpW2XqBGGNIm/J4Ows4KxgGJzDguYVPKwG/n5A==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.8.tgz", + "integrity": "sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg==", "dev": true, "license": "MIT", "dependencies": { @@ -16426,9 +16676,9 @@ } }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", "cpu": [ "ppc64" ], @@ -16439,13 +16689,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", "cpu": [ "arm" ], @@ -16456,13 +16706,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", "cpu": [ "arm64" ], @@ -16473,13 +16723,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", "cpu": [ "x64" ], @@ -16490,13 +16740,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", "cpu": [ "arm64" ], @@ -16507,13 +16757,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", "cpu": [ "x64" ], @@ -16524,13 +16774,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", "cpu": [ "arm64" ], @@ -16541,13 +16791,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", "cpu": [ "x64" ], @@ -16558,13 +16808,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", "cpu": [ "arm" ], @@ -16575,13 +16825,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", "cpu": [ "arm64" ], @@ -16592,13 +16842,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", "cpu": [ "ia32" ], @@ -16609,13 +16859,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", "cpu": [ "loong64" ], @@ -16626,13 +16876,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", "cpu": [ "mips64el" ], @@ -16643,13 +16893,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", "cpu": [ "ppc64" ], @@ -16660,13 +16910,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", "cpu": [ "riscv64" ], @@ -16677,13 +16927,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", "cpu": [ "s390x" ], @@ -16694,13 +16944,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", "cpu": [ "x64" ], @@ -16711,13 +16961,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", "cpu": [ "x64" ], @@ -16728,13 +16978,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", "cpu": [ "x64" ], @@ -16745,13 +17012,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", "cpu": [ "x64" ], @@ -16762,13 +17029,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", "cpu": [ "arm64" ], @@ -16779,13 +17046,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", "cpu": [ "ia32" ], @@ -16796,13 +17063,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", "cpu": [ "x64" ], @@ -16813,13 +17080,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/vite/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -16827,38 +17094,40 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" } }, "node_modules/vite/node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, "funding": [ { @@ -16876,7 +17145,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -16885,19 +17154,19 @@ } }, "node_modules/vitest": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.7.tgz", - "integrity": "sha512-IP7gPK3LS3Fvn44x30X1dM9vtawm0aesAa2yBIZ9vQf+qB69NXC5776+Qmcr7ohUXIQuLhk7xQR0aSUIDPqavg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.8.tgz", + "integrity": "sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.0.7", - "@vitest/mocker": "3.0.7", - "@vitest/pretty-format": "^3.0.7", - "@vitest/runner": "3.0.7", - "@vitest/snapshot": "3.0.7", - "@vitest/spy": "3.0.7", - "@vitest/utils": "3.0.7", + "@vitest/expect": "3.0.8", + "@vitest/mocker": "3.0.8", + "@vitest/pretty-format": "^3.0.8", + "@vitest/runner": "3.0.8", + "@vitest/snapshot": "3.0.8", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.1.0", @@ -16909,7 +17178,7 @@ "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.0.7", + "vite-node": "3.0.8", "why-is-node-running": "^2.3.0" }, "bin": { @@ -16925,8 +17194,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.0.7", - "@vitest/ui": "3.0.7", + "@vitest/browser": "3.0.8", + "@vitest/ui": "3.0.8", "happy-dom": "*", "jsdom": "*" }, @@ -17074,6 +17343,24 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/webpack/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/webpack/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -17118,6 +17405,29 @@ "dev": true, "license": "MIT" }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/webpack/node_modules/schema-utils": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", @@ -17237,6 +17547,48 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -17289,9 +17641,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", - "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -17366,6 +17718,16 @@ "engines": { "node": ">= 14" } + }, + "node_modules/zod": { + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/server/package.json b/server/package.json index ff1a987609..bc12bc67a0 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "immich", - "version": "1.128.0", + "version": "1.129.0", "description": "", "author": "", "private": true, @@ -43,7 +43,6 @@ "@nestjs/platform-socket.io": "^11.0.4", "@nestjs/schedule": "^5.0.0", "@nestjs/swagger": "^11.0.2", - "@nestjs/typeorm": "^11.0.0", "@nestjs/websockets": "^11.0.4", "@opentelemetry/auto-instrumentations-node": "^0.56.0", "@opentelemetry/context-async-hooks": "^1.24.0", @@ -58,7 +57,8 @@ "chokidar": "^3.5.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", - "cookie-parser": "^1.4.6", + "cookie": "^1.0.2", + "cookie-parser": "^1.4.7", "exiftool-vendored": "^28.3.1", "fast-glob": "^3.3.2", "fluent-ffmpeg": "^2.1.2", @@ -107,14 +107,14 @@ "@types/archiver": "^6.0.0", "@types/async-lock": "^1.4.2", "@types/bcrypt": "^5.0.0", - "@types/cookie-parser": "^1.4.3", + "@types/cookie-parser": "^1.4.8", "@types/express": "^4.17.17", "@types/fluent-ffmpeg": "^2.1.21", "@types/js-yaml": "^4.0.9", "@types/lodash": "^4.14.197", "@types/mock-fs": "^4.13.1", "@types/multer": "^1.4.7", - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "@types/nodemailer": "^6.4.14", "@types/picomatch": "^3.0.0", "@types/pngjs": "^6.0.5", @@ -131,7 +131,7 @@ "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-unicorn": "^56.0.1", "globals": "^16.0.0", - "kysely-codegen": "^0.17.0", + "kysely-codegen": "^0.18.0", "mock-fs": "^5.2.0", "node-addon-api": "^8.3.0", "pngjs": "^7.0.0", diff --git a/server/src/app.module.ts b/server/src/app.module.ts index b02d869a1e..3cc0446306 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -1,8 +1,7 @@ import { BullModule } from '@nestjs/bullmq'; import { Inject, Module, OnModuleDestroy, OnModuleInit, ValidationPipe } from '@nestjs/common'; -import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE, ModuleRef } from '@nestjs/core'; +import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core'; import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { PostgresJSDialect } from 'kysely-postgres-js'; import { ClsModule } from 'nestjs-cls'; import { KyselyModule } from 'nestjs-kysely'; @@ -11,7 +10,6 @@ import postgres from 'postgres'; import { commands } from 'src/commands'; import { IWorker } from 'src/constants'; import { controllers } from 'src/controllers'; -import { entities } from 'src/entities'; import { ImmichWorker } from 'src/enum'; import { AuthGuard } from 'src/middleware/auth.guard'; import { ErrorInterceptor } from 'src/middleware/error.interceptor'; @@ -27,7 +25,6 @@ import { teardownTelemetry, TelemetryRepository } from 'src/repositories/telemet import { services } from 'src/services'; import { AuthService } from 'src/services/auth.service'; import { CliService } from 'src/services/cli.service'; -import { DatabaseService } from 'src/services/database.service'; const common = [...repositories, ...services, GlobalExceptionFilter]; @@ -48,18 +45,6 @@ const imports = [ BullModule.registerQueue(...bull.queues), ClsModule.forRoot(cls.config), OpenTelemetryModule.forRoot(otel), - TypeOrmModule.forRootAsync({ - inject: [ModuleRef], - useFactory: (moduleRef: ModuleRef) => { - return { - ...database.config.typeorm, - poolErrorHandler: (error) => { - moduleRef.get(DatabaseService, { strict: false }).handleConnectionError(error); - }, - }; - }, - }), - TypeOrmModule.forFeature(entities), KyselyModule.forRoot({ dialect: new PostgresJSDialect({ postgres: postgres(database.config.kysely) }), log(event) { diff --git a/server/src/bin/sync-sql.ts b/server/src/bin/sync-sql.ts index a9f5d72ec9..61c19c02fb 100644 --- a/server/src/bin/sync-sql.ts +++ b/server/src/bin/sync-sql.ts @@ -3,7 +3,6 @@ import { INestApplication } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { SchedulerRegistry } from '@nestjs/schedule'; import { Test } from '@nestjs/testing'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { ClassConstructor } from 'class-transformer'; import { PostgresJSDialect } from 'kysely-postgres-js'; import { ClsModule } from 'nestjs-cls'; @@ -14,15 +13,13 @@ import { join } from 'node:path'; import postgres from 'postgres'; import { format } from 'sql-formatter'; import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators'; -import { entities } from 'src/entities'; import { repositories } from 'src/repositories'; import { AccessRepository } from 'src/repositories/access.repository'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { AuthService } from 'src/services/auth.service'; -import { Logger } from 'typeorm'; -export class SqlLogger implements Logger { +export class SqlLogger { queries: string[] = []; errors: Array<{ error: string | Error; query: string }> = []; @@ -38,11 +35,6 @@ export class SqlLogger implements Logger { logQueryError(error: string | Error, query: string) { this.errors.push({ error, query }); } - - logQuerySlow() {} - logSchemaBuild() {} - logMigration() {} - log() {} } const reflector = new Reflector(); @@ -94,13 +86,6 @@ class SqlGenerator { }, }), ClsModule.forRoot(cls.config), - TypeOrmModule.forRoot({ - ...database.config.typeorm, - entities, - logging: ['query'], - logger: this.sqlLogger, - }), - TypeOrmModule.forFeature(entities), OpenTelemetryModule.forRoot(otel), ], providers: [...repositories, AuthService, SchedulerRegistry], diff --git a/server/src/constants.ts b/server/src/constants.ts index 20ce7dd497..3e946578ab 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -7,10 +7,6 @@ export const POSTGRES_VERSION_RANGE = '>=14.0.0'; export const VECTORS_VERSION_RANGE = '>=0.2 <0.4'; export const VECTOR_VERSION_RANGE = '>=0.5 <1'; -export const ASSET_FILE_CONFLICT_KEYS = ['assetId', 'type'] as const; -export const EXIF_CONFLICT_KEYS = ['assetId'] as const; -export const JOB_STATUS_CONFLICT_KEYS = ['assetId'] as const; - export const NEXT_RELEASE = 'NEXT_RELEASE'; export const LIFECYCLE_EXTENSION = 'x-immich-lifecycle'; export const DEPRECATED_IN_PREFIX = 'This property was deprecated in '; diff --git a/server/src/controllers/api-key.controller.ts b/server/src/controllers/api-key.controller.ts index 4691ce05ef..08efd753cf 100644 --- a/server/src/controllers/api-key.controller.ts +++ b/server/src/controllers/api-key.controller.ts @@ -4,13 +4,13 @@ import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto, APIKeyUpda import { AuthDto } from 'src/dtos/auth.dto'; import { Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { APIKeyService } from 'src/services/api-key.service'; +import { ApiKeyService } from 'src/services/api-key.service'; import { UUIDParamDto } from 'src/validation'; @ApiTags('API Keys') @Controller('api-keys') export class APIKeyController { - constructor(private service: APIKeyService) {} + constructor(private service: ApiKeyService) {} @Post() @Authenticated({ permission: Permission.API_KEY_CREATE }) diff --git a/server/src/controllers/timeline.controller.ts b/server/src/controllers/timeline.controller.ts index 92de84d346..906664e717 100644 --- a/server/src/controllers/timeline.controller.ts +++ b/server/src/controllers/timeline.controller.ts @@ -1,16 +1,17 @@ import { Controller, Get, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { AssetResponseDto } from 'src/dtos/asset-response.dto'; +import { AssetResponseDto, hexOrBufferToBase64 } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { TimeBucketAssetDto, TimeBucketDto, TimeBucketResponseDto } from 'src/dtos/time-bucket.dto'; -import { Permission } from 'src/enum'; +import { LiteTimeBucketAssetResponseDto, LiteTimeBucketDto, LiteTimeBucketResponseDto, TimeBucketAssetDto, TimeBucketDto, TimeBucketResponseDto } from 'src/dtos/time-bucket.dto'; +import { AssetStatus, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { TimelineService } from 'src/services/timeline.service'; + @ApiTags('Timeline') @Controller('timeline') export class TimelineController { - constructor(private service: TimelineService) {} + constructor(private service: TimelineService) { } @Get('buckets') @Authenticated({ permission: Permission.ASSET_READ, sharedLink: true }) @@ -23,4 +24,29 @@ export class TimelineController { getTimeBucket(@Auth() auth: AuthDto, @Query() dto: TimeBucketAssetDto): Promise { return this.service.getTimeBucket(auth, dto) as Promise; } + + @Get('liteBucket') + @Authenticated({ permission: Permission.ASSET_READ, sharedLink: true }) + async getLiteTimeBucket(@Auth() auth: AuthDto, @Query() dto: LiteTimeBucketDto): Promise { + const paginated = await this.service.getLiteTimeBucket(auth, dto); + const liteAssets = paginated.items.map((item) => { + const liteAsset: LiteTimeBucketAssetResponseDto = { + id: item.id!, + isFavorite: item.isFavorite || false, + isArchived: item.isArchived || false, + isTrashed: item.status === AssetStatus.TRASHED, + localDateTime: item.localDateTime!, + height: item.height!, + width: item.width!, + thumbhash: item.thumbhash ? hexOrBufferToBase64(item.thumbhash) : null + }; + + return liteAsset; + }); + return { + liteAssets, + hasNextPage: paginated.hasNextPage + } + } + } diff --git a/server/src/database.ts b/server/src/database.ts index 46d33d916d..9caa0c196e 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -1,5 +1,5 @@ import { sql } from 'kysely'; -import { Permission } from 'src/enum'; +import { AssetStatus, AssetType, Permission } from 'src/enum'; export type AuthUser = { id: string; @@ -10,11 +10,74 @@ export type AuthUser = { quotaSizeInBytes: number | null; }; +export type Library = { + id: string; + ownerId: string; + createdAt: Date; + updatedAt: Date; + updateId: string; + name: string; + importPaths: string[]; + exclusionPatterns: string[]; + deletedAt: Date | null; + refreshedAt: Date | null; + assets?: Asset[]; +}; + export type AuthApiKey = { id: string; permissions: Permission[]; }; +export type ApiKey = { + id: string; + name: string; + userId: string; + createdAt: Date; + updatedAt: Date; + permissions: Permission[]; +}; + +export type User = { + id: string; + name: string; + email: string; + profileImagePath: string; + profileChangedAt: Date; +}; + +export type Asset = { + createdAt: Date; + updatedAt: Date; + deletedAt: Date | null; + id: string; + updateId: string; + status: AssetStatus; + checksum: Buffer; + deviceAssetId: string; + deviceId: string; + duplicateId: string | null; + duration: string | null; + encodedVideoPath: string | null; + fileCreatedAt: Date | null; + fileModifiedAt: Date | null; + isArchived: boolean; + isExternal: boolean; + isFavorite: boolean; + isOffline: boolean; + isVisible: boolean; + libraryId: string | null; + livePhotoVideoId: string | null; + localDateTime: Date | null; + originalFileName: string; + originalPath: string; + ownerId: string; + sidecarPath: string | null; + stackId: string | null; + thumbhash: Buffer | null; + type: AssetType; +}; + export type AuthSharedLink = { id: string; expiresAt: Date | null; @@ -29,6 +92,17 @@ export type AuthSession = { id: string; }; +export type Partner = { + sharedById: string; + sharedBy: User; + sharedWithId: string; + sharedWith: User; + createdAt: Date; + updatedAt: Date; + updateId: string; + inTimeline: boolean; +}; + export const columns = { ackEpoch: (columnName: 'createdAt' | 'updatedAt' | 'deletedAt') => sql.raw(`extract(epoch from "${columnName}")::text`).as('ackEpoch'), @@ -54,4 +128,46 @@ export const columns = { userDto: ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'], tagDto: ['id', 'value', 'createdAt', 'updatedAt', 'color', 'parentId'], apiKey: ['id', 'name', 'userId', 'createdAt', 'updatedAt', 'permissions'], + syncAsset: [ + 'id', + 'ownerId', + 'thumbhash', + 'checksum', + 'fileCreatedAt', + 'fileModifiedAt', + 'localDateTime', + 'type', + 'deletedAt', + 'isFavorite', + 'isVisible', + 'updateId', + ], + syncAssetExif: [ + 'exif.assetId', + 'exif.description', + 'exif.exifImageWidth', + 'exif.exifImageHeight', + 'exif.fileSizeInByte', + 'exif.orientation', + 'exif.dateTimeOriginal', + 'exif.modifyDate', + 'exif.timeZone', + 'exif.latitude', + 'exif.longitude', + 'exif.projectionType', + 'exif.city', + 'exif.state', + 'exif.country', + 'exif.make', + 'exif.model', + 'exif.lensModel', + 'exif.fNumber', + 'exif.focalLength', + 'exif.iso', + 'exif.exposureTime', + 'exif.profileDescription', + 'exif.rating', + 'exif.fps', + 'exif.updateId', + ], } as const; diff --git a/server/src/db.d.ts b/server/src/db.d.ts index 4c75562ba1..85aade2c9b 100644 --- a/server/src/db.d.ts +++ b/server/src/db.d.ts @@ -4,7 +4,8 @@ */ import type { ColumnType } from 'kysely'; -import { Permission, SyncEntityType } from 'src/enum'; +import { OnThisDayData } from 'src/entities/memory.entity'; +import { AssetType, MemoryType, Permission, SyncEntityType } from 'src/enum'; export type ArrayType = ArrayTypeImpl extends (infer U)[] ? U[] : ArrayTypeImpl; @@ -118,6 +119,13 @@ export interface AssetJobStatus { thumbnailAt: Timestamp | null; } +export interface AssetsAudit { + deletedAt: Generated; + id: Generated; + assetId: string; + ownerId: string; +} + export interface Assets { checksum: Buffer; createdAt: Generated; @@ -145,7 +153,7 @@ export interface Assets { stackId: string | null; status: Generated; thumbhash: Buffer | null; - type: string; + type: AssetType; updatedAt: Generated; updateId: Generated; } @@ -167,6 +175,8 @@ export interface Audit { export interface Exif { assetId: string; + updateId: Generated; + updatedAt: Generated; autoStackId: string | null; bitsPerSample: number | null; city: string | null; @@ -231,7 +241,7 @@ export interface Libraries { export interface Memories { createdAt: Generated; - data: Json; + data: OnThisDayData; deletedAt: Timestamp | null; hideAt: Timestamp | null; id: Generated; @@ -240,7 +250,7 @@ export interface Memories { ownerId: string; seenAt: Timestamp | null; showAt: Timestamp | null; - type: string; + type: MemoryType; updatedAt: Generated; updateId: Generated; } @@ -458,6 +468,7 @@ export interface DB { asset_job_status: AssetJobStatus; asset_stack: AssetStack; assets: Assets; + assets_audit: AssetsAudit; audit: Audit; exif: Exif; face_search: FaceSearch; diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index 9a963e1e98..056da5abe5 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -102,7 +102,7 @@ const mapStack = (entity: AssetEntity) => { }; // if an asset is jsonified in the DB before being returned, its buffer fields will be hex-encoded strings -const hexOrBufferToBase64 = (encoded: string | Buffer) => { +export const hexOrBufferToBase64 = (encoded: string | Buffer) => { if (typeof encoded === 'string') { return Buffer.from(encoded.slice(2), 'hex').toString('base64'); } @@ -110,7 +110,7 @@ const hexOrBufferToBase64 = (encoded: string | Buffer) => { return encoded.toString('base64'); }; -export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): AssetResponseDto { +export function mapAsset(entity: AssetEntity & { dimensions?: any, height?: any, width?: any }, options: AssetMapOptions = {}): AssetResponseDto & { dimensions?: any, height?: any, width?: any } { const { stripMetadata = false, withStack = false } = options; if (stripMetadata) { @@ -148,6 +148,9 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As isTrashed: !!entity.deletedAt, duration: entity.duration ?? '0:00:00.00000', exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined, + dimensions: entity.dimensions, + height: entity.height, + width: entity.width, livePhotoVideoId: entity.livePhotoVideoId, tags: entity.tags?.map((tag) => mapTag(tag)), people: peopleWithFaces(entity.faces), diff --git a/server/src/dtos/library.dto.ts b/server/src/dtos/library.dto.ts index 7fb363dd9a..a0aaace13d 100644 --- a/server/src/dtos/library.dto.ts +++ b/server/src/dtos/library.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { ArrayMaxSize, ArrayUnique, IsNotEmpty, IsString } from 'class-validator'; -import { LibraryEntity } from 'src/entities/library.entity'; +import { Library } from 'src/database'; import { Optional, ValidateUUID } from 'src/validation'; export class CreateLibraryDto { @@ -120,7 +120,7 @@ export class LibraryStatsResponseDto { usage = 0; } -export function mapLibrary(entity: LibraryEntity): LibraryResponseDto { +export function mapLibrary(entity: Library): LibraryResponseDto { let assetCount = 0; if (entity.assets) { assetCount = entity.assets.length; diff --git a/server/src/dtos/person.dto.ts b/server/src/dtos/person.dto.ts index 0778c35b8f..49f3416b9a 100644 --- a/server/src/dtos/person.dto.ts +++ b/server/src/dtos/person.dto.ts @@ -7,6 +7,7 @@ import { AuthDto } from 'src/dtos/auth.dto'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { SourceType } from 'src/enum'; +import { asDateString } from 'src/utils/date'; import { IsDateStringFormat, MaxDateString, @@ -32,7 +33,7 @@ export class PersonCreateDto { @MaxDateString(() => DateTime.now(), { message: 'Birth date cannot be in the future' }) @IsDateStringFormat('yyyy-MM-dd') @Optional({ nullable: true }) - birthDate?: string | null; + birthDate?: Date | null; /** * Person visibility @@ -222,7 +223,7 @@ export function mapPerson(person: PersonEntity): PersonResponseDto { return { id: person.id, name: person.name, - birthDate: person.birthDate, + birthDate: asDateString(person.birthDate), thumbnailPath: person.thumbnailPath, isHidden: person.isHidden, isFavorite: person.isFavorite, diff --git a/server/src/dtos/sync.dto.ts b/server/src/dtos/sync.dto.ts index d191c82bb3..a035f8ecb9 100644 --- a/server/src/dtos/sync.dto.ts +++ b/server/src/dtos/sync.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsInt, IsPositive, IsString } from 'class-validator'; import { AssetResponseDto } from 'src/dtos/asset-response.dto'; -import { SyncEntityType, SyncRequestType } from 'src/enum'; +import { AssetType, SyncEntityType, SyncRequestType } from 'src/enum'; import { Optional, ValidateDate, ValidateUUID } from 'src/validation'; export class AssetFullSyncDto { @@ -56,11 +56,73 @@ export class SyncPartnerDeleteV1 { sharedWithId!: string; } +export class SyncAssetV1 { + id!: string; + ownerId!: string; + thumbhash!: string | null; + checksum!: string; + fileCreatedAt!: Date | null; + fileModifiedAt!: Date | null; + localDateTime!: Date | null; + type!: AssetType; + deletedAt!: Date | null; + isFavorite!: boolean; + isVisible!: boolean; +} + +export class SyncAssetDeleteV1 { + assetId!: string; +} + +export class SyncAssetExifV1 { + assetId!: string; + description!: string | null; + @ApiProperty({ type: 'integer' }) + exifImageWidth!: number | null; + @ApiProperty({ type: 'integer' }) + exifImageHeight!: number | null; + @ApiProperty({ type: 'integer' }) + fileSizeInByte!: number | null; + orientation!: string | null; + dateTimeOriginal!: Date | null; + modifyDate!: Date | null; + timeZone!: string | null; + @ApiProperty({ type: 'integer' }) + latitude!: number | null; + @ApiProperty({ type: 'integer' }) + longitude!: number | null; + projectionType!: string | null; + city!: string | null; + state!: string | null; + country!: string | null; + make!: string | null; + model!: string | null; + lensModel!: string | null; + @ApiProperty({ type: 'integer' }) + fNumber!: number | null; + @ApiProperty({ type: 'integer' }) + focalLength!: number | null; + @ApiProperty({ type: 'integer' }) + iso!: number | null; + exposureTime!: string | null; + profileDescription!: string | null; + @ApiProperty({ type: 'integer' }) + rating!: number | null; + @ApiProperty({ type: 'integer' }) + fps!: number | null; +} + export type SyncItem = { [SyncEntityType.UserV1]: SyncUserV1; [SyncEntityType.UserDeleteV1]: SyncUserDeleteV1; [SyncEntityType.PartnerV1]: SyncPartnerV1; [SyncEntityType.PartnerDeleteV1]: SyncPartnerDeleteV1; + [SyncEntityType.AssetV1]: SyncAssetV1; + [SyncEntityType.AssetDeleteV1]: SyncAssetDeleteV1; + [SyncEntityType.AssetExifV1]: SyncAssetExifV1; + [SyncEntityType.PartnerAssetV1]: SyncAssetV1; + [SyncEntityType.PartnerAssetDeleteV1]: SyncAssetDeleteV1; + [SyncEntityType.PartnerAssetExifV1]: SyncAssetExifV1; }; const responseDtos = [ @@ -69,6 +131,9 @@ const responseDtos = [ SyncUserDeleteV1, SyncPartnerV1, SyncPartnerDeleteV1, + SyncAssetV1, + SyncAssetDeleteV1, + SyncAssetExifV1, ]; export const extraSyncModels = responseDtos; diff --git a/server/src/dtos/time-bucket.dto.ts b/server/src/dtos/time-bucket.dto.ts index a9dfa49a07..02290c1600 100644 --- a/server/src/dtos/time-bucket.dto.ts +++ b/server/src/dtos/time-bucket.dto.ts @@ -1,8 +1,11 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; +import { ApiProperty, OmitType } from '@nestjs/swagger'; import { AssetOrder } from 'src/enum'; import { TimeBucketSize } from 'src/repositories/asset.repository'; import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; +import { IsEnum, IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator'; +import { Type } from 'class-transformer'; +import { PaginationResult } from 'src/utils/pagination'; +import { hexOrBufferToBase64 } from 'src/dtos/asset-response.dto'; export class TimeBucketDto { @IsNotEmpty() @@ -42,11 +45,47 @@ export class TimeBucketDto { @ApiProperty({ enum: AssetOrder, enumName: 'AssetOrder' }) order?: AssetOrder; } +export class LiteTimeBucketDto extends TimeBucketDto { + @IsString() + timeBucket!: string; +} export class TimeBucketAssetDto extends TimeBucketDto { @IsString() timeBucket!: string; } +export class LiteTimeBucketAssetDto extends LiteTimeBucketDto { + @IsInt() + @Min(1) + @Type(() => Number) + @Optional() + page?: number; +} +export class LiteTimeBucketAssetResponseDto { + @IsString() + id!: string; + + @ApiProperty() + isFavorite!: boolean; + + @ApiProperty() + isArchived!: boolean; + + @ApiProperty() + isTrashed!: boolean; + + @ApiProperty() + thumbhash?: string | null; + + @ApiProperty() + localDateTime!: Date; + + @ApiProperty({ type: 'integer' }) + height!: number; + + @ApiProperty({ type: 'integer' }) + width!: number; +} export class TimeBucketResponseDto { @ApiProperty({ type: 'string' }) @@ -55,3 +94,20 @@ export class TimeBucketResponseDto { @ApiProperty({ type: 'integer' }) count!: number; } +export interface LiteTimeBucketDto { + id: string; + isFavorite: boolean; + isArchived: boolean; + thumbhash: string; + localDateTime: Date; + height: number; + width: number; +} +export class LiteTimeBucketResponseDto { + @ApiProperty({ type: [LiteTimeBucketAssetResponseDto] }) + liteAssets!: LiteTimeBucketAssetResponseDto[]; + + @ApiProperty() + hasNextPage!: boolean +} + diff --git a/server/src/dtos/user.dto.ts b/server/src/dtos/user.dto.ts index 03895aa880..5b7784aa3d 100644 --- a/server/src/dtos/user.dto.ts +++ b/server/src/dtos/user.dto.ts @@ -1,6 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsBoolean, IsEmail, IsNotEmpty, IsNumber, IsPositive, IsString } from 'class-validator'; +import { User } from 'src/database'; import { UserMetadataEntity } from 'src/entities/user-metadata.entity'; import { UserEntity } from 'src/entities/user.entity'; import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum'; @@ -52,6 +53,17 @@ export const mapUser = (entity: UserEntity): UserResponseDto => { }; }; +export const mapDatabaseUser = (user: User): UserResponseDto => { + return { + id: user.id, + email: user.email, + name: user.name, + profileImagePath: user.profileImagePath, + avatarColor: getPreferences(user.email, []).avatar.color, + profileChangedAt: user.profileChangedAt, + }; +}; + export class UserAdminSearchDto { @ValidateBoolean({ optional: true }) withDeleted?: boolean; diff --git a/server/src/entities/asset-audit.entity.ts b/server/src/entities/asset-audit.entity.ts new file mode 100644 index 0000000000..0172d15ce6 --- /dev/null +++ b/server/src/entities/asset-audit.entity.ts @@ -0,0 +1,19 @@ +import { Column, CreateDateColumn, Entity, Index, PrimaryColumn } from 'typeorm'; + +@Entity('assets_audit') +export class AssetAuditEntity { + @PrimaryColumn({ type: 'uuid', nullable: false, default: () => 'immich_uuid_v7()' }) + id!: string; + + @Index('IDX_assets_audit_asset_id') + @Column({ type: 'uuid' }) + assetId!: string; + + @Index('IDX_assets_audit_owner_id') + @Column({ type: 'uuid' }) + ownerId!: string; + + @Index('IDX_assets_audit_deleted_at') + @CreateDateColumn({ type: 'timestamptz', default: () => 'clock_timestamp()' }) + deletedAt!: Date; +} diff --git a/server/src/entities/asset.entity.ts b/server/src/entities/asset.entity.ts index b2589e1231..2d46a97c6f 100644 --- a/server/src/entities/asset.entity.ts +++ b/server/src/entities/asset.entity.ts @@ -1,4 +1,4 @@ -import { DeduplicateJoinsPlugin, ExpressionBuilder, Kysely, SelectQueryBuilder, sql } from 'kysely'; +import { DeduplicateJoinsPlugin, Expression, expressionBuilder, ExpressionBuilder, Kysely, SelectQueryBuilder, sql } from 'kysely'; import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres'; import { DB } from 'src/db'; import { AlbumEntity } from 'src/entities/album.entity'; @@ -31,6 +31,7 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; +import { jsonBuildObject } from 'kysely/helpers/postgres' export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_checksum'; @@ -191,7 +192,7 @@ export type AssetEntityPlaceholder = AssetEntity & { }; export function withExif(qb: SelectQueryBuilder) { - return qb.leftJoin('exif', 'assets.id', 'exif.assetId').select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo')); + return qb.leftJoin('exif', 'assets.id', 'exif.assetId').select(eb => eb.fn.toJson(eb.table('exif')).as('exifInfo')); } export function withExifInner(qb: SelectQueryBuilder) { @@ -253,19 +254,17 @@ export function withFacesAndPeople(eb: ExpressionBuilder, withDele .as('faces'); } -export function hasPeople(qb: SelectQueryBuilder, personIds: string[]) { - return qb.innerJoin( - (eb) => - eb - .selectFrom('asset_faces') - .select('assetId') - .where('personId', '=', anyUuid(personIds!)) - .where('deletedAt', 'is', null) - .groupBy('assetId') - .having((eb) => eb.fn.count('personId').distinct(), '=', personIds.length) - .as('has_people'), - (join) => join.onRef('has_people.assetId', '=', 'assets.id'), - ); +export function hasPeople(personIds: string[]) { + const eb = expressionBuilder(); + return eb + .selectFrom('asset_faces') + .select('assetId') + .where('personId', '=', anyUuid(personIds!)) + .where('deletedAt', 'is', null) + .groupBy('assetId') + .having((eb) => eb.fn.count('personId').distinct(), '=', personIds.length) + .as('has_people'); + } export function hasTags(qb: SelectQueryBuilder, tagIds: string[]) { @@ -293,32 +292,55 @@ export function withLibrary(eb: ExpressionBuilder) { ); } -export function withAlbums(qb: SelectQueryBuilder, { albumId }: { albumId?: string }) { - return qb - .select((eb) => - jsonArrayFrom( - eb - .selectFrom('albums') - .selectAll() - .innerJoin('albums_assets_assets', (join) => - join - .onRef('albums.id', '=', 'albums_assets_assets.albumsId') - .onRef('assets.id', '=', 'albums_assets_assets.assetsId'), - ) - .whereRef('albums.id', '=', 'albums_assets_assets.albumsId') - .$if(!!albumId, (qb) => qb.where('albums.id', '=', asUuid(albumId!))), - ).as('albums'), - ) - .$if(!!albumId, (qb) => - qb.where((eb) => - eb.exists((eb) => - eb - .selectFrom('albums_assets_assets') - .whereRef('albums_assets_assets.assetsId', '=', 'assets.id') - .where('albums_assets_assets.albumsId', '=', asUuid(albumId!)), - ), - ), - ); +export function withAlbums(assetId: Expression, albumId?: string) { + const eb = expressionBuilder(); + return jsonArrayFrom( + eb + .selectFrom('albums') + .selectAll() + .innerJoin('albums_assets_assets', (join) => + join + .onRef('albums.id', '=', 'albums_assets_assets.albumsId') + .onRef(assetId, '=', 'albums_assets_assets.assetsId'), + ) + .whereRef('albums.id', '=', 'albums_assets_assets.albumsId') + .$if(!!albumId, (qb) => qb.where('albums.id', '=', asUuid(albumId!))), + ).as('albums'); + + // return qb + // .select((eb) => + // jsonArrayFrom( + // eb + // .selectFrom('albums') + + // .innerJoin('albums_assets_assets', (join) => + // join + // .onRef('albums.id', '=', 'albums_assets_assets.albumsId') + // .onRef('assets.id', '=', 'albums_assets_assets.assetsId'), + // ) + // .whereRef('albums.id', '=', 'albums_assets_assets.albumsId') + // .$if(!!albumId, (qb) => qb.where('albums.id', '=', asUuid(albumId!))), + // ).as('albums'), + // ) + // .$if(!!albumId, (qb) => + // qb.where((eb) => + // eb.exists((eb) => + // eb + // .selectFrom('albums_assets_assets') + // .whereRef('albums_assets_assets.assetsId', '=', 'assets.id') + // .where('albums_assets_assets.albumsId', '=', asUuid(albumId!)), + // ), + // ), + // ); +} +export function withAlbumsAssets(albumId: string, assetId: Expression) { + const eb = expressionBuilder(); + return eb.exists((eb) => + eb + .selectFrom('albums_assets_assets') + .whereRef('albums_assets_assets.assetsId', '=', assetId) + .where('albums_assets_assets.albumsId', '=', asUuid(albumId!)), + ); } export function withTags(eb: ExpressionBuilder) { @@ -335,15 +357,14 @@ export function truncatedDate(size: TimeBucketSize) { return sql`date_trunc(${size}, "localDateTime" at time zone 'UTC') at time zone 'UTC'`; } -export function withTagId(qb: SelectQueryBuilder, tagId: string) { - return qb.where((eb) => - eb.exists( - eb - .selectFrom('tags_closure') - .innerJoin('tag_asset', 'tag_asset.tagsId', 'tags_closure.id_descendant') - .whereRef('tag_asset.assetsId', '=', 'assets.id') - .where('tags_closure.id_ancestor', '=', tagId), - ), +export function withTagId(tagId: string, assetId: Expression) { + const eb = expressionBuilder(); + return eb.exists( + eb + .selectFrom('tags_closure') + .innerJoin('tag_asset', 'tag_asset.tagsId', 'tags_closure.id_descendant') + .whereRef('tag_asset.assetsId', '=', assetId) + .where('tags_closure.id_ancestor', '=', tagId), ); } @@ -358,7 +379,7 @@ export function searchAssetBuilder(kysely: Kysely, options: AssetSearchBuild .selectFrom('assets') .selectAll('assets') .$if(!!options.tagIds && options.tagIds.length > 0, (qb) => hasTags(qb, options.tagIds!)) - .$if(!!options.personIds && options.personIds.length > 0, (qb) => hasPeople(qb, options.personIds!)) + .$if(!!options.personIds && options.personIds.length > 0, (qb) => qb.innerJoin(() => hasPeople(options.personIds!), (join) => join.onRef('has_people.assetId', '=', 'assets.id'))) .$if(!!options.createdBefore, (qb) => qb.where('assets.createdAt', '<=', options.createdBefore!)) .$if(!!options.createdAfter, (qb) => qb.where('assets.createdAt', '>=', options.createdAfter!)) .$if(!!options.updatedBefore, (qb) => qb.where('assets.updatedAt', '<=', options.updatedBefore!)) diff --git a/server/src/entities/exif.entity.ts b/server/src/entities/exif.entity.ts index c9c29d732a..5b402109a5 100644 --- a/server/src/entities/exif.entity.ts +++ b/server/src/entities/exif.entity.ts @@ -1,5 +1,5 @@ import { AssetEntity } from 'src/entities/asset.entity'; -import { Index, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm'; +import { Index, JoinColumn, OneToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm'; import { Column } from 'typeorm/decorator/columns/Column.js'; import { Entity } from 'typeorm/decorator/entity/Entity.js'; @@ -12,6 +12,13 @@ export class ExifEntity { @PrimaryColumn() assetId!: string; + @UpdateDateColumn({ type: 'timestamptz', default: () => 'clock_timestamp()' }) + updatedAt?: Date; + + @Index('IDX_asset_exif_update_id') + @Column({ type: 'uuid', nullable: false, default: () => 'immich_uuid_v7()' }) + updateId?: string; + /* General info */ @Column({ type: 'text', default: '' }) description!: string; // or caption diff --git a/server/src/entities/index.ts b/server/src/entities/index.ts deleted file mode 100644 index a1df269c09..0000000000 --- a/server/src/entities/index.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { ActivityEntity } from 'src/entities/activity.entity'; -import { AlbumUserEntity } from 'src/entities/album-user.entity'; -import { AlbumEntity } from 'src/entities/album.entity'; -import { APIKeyEntity } from 'src/entities/api-key.entity'; -import { AssetFaceEntity } from 'src/entities/asset-face.entity'; -import { AssetFileEntity } from 'src/entities/asset-files.entity'; -import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity'; -import { AssetEntity } from 'src/entities/asset.entity'; -import { AuditEntity } from 'src/entities/audit.entity'; -import { ExifEntity } from 'src/entities/exif.entity'; -import { FaceSearchEntity } from 'src/entities/face-search.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 { NaturalEarthCountriesEntity } from 'src/entities/natural-earth-countries.entity'; -import { PartnerEntity } from 'src/entities/partner.entity'; -import { PersonEntity } from 'src/entities/person.entity'; -import { SessionEntity } from 'src/entities/session.entity'; -import { SharedLinkEntity } from 'src/entities/shared-link.entity'; -import { SmartSearchEntity } from 'src/entities/smart-search.entity'; -import { StackEntity } from 'src/entities/stack.entity'; -import { SessionSyncCheckpointEntity } from 'src/entities/sync-checkpoint.entity'; -import { SystemMetadataEntity } from 'src/entities/system-metadata.entity'; -import { TagEntity } from 'src/entities/tag.entity'; -import { UserAuditEntity } from 'src/entities/user-audit.entity'; -import { UserMetadataEntity } from 'src/entities/user-metadata.entity'; -import { UserEntity } from 'src/entities/user.entity'; -import { VersionHistoryEntity } from 'src/entities/version-history.entity'; - -export const entities = [ - ActivityEntity, - AlbumEntity, - AlbumUserEntity, - APIKeyEntity, - AssetEntity, - AssetFaceEntity, - AssetFileEntity, - AssetJobStatusEntity, - AuditEntity, - ExifEntity, - FaceSearchEntity, - GeodataPlacesEntity, - NaturalEarthCountriesEntity, - MemoryEntity, - MoveEntity, - PartnerEntity, - PersonEntity, - SessionSyncCheckpointEntity, - SharedLinkEntity, - SmartSearchEntity, - StackEntity, - SystemMetadataEntity, - TagEntity, - UserEntity, - UserAuditEntity, - UserMetadataEntity, - SessionEntity, - LibraryEntity, - VersionHistoryEntity, -]; diff --git a/server/src/entities/library.entity.ts b/server/src/entities/library.entity.ts index a594fd83ad..0471661fca 100644 --- a/server/src/entities/library.entity.ts +++ b/server/src/entities/library.entity.ts @@ -26,7 +26,7 @@ export class LibraryEntity { assets!: AssetEntity[]; @ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) - owner!: UserEntity; + owner?: UserEntity; @Column() ownerId!: string; diff --git a/server/src/entities/move.entity.ts b/server/src/entities/move.entity.ts index 5cdef5d22e..7a998eaebe 100644 --- a/server/src/entities/move.entity.ts +++ b/server/src/entities/move.entity.ts @@ -10,7 +10,7 @@ export class MoveEntity { @PrimaryGeneratedColumn('uuid') id!: string; - @Column({ type: 'varchar' }) + @Column({ type: 'uuid' }) entityId!: string; @Column({ type: 'varchar' }) diff --git a/server/src/entities/partner.entity.ts b/server/src/entities/partner.entity.ts index 877330a8e7..5326757736 100644 --- a/server/src/entities/partner.entity.ts +++ b/server/src/entities/partner.entity.ts @@ -10,6 +10,7 @@ import { UpdateDateColumn, } from 'typeorm'; +/** @deprecated delete after coming up with a migration workflow for kysely */ @Entity('partners') export class PartnerEntity { @PrimaryColumn('uuid') diff --git a/server/src/entities/person.entity.ts b/server/src/entities/person.entity.ts index 5ca74c12d2..5efa602cc8 100644 --- a/server/src/entities/person.entity.ts +++ b/server/src/entities/person.entity.ts @@ -38,7 +38,7 @@ export class PersonEntity { name!: string; @Column({ type: 'date', nullable: true }) - birthDate!: string | null; + birthDate!: Date | string | null; @Column({ default: '' }) thumbnailPath!: string; diff --git a/server/src/enum.ts b/server/src/enum.ts index b9a56144fb..6ebd1906f7 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -237,6 +237,7 @@ export enum ManualJobName { USER_CLEANUP = 'user-cleanup', MEMORY_CLEANUP = 'memory-cleanup', MEMORY_CREATE = 'memory-create', + BACKUP_DATABASE = 'backup-database', } export enum AssetPathType { @@ -469,8 +470,9 @@ export enum JobName { // library management LIBRARY_QUEUE_SYNC_FILES = 'library-queue-sync-files', LIBRARY_QUEUE_SYNC_ASSETS = 'library-queue-sync-assets', - LIBRARY_SYNC_FILE = 'library-sync-file', - LIBRARY_SYNC_ASSET = 'library-sync-asset', + LIBRARY_SYNC_FILES = 'library-sync-files', + LIBRARY_SYNC_ASSETS = 'library-sync-assets', + LIBRARY_ASSET_REMOVAL = 'handle-library-file-deletion', LIBRARY_DELETE = 'library-delete', LIBRARY_QUEUE_SCAN_ALL = 'library-queue-scan-all', LIBRARY_QUEUE_CLEANUP = 'library-queue-cleanup', @@ -548,11 +550,24 @@ export enum DatabaseLock { export enum SyncRequestType { UsersV1 = 'UsersV1', PartnersV1 = 'PartnersV1', + AssetsV1 = 'AssetsV1', + AssetExifsV1 = 'AssetExifsV1', + PartnerAssetsV1 = 'PartnerAssetsV1', + PartnerAssetExifsV1 = 'PartnerAssetExifsV1', } export enum SyncEntityType { UserV1 = 'UserV1', UserDeleteV1 = 'UserDeleteV1', + PartnerV1 = 'PartnerV1', PartnerDeleteV1 = 'PartnerDeleteV1', + + AssetV1 = 'AssetV1', + AssetDeleteV1 = 'AssetDeleteV1', + AssetExifV1 = 'AssetExifV1', + + PartnerAssetV1 = 'PartnerAssetV1', + PartnerAssetDeleteV1 = 'PartnerAssetDeleteV1', + PartnerAssetExifV1 = 'PartnerAssetExifV1', } diff --git a/server/src/migrations/1645130759468-CreateUserTable.ts b/server/src/migrations/1645130759468-CreateUserTable.ts index 6e3d427dd2..1aedfb67d4 100644 --- a/server/src/migrations/1645130759468-CreateUserTable.ts +++ b/server/src/migrations/1645130759468-CreateUserTable.ts @@ -2,6 +2,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; export class CreateUserTable1645130759468 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`); await queryRunner.query(` create table if not exists users ( diff --git a/server/src/migrations/1741179334403-MoveHistoryUuidEntityId.ts b/server/src/migrations/1741179334403-MoveHistoryUuidEntityId.ts new file mode 100644 index 0000000000..449272341c --- /dev/null +++ b/server/src/migrations/1741179334403-MoveHistoryUuidEntityId.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MoveHistoryUuidEntityId1741179334403 implements MigrationInterface { + name = 'MoveHistoryUuidEntityId1741179334403'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "move_history" ALTER COLUMN "entityId" TYPE uuid USING "entityId"::uuid;`); + await queryRunner.query(`delete from "move_history" + where + "move_history"."entityId" not in ( + select + "id" + from + "assets" + where + "assets"."id" = "move_history"."entityId" + ) + and "move_history"."pathType" = 'original' + `) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "move_history" ALTER COLUMN "entityId" TYPE character varying`); + } +} + diff --git a/server/src/migrations/1741191762113-AssetAuditTable.ts b/server/src/migrations/1741191762113-AssetAuditTable.ts new file mode 100644 index 0000000000..c02408c384 --- /dev/null +++ b/server/src/migrations/1741191762113-AssetAuditTable.ts @@ -0,0 +1,37 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AssetAuditTable1741191762113 implements MigrationInterface { + name = 'AssetAuditTable1741191762113' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "assets_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "assetId" uuid NOT NULL, "ownerId" uuid NOT NULL, "deletedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT clock_timestamp(), CONSTRAINT "PK_99bd5c015f81a641927a32b4212" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_assets_audit_asset_id" ON "assets_audit" ("assetId") `); + await queryRunner.query(`CREATE INDEX "IDX_assets_audit_owner_id" ON "assets_audit" ("ownerId") `); + await queryRunner.query(`CREATE INDEX "IDX_assets_audit_deleted_at" ON "assets_audit" ("deletedAt") `); + await queryRunner.query(`CREATE OR REPLACE FUNCTION assets_delete_audit() RETURNS TRIGGER AS + $$ + BEGIN + INSERT INTO assets_audit ("assetId", "ownerId") + SELECT "id", "ownerId" + FROM OLD; + RETURN NULL; + END; + $$ LANGUAGE plpgsql` + ); + await queryRunner.query(`CREATE OR REPLACE TRIGGER assets_delete_audit + AFTER DELETE ON assets + REFERENCING OLD TABLE AS OLD + FOR EACH STATEMENT + EXECUTE FUNCTION assets_delete_audit(); + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TRIGGER assets_delete_audit`); + await queryRunner.query(`DROP FUNCTION assets_delete_audit`); + await queryRunner.query(`DROP INDEX "IDX_assets_audit_deleted_at"`); + await queryRunner.query(`DROP INDEX "IDX_assets_audit_owner_id"`); + await queryRunner.query(`DROP INDEX "IDX_assets_audit_asset_id"`); + await queryRunner.query(`DROP TABLE "assets_audit"`); + } +} diff --git a/server/src/migrations/1741280328985-FixAssetAndUserCascadeConditions.ts b/server/src/migrations/1741280328985-FixAssetAndUserCascadeConditions.ts new file mode 100644 index 0000000000..20215c1b59 --- /dev/null +++ b/server/src/migrations/1741280328985-FixAssetAndUserCascadeConditions.ts @@ -0,0 +1,50 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FixAssetAndUserCascadeConditions1741280328985 implements MigrationInterface { + name = 'FixAssetAndUserCascadeConditions1741280328985'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE OR REPLACE TRIGGER assets_delete_audit + AFTER DELETE ON assets + REFERENCING OLD TABLE AS OLD + FOR EACH STATEMENT + WHEN (pg_trigger_depth() = 0) + EXECUTE FUNCTION assets_delete_audit();`); + await queryRunner.query(` + CREATE OR REPLACE TRIGGER users_delete_audit + AFTER DELETE ON users + REFERENCING OLD TABLE AS OLD + FOR EACH STATEMENT + WHEN (pg_trigger_depth() = 0) + EXECUTE FUNCTION users_delete_audit();`); + await queryRunner.query(` + CREATE OR REPLACE TRIGGER partners_delete_audit + AFTER DELETE ON partners + REFERENCING OLD TABLE AS OLD + FOR EACH STATEMENT + WHEN (pg_trigger_depth() = 0) + EXECUTE FUNCTION partners_delete_audit();`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE OR REPLACE TRIGGER assets_delete_audit + AFTER DELETE ON assets + REFERENCING OLD TABLE AS OLD + FOR EACH STATEMENT + EXECUTE FUNCTION assets_delete_audit();`); + await queryRunner.query(` + CREATE OR REPLACE TRIGGER users_delete_audit + AFTER DELETE ON users + REFERENCING OLD TABLE AS OLD + FOR EACH STATEMENT + EXECUTE FUNCTION users_delete_audit();`); + await queryRunner.query(` + CREATE OR REPLACE TRIGGER partners_delete_audit + AFTER DELETE ON partners + REFERENCING OLD TABLE AS OLD + FOR EACH STATEMENT + EXECUTE FUNCTION partners_delete_audit();`); + } +} diff --git a/server/src/migrations/1741281344519-AddExifUpdateId.ts b/server/src/migrations/1741281344519-AddExifUpdateId.ts new file mode 100644 index 0000000000..eb32836a1d --- /dev/null +++ b/server/src/migrations/1741281344519-AddExifUpdateId.ts @@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddExifUpdateId1741281344519 implements MigrationInterface { + name = 'AddExifUpdateId1741281344519'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "exif" ADD "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT clock_timestamp()`, + ); + await queryRunner.query(`ALTER TABLE "exif" ADD "updateId" uuid NOT NULL DEFAULT immich_uuid_v7()`); + await queryRunner.query(`CREATE INDEX "IDX_asset_exif_update_id" ON "exif" ("updateId") `); + await queryRunner.query(` + create trigger asset_exif_updated_at + before update on exif + for each row execute procedure updated_at() + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "public"."IDX_asset_exif_update_id"`); + await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "updateId"`); + await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "updatedAt"`); + await queryRunner.query(`DROP TRIGGER asset_exif_updated_at on exif`); + } +} diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index c0b778bb50..1fd8f55f31 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -420,8 +420,8 @@ from ) as "stacked_assets" on "asset_stack"."id" is not null where "assets"."ownerId" = $1::uuid - and "isVisible" = $2 - and "updatedAt" <= $3 + and "assets"."isVisible" = $2 + and "assets"."updatedAt" <= $3 and "assets"."id" > $4 order by "assets"."id" @@ -450,7 +450,7 @@ from ) as "stacked_assets" on "asset_stack"."id" is not null where "assets"."ownerId" = any ($1::uuid[]) - and "isVisible" = $2 - and "updatedAt" > $3 + and "assets"."isVisible" = $2 + and "assets"."updatedAt" > $3 limit $4 diff --git a/server/src/queries/database.repository.sql b/server/src/queries/database.repository.sql new file mode 100644 index 0000000000..8c87a7470f --- /dev/null +++ b/server/src/queries/database.repository.sql @@ -0,0 +1,21 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- DatabaseRepository.getExtensionVersion +SELECT + default_version as "availableVersion", + installed_version as "installedVersion" +FROM + pg_available_extensions +WHERE + name = $1 + +-- DatabaseRepository.getPostgresVersion +SHOW server_version + +-- DatabaseRepository.shouldReindex +SELECT + idx_status +FROM + pg_vector_index_stat +WHERE + indexname = $1 diff --git a/server/src/queries/library.repository.sql b/server/src/queries/library.repository.sql index b0b20fd8a2..43500a8748 100644 --- a/server/src/queries/library.repository.sql +++ b/server/src/queries/library.repository.sql @@ -2,34 +2,7 @@ -- LibraryRepository.get select - "libraries".*, - ( - select - to_json(obj) - from - ( - select - "users"."id", - "users"."email", - "users"."createdAt", - "users"."profileImagePath", - "users"."isAdmin", - "users"."shouldChangePassword", - "users"."deletedAt", - "users"."oauthId", - "users"."updatedAt", - "users"."storageLabel", - "users"."name", - "users"."quotaSizeInBytes", - "users"."quotaUsageInBytes", - "users"."status", - "users"."profileChangedAt" - from - "users" - where - "users"."id" = "libraries"."ownerId" - ) as obj - ) as "owner" + "libraries".* from "libraries" where @@ -38,34 +11,7 @@ where -- LibraryRepository.getAll select - "libraries".*, - ( - select - to_json(obj) - from - ( - select - "users"."id", - "users"."email", - "users"."createdAt", - "users"."profileImagePath", - "users"."isAdmin", - "users"."shouldChangePassword", - "users"."deletedAt", - "users"."oauthId", - "users"."updatedAt", - "users"."storageLabel", - "users"."name", - "users"."quotaSizeInBytes", - "users"."quotaUsageInBytes", - "users"."status", - "users"."profileChangedAt" - from - "users" - where - "users"."id" = "libraries"."ownerId" - ) as obj - ) as "owner" + "libraries".* from "libraries" where @@ -75,34 +21,7 @@ order by -- LibraryRepository.getAllDeleted select - "libraries".*, - ( - select - to_json(obj) - from - ( - select - "users"."id", - "users"."email", - "users"."createdAt", - "users"."profileImagePath", - "users"."isAdmin", - "users"."shouldChangePassword", - "users"."deletedAt", - "users"."oauthId", - "users"."updatedAt", - "users"."storageLabel", - "users"."name", - "users"."quotaSizeInBytes", - "users"."quotaUsageInBytes", - "users"."status", - "users"."profileChangedAt" - from - "users" - where - "users"."id" = "libraries"."ownerId" - ) as obj - ) as "owner" + "libraries".* from "libraries" where diff --git a/server/src/queries/move.repository.sql b/server/src/queries/move.repository.sql index e51f2829df..a65c7a8b85 100644 --- a/server/src/queries/move.repository.sql +++ b/server/src/queries/move.repository.sql @@ -15,3 +15,22 @@ where "id" = $1 returning * + +-- MoveRepository.cleanMoveHistory +delete from "move_history" +where + "move_history"."entityId" not in ( + select + "id" + from + "assets" + where + "assets"."id" = "move_history"."entityId" + ) + and "move_history"."pathType" = 'original' + +-- MoveRepository.cleanMoveHistorySingle +delete from "move_history" +where + "move_history"."pathType" = 'original' + and "entityId" = $1 diff --git a/server/src/repositories/api-key.repository.ts b/server/src/repositories/api-key.repository.ts index 4ed463365b..0085db2337 100644 --- a/server/src/repositories/api-key.repository.ts +++ b/server/src/repositories/api-key.repository.ts @@ -12,7 +12,7 @@ export class ApiKeyRepository { constructor(@InjectKysely() private db: Kysely) {} create(dto: Insertable) { - return this.db.insertInto('api_keys').values(dto).returningAll().executeTakeFirstOrThrow(); + return this.db.insertInto('api_keys').values(dto).returning(columns.apiKey).executeTakeFirstOrThrow(); } async update(userId: string, id: string, dto: Updateable) { @@ -21,7 +21,7 @@ export class ApiKeyRepository { .set(dto) .where('api_keys.userId', '=', userId) .where('id', '=', asUuid(id)) - .returningAll() + .returning(columns.apiKey) .executeTakeFirstOrThrow(); } diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index 91597ed720..6d4f85f0e9 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, Updateable, sql } from 'kysely'; +import { Insertable, Kysely, UpdateResult, Updateable, sql } from 'kysely'; import { isEmpty, isUndefined, omitBy } from 'lodash'; import { InjectKysely } from 'nestjs-kysely'; -import { ASSET_FILE_CONFLICT_KEYS, EXIF_CONFLICT_KEYS, JOB_STATUS_CONFLICT_KEYS } from 'src/constants'; import { AssetFiles, AssetJobStatus, Assets, DB, Exif } from 'src/db'; import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; +import { LiteTimeBucketAssetResponseDto, LiteTimeBucketDto, LiteTimeBucketResponseDto } from 'src/dtos/time-bucket.dto'; import { AssetEntity, AssetEntityPlaceholder, @@ -12,6 +12,7 @@ import { searchAssetBuilder, truncatedDate, withAlbums, + withAlbumsAssets, withExif, withFaces, withFacesAndPeople, @@ -24,8 +25,11 @@ import { } from 'src/entities/asset.entity'; import { AssetFileType, AssetOrder, AssetStatus, AssetType } from 'src/enum'; import { AssetSearchOptions, SearchExploreItem, SearchExploreItemSet } from 'src/repositories/search.repository'; -import { anyUuid, asUuid, mapUpsertColumns } from 'src/utils/database'; -import { Paginated, PaginationOptions, paginationHelper } from 'src/utils/pagination'; +import { StorageAsset } from 'src/types'; +import { isFlipped } from 'src/utils/asset.util'; +import { anyUuid, asUuid, removeUndefinedKeys, unnest } from 'src/utils/database'; +import { globToSqlPattern } from 'src/utils/misc'; +import { Paginated, PaginationOptions, PaginationResult, paginationHelper } from 'src/utils/pagination'; export type AssetStats = Record; @@ -151,9 +155,11 @@ export interface DayOfYearAssets { assets: AssetEntity[]; } + + @Injectable() export class AssetRepository { - constructor(@InjectKysely() private db: Kysely) {} + constructor(@InjectKysely() private db: Kysely) { } async upsertExif(exif: Insertable): Promise { const value = { ...exif, assetId: asUuid(exif.assetId) }; @@ -161,7 +167,41 @@ export class AssetRepository { .insertInto('exif') .values(value) .onConflict((oc) => - oc.columns(EXIF_CONFLICT_KEYS).doUpdateSet(() => mapUpsertColumns('exif', value, EXIF_CONFLICT_KEYS)), + oc.column('assetId').doUpdateSet((eb) => + removeUndefinedKeys( + { + description: eb.ref('excluded.description'), + exifImageWidth: eb.ref('excluded.exifImageWidth'), + exifImageHeight: eb.ref('excluded.exifImageHeight'), + fileSizeInByte: eb.ref('excluded.fileSizeInByte'), + orientation: eb.ref('excluded.orientation'), + dateTimeOriginal: eb.ref('excluded.dateTimeOriginal'), + modifyDate: eb.ref('excluded.modifyDate'), + timeZone: eb.ref('excluded.timeZone'), + latitude: eb.ref('excluded.latitude'), + longitude: eb.ref('excluded.longitude'), + projectionType: eb.ref('excluded.projectionType'), + city: eb.ref('excluded.city'), + livePhotoCID: eb.ref('excluded.livePhotoCID'), + autoStackId: eb.ref('excluded.autoStackId'), + state: eb.ref('excluded.state'), + country: eb.ref('excluded.country'), + make: eb.ref('excluded.make'), + model: eb.ref('excluded.model'), + lensModel: eb.ref('excluded.lensModel'), + fNumber: eb.ref('excluded.fNumber'), + focalLength: eb.ref('excluded.focalLength'), + iso: eb.ref('excluded.iso'), + exposureTime: eb.ref('excluded.exposureTime'), + profileDescription: eb.ref('excluded.profileDescription'), + colorspace: eb.ref('excluded.colorspace'), + bitsPerSample: eb.ref('excluded.bitsPerSample'), + rating: eb.ref('excluded.rating'), + fps: eb.ref('excluded.fps'), + }, + value, + ), + ), ) .execute(); } @@ -176,9 +216,18 @@ export class AssetRepository { .insertInto('asset_job_status') .values(values) .onConflict((oc) => - oc - .columns(JOB_STATUS_CONFLICT_KEYS) - .doUpdateSet(() => mapUpsertColumns('asset_job_status', values[0], JOB_STATUS_CONFLICT_KEYS)), + oc.column('assetId').doUpdateSet((eb) => + removeUndefinedKeys( + { + duplicatesDetectedAt: eb.ref('excluded.duplicatesDetectedAt'), + facesRecognizedAt: eb.ref('excluded.facesRecognizedAt'), + metadataExtractedAt: eb.ref('excluded.metadataExtractedAt'), + previewAt: eb.ref('excluded.previewAt'), + thumbnailAt: eb.ref('excluded.thumbnailAt'), + }, + values[0], + ), + ), ) .execute(); } @@ -191,6 +240,10 @@ export class AssetRepository { .executeTakeFirst() as any as Promise; } + createAll(assets: Insertable[]): Promise { + return this.db.insertInto('assets').values(assets).returningAll().execute() as any as Promise; + } + @GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] }) getByDayOfYear(ownerIds: string[], { day, month }: MonthDay) { return this.db @@ -331,8 +384,10 @@ export class AssetRepository { } async getByAlbumId(pagination: PaginationOptions, albumId: string): Paginated { - const items = await withAlbums(this.db.selectFrom('assets'), { albumId }) + const items = await this.db.selectFrom('assets') .selectAll('assets') + .$if(!!albumId, (qb) => qb.select(eb => withAlbums(eb.ref('assets.id'), albumId))) + .$if(!!albumId, (qb) => qb.where(eb => withAlbumsAssets(albumId!, eb.ref('assets.id')))) .where('deletedAt', 'is', null) .orderBy('fileCreatedAt', 'desc') .execute(); @@ -470,6 +525,10 @@ export class AssetRepository { await this.db.updateTable('assets').set(options).where('id', '=', anyUuid(ids)).execute(); } + async updateByLibraryId(libraryId: string, options: Updateable): Promise { + await this.db.updateTable('assets').set(options).where('libraryId', '=', asUuid(libraryId)).execute(); + } + @GenerateSql({ params: [{ targetDuplicateId: DummyValue.UUID, duplicateIds: [DummyValue.UUID], assetIds: [DummyValue.UUID] }], }) @@ -499,7 +558,7 @@ export class AssetRepository { return this.getById(asset.id, { exifInfo: true, faces: { person: true } }) as Promise; } - async remove(asset: AssetEntity): Promise { + async remove(asset: { id: string }): Promise { await this.db.deleteFrom('assets').where('id', '=', asUuid(asset.id)).execute(); } @@ -553,6 +612,46 @@ export class AssetRepository { .executeTakeFirst() as Promise; } + private storageTemplateAssetQuery() { + return this.db + .selectFrom('assets') + .innerJoin('exif', 'assets.id', 'exif.assetId') + .select([ + 'assets.id', + 'assets.ownerId', + 'assets.type', + 'assets.checksum', + 'assets.originalPath', + 'assets.isExternal', + 'assets.sidecarPath', + 'assets.originalFileName', + 'assets.livePhotoVideoId', + 'assets.fileCreatedAt', + 'exif.timeZone', + 'exif.fileSizeInByte', + ]) + .where('assets.deletedAt', 'is', null) + .where('assets.fileCreatedAt', 'is not', null); + } + + getStorageTemplateAsset(id: string): Promise { + return this.storageTemplateAssetQuery().where('assets.id', '=', id).executeTakeFirst() as Promise< + StorageAsset | undefined + >; + } + + streamStorageTemplateAssets() { + return this.storageTemplateAssetQuery().stream() as AsyncIterableIterator; + } + + streamDeletedAssets(trashedBefore: Date) { + return this.db + .selectFrom('assets') + .select(['id', 'isOffline']) + .where('assets.deletedAt', '<=', trashedBefore) + .stream(); + } + @GenerateSql( ...Object.values(WithProperty).map((property) => ({ name: property, @@ -689,7 +788,7 @@ export class AssetRepository { .innerJoin('albums_assets_assets', 'assets.id', 'albums_assets_assets.assetsId') .where('albums_assets_assets.albumsId', '=', asUuid(options.albumId!)), ) - .$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!])) + .$if(!!options.personId, (qb) => qb.innerJoin(() => hasPeople([options.personId!]), (join) => join.onRef('has_people.assetId', '=', 'assets.id'))) .$if(!!options.withStacked, (qb) => qb .leftJoin('asset_stack', (join) => @@ -706,7 +805,7 @@ export class AssetRepository { .$if(options.isDuplicate !== undefined, (qb) => qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null), ) - .$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)), + .$if(!!options.tagId, (qb) => qb.where((eb) => withTagId(options.tagId!, eb.ref('assets.id')))), ) .selectFrom('assets') .select('timeBucket') @@ -724,12 +823,16 @@ export class AssetRepository { @GenerateSql({ params: [DummyValue.TIME_BUCKET, { size: TimeBucketSize.MONTH, withStacked: true }] }) async getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise { - return this.db + console.dir(options); + const a = this.db .selectFrom('assets') + // .select(['assets.id', 'isFavorite', 'isArchived', 'thumbhash', 'localDateTime']) .selectAll('assets') + .select('assets.id as id') .$call(withExif) - .$if(!!options.albumId, (qb) => withAlbums(qb, { albumId: options.albumId })) - .$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!])) + .$if(!!options.albumId, (qb) => qb.select(eb => withAlbums(eb.ref('assets.id'), options.albumId))) + .$if(!!options.albumId, (qb) => qb.where(eb => withAlbumsAssets(options.albumId!, eb.ref('assets.id')))) + .$if(!!options.personId, (qb) => qb.innerJoin(() => hasPeople([options.personId!]), (join) => join.onRef('has_people.assetId', '=', 'assets.id'))) .$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!))) .$if(options.isArchived !== undefined, (qb) => qb.where('assets.isArchived', '=', options.isArchived!)) .$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!)) @@ -759,12 +862,82 @@ export class AssetRepository { qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null), ) .$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED)) - .$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)) + .$if(!!options.tagId, (qb) => qb.where((eb) => withTagId(options.tagId!, eb.ref('assets.id')))) + .where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null) + .where('assets.isVisible', '=', true) + .where(truncatedDate(options.size), '=', timeBucket.replace(/^[+-]/, '')) + .orderBy('assets.localDateTime', options.order ?? 'desc'); + + return a + .execute() as any as Promise; + } + + @GenerateSql({ params: [DummyValue.TIME_BUCKET, { size: TimeBucketSize.MONTH, withStacked: true }] }) + async getLiteTimeBucket(pagination: PaginationOptions, timeBucket: string, options: TimeBucketOptions) { + console.dir(options); + const qb = this.db + .selectFrom('assets') + .select(['assets.id as id', 'isFavorite', 'isArchived', 'thumbhash', 'localDateTime', 'assets.status']) + .leftJoin('exif', 'assets.id', 'exif.assetId') + .select(['exif.exifImageHeight as height', 'exifImageWidth as width', 'exif.orientation']) + .$if(!!options.albumId, (qb) => qb.select(eb => withAlbums(eb.ref('assets.id'), options.albumId))) + .$if(!!options.albumId, (qb) => qb.where(eb => withAlbumsAssets(options.albumId!, eb.ref('assets.id')))) + .$if(!!options.personId, (qb) => qb.innerJoin(() => hasPeople([options.personId!]), (join) => join.onRef('has_people.assetId', '=', 'assets.id'))) + .$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!))) + .$if(options.isArchived !== undefined, (qb) => qb.where('assets.isArchived', '=', options.isArchived!)) + .$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!)) + .$if(!!options.withStacked, (qb) => + qb + .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId') + .where((eb) => + eb.or([eb('asset_stack.primaryAssetId', '=', eb.ref('assets.id')), eb('assets.stackId', 'is', null)]), + ) + .leftJoinLateral( + (eb) => + eb + .selectFrom('assets as stacked') + .selectAll('asset_stack') + .select((eb) => eb.fn.count(eb.table('stacked')).as('assetCount')) + .whereRef('stacked.stackId', '=', 'asset_stack.id') + .where('stacked.deletedAt', 'is', null) + .where('stacked.isArchived', '=', false) + .groupBy('asset_stack.id') + .as('stacked_assets'), + (join) => join.on('asset_stack.id', 'is not', null), + ) + .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')), + ) + .$if(!!options.assetType, (qb) => qb.where('assets.type', '=', options.assetType!)) + .$if(options.isDuplicate !== undefined, (qb) => + qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null), + ) + .$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED)) + .$if(!!options.tagId, (qb) => qb.where((eb) => withTagId(options.tagId!, eb.ref('assets.id')))) .where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null) .where('assets.isVisible', '=', true) .where(truncatedDate(options.size), '=', timeBucket.replace(/^[+-]/, '')) .orderBy('assets.localDateTime', options.order ?? 'desc') - .execute() as any as Promise; + .$if(pagination.skip !== undefined, (qb) => qb.offset(pagination.skip!)) + .$if(pagination.skip !== undefined, (qb) => qb.limit(pagination.take + 1)); + + console.log(pagination, qb.compile()) + + let items = await qb.execute(); + items.forEach(item => { + if (isFlipped(item.orientation)) { + const width = item.width; + const height = item.height; + item.height = width; + item.width = height; + } + }); + if (pagination.skip !== undefined) { + return paginationHelper(items, pagination.take); + } + return { + items, + hasNextPage: false, + } } @GenerateSql({ params: [DummyValue.UUID] }) @@ -876,8 +1049,8 @@ export class AssetRepository { ) .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')) .where('assets.ownerId', '=', asUuid(ownerId)) - .where('isVisible', '=', true) - .where('updatedAt', '<=', updatedUntil) + .where('assets.isVisible', '=', true) + .where('assets.updatedAt', '<=', updatedUntil) .$if(!!lastId, (qb) => qb.where('assets.id', '>', lastId!)) .orderBy('assets.id') .limit(limit) @@ -904,8 +1077,8 @@ export class AssetRepository { ) .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')) .where('assets.ownerId', '=', anyUuid(options.userIds)) - .where('isVisible', '=', true) - .where('updatedAt', '>', options.updatedAfter) + .where('assets.isVisible', '=', true) + .where('assets.updatedAt', '>', options.updatedAfter) .limit(options.limit) .execute() as any as Promise; } @@ -916,9 +1089,9 @@ export class AssetRepository { .insertInto('asset_files') .values(value) .onConflict((oc) => - oc - .columns(ASSET_FILE_CONFLICT_KEYS) - .doUpdateSet(() => mapUpsertColumns('asset_files', value, ASSET_FILE_CONFLICT_KEYS)), + oc.columns(['assetId', 'type']).doUpdateSet((eb) => ({ + path: eb.ref('excluded.path'), + })), ) .execute(); } @@ -933,10 +1106,70 @@ export class AssetRepository { .insertInto('asset_files') .values(values) .onConflict((oc) => - oc - .columns(ASSET_FILE_CONFLICT_KEYS) - .doUpdateSet(() => mapUpsertColumns('asset_files', values[0], ASSET_FILE_CONFLICT_KEYS)), + oc.columns(['assetId', 'type']).doUpdateSet((eb) => ({ + path: eb.ref('excluded.path'), + })), ) .execute(); } + + @GenerateSql({ + params: [{ libraryId: DummyValue.UUID, importPaths: [DummyValue.STRING], exclusionPatterns: [DummyValue.STRING] }], + }) + async detectOfflineExternalAssets( + libraryId: string, + importPaths: string[], + exclusionPatterns: string[], + ): Promise { + const paths = importPaths.map((importPath) => `${importPath}%`); + const exclusions = exclusionPatterns.map((pattern) => globToSqlPattern(pattern)); + + return this.db + .updateTable('assets') + .set({ + isOffline: true, + deletedAt: new Date(), + }) + .where('isOffline', '=', false) + .where('isExternal', '=', true) + .where('libraryId', '=', asUuid(libraryId)) + .where((eb) => + eb.or([eb('originalPath', 'not like', paths.join('|')), eb('originalPath', 'like', exclusions.join('|'))]), + ) + .executeTakeFirstOrThrow(); + } + + @GenerateSql({ + params: [{ libraryId: DummyValue.UUID, paths: [DummyValue.STRING] }], + }) + async filterNewExternalAssetPaths(libraryId: string, paths: string[]): Promise { + const result = await this.db + .selectFrom(unnest(paths).as('path')) + .select('path') + .where((eb) => + eb.not( + eb.exists( + this.db + .selectFrom('assets') + .select('originalPath') + .whereRef('assets.originalPath', '=', eb.ref('path')) + .where('libraryId', '=', asUuid(libraryId)) + .where('isExternal', '=', true), + ), + ), + ) + .execute(); + + return result.map((row) => row.path as string); + } + + async getLibraryAssetCount(libraryId: string): Promise { + const { count } = await this.db + .selectFrom('assets') + .select((eb) => eb.fn.countAll().as('count')) + .where('libraryId', '=', asUuid(libraryId)) + .executeTakeFirstOrThrow(); + + return Number(count); + } } diff --git a/server/src/repositories/database.repository.ts b/server/src/repositories/database.repository.ts index ef97147c61..c4aeb74028 100644 --- a/server/src/repositories/database.repository.ts +++ b/server/src/repositories/database.repository.ts @@ -1,18 +1,17 @@ import { Injectable } from '@nestjs/common'; -import { InjectDataSource } from '@nestjs/typeorm'; import AsyncLock from 'async-lock'; -import { Kysely, sql } from 'kysely'; +import { Kysely, sql, Transaction } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; import semver from 'semver'; import { EXTENSION_NAMES, POSTGRES_VERSION_RANGE, VECTOR_VERSION_RANGE, VECTORS_VERSION_RANGE } from 'src/constants'; import { DB } from 'src/db'; +import { GenerateSql } from 'src/decorators'; import { DatabaseExtension, DatabaseLock, VectorIndex } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { ExtensionVersion, VectorExtension, VectorUpdateResult } from 'src/types'; -import { UPSERT_COLUMNS } from 'src/utils/database'; import { isValidInteger } from 'src/validation'; -import { DataSource, EntityManager, EntityMetadata, QueryRunner } from 'typeorm'; +import { DataSource } from 'typeorm'; @Injectable() export class DatabaseRepository { @@ -21,9 +20,8 @@ export class DatabaseRepository { constructor( @InjectKysely() private db: Kysely, - @InjectDataSource() private dataSource: DataSource, private logger: LoggingRepository, - configRepository: ConfigRepository, + private configRepository: ConfigRepository, ) { this.vectorExtension = configRepository.getEnv().database.vectorExtension; this.logger.setContext(DatabaseRepository.name); @@ -33,43 +31,24 @@ export class DatabaseRepository { await this.db.destroy(); } - init() { - for (const metadata of this.dataSource.entityMetadatas) { - const table = metadata.tableName as keyof DB; - UPSERT_COLUMNS[table] = this.getUpsertColumns(metadata); - } - } - - async reconnect() { - try { - if (this.dataSource.isInitialized) { - await this.dataSource.destroy(); - } - const { isInitialized } = await this.dataSource.initialize(); - return isInitialized; - } catch (error) { - this.logger.error(`Database connection failed: ${error}`); - return false; - } - } - + @GenerateSql({ params: [DatabaseExtension.VECTORS] }) async getExtensionVersion(extension: DatabaseExtension): Promise { - const [res]: ExtensionVersion[] = await this.dataSource.query( - `SELECT default_version as "availableVersion", installed_version as "installedVersion" + const { rows } = await sql` + SELECT default_version as "availableVersion", installed_version as "installedVersion" FROM pg_available_extensions - WHERE name = $1`, - [extension], - ); - return res ?? { availableVersion: null, installedVersion: null }; + WHERE name = ${extension} + `.execute(this.db); + return rows[0] ?? { availableVersion: null, installedVersion: null }; } getExtensionVersionRange(extension: VectorExtension): string { return extension === DatabaseExtension.VECTORS ? VECTORS_VERSION_RANGE : VECTOR_VERSION_RANGE; } + @GenerateSql() async getPostgresVersion(): Promise { - const [{ server_version: version }] = await this.dataSource.query(`SHOW server_version`); - return version; + const { rows } = await sql<{ server_version: string }>`SHOW server_version`.execute(this.db); + return rows[0].server_version; } getPostgresVersionRange(): string { @@ -77,7 +56,7 @@ export class DatabaseRepository { } async createExtension(extension: DatabaseExtension): Promise { - await this.dataSource.query(`CREATE EXTENSION IF NOT EXISTS ${extension}`); + await sql`CREATE EXTENSION IF NOT EXISTS ${sql.raw(extension)}`.execute(this.db); } async updateVectorExtension(extension: VectorExtension, targetVersion?: string): Promise { @@ -93,23 +72,23 @@ export class DatabaseRepository { const isVectors = extension === DatabaseExtension.VECTORS; let restartRequired = false; - await this.dataSource.manager.transaction(async (manager) => { - await this.setSearchPath(manager); + await this.db.transaction().execute(async (tx) => { + await this.setSearchPath(tx); if (isVectors && installedVersion === '0.1.1') { - await this.setExtVersion(manager, DatabaseExtension.VECTORS, '0.1.11'); + await this.setExtVersion(tx, DatabaseExtension.VECTORS, '0.1.11'); } const isSchemaUpgrade = semver.satisfies(installedVersion, '0.1.1 || 0.1.11'); if (isSchemaUpgrade && isVectors) { - await this.updateVectorsSchema(manager); + await this.updateVectorsSchema(tx); } - await manager.query(`ALTER EXTENSION ${extension} UPDATE TO '${targetVersion}'`); + await sql`ALTER EXTENSION ${sql.raw(extension)} UPDATE TO ${sql.lit(targetVersion)}`.execute(tx); const diff = semver.diff(installedVersion, targetVersion); if (isVectors && diff && ['minor', 'major'].includes(diff)) { - await manager.query('SELECT pgvectors_upgrade()'); + await sql`SELECT pgvectors_upgrade()`.execute(tx); restartRequired = true; } else { await this.reindex(VectorIndex.CLIP); @@ -122,7 +101,7 @@ export class DatabaseRepository { async reindex(index: VectorIndex): Promise { try { - await this.dataSource.query(`REINDEX INDEX ${index}`); + await sql`REINDEX INDEX ${sql.raw(index)}`.execute(this.db); } catch (error) { if (this.vectorExtension !== DatabaseExtension.VECTORS) { throw error; @@ -131,29 +110,34 @@ export class DatabaseRepository { const table = await this.getIndexTable(index); const dimSize = await this.getDimSize(table); - await this.dataSource.manager.transaction(async (manager) => { - await this.setSearchPath(manager); - await manager.query(`DROP INDEX IF EXISTS ${index}`); - await manager.query(`ALTER TABLE ${table} ALTER COLUMN embedding SET DATA TYPE real[]`); - await manager.query(`ALTER TABLE ${table} ALTER COLUMN embedding SET DATA TYPE vector(${dimSize})`); - await manager.query(`SET vectors.pgvector_compatibility=on`); - await manager.query(` - CREATE INDEX IF NOT EXISTS ${index} ON ${table} + await this.db.transaction().execute(async (tx) => { + await this.setSearchPath(tx); + await sql`DROP INDEX IF EXISTS ${sql.raw(index)}`.execute(tx); + await sql`ALTER TABLE ${sql.raw(table)} ALTER COLUMN embedding SET DATA TYPE real[]`.execute(tx); + await sql`ALTER TABLE ${sql.raw(table)} ALTER COLUMN embedding SET DATA TYPE vector(${sql.raw(String(dimSize))})`.execute( + tx, + ); + await sql`SET vectors.pgvector_compatibility=on`.execute(tx); + await sql` + CREATE INDEX IF NOT EXISTS ${sql.raw(index)} ON ${sql.raw(table)} USING hnsw (embedding vector_cosine_ops) - WITH (ef_construction = 300, m = 16)`); + WITH (ef_construction = 300, m = 16) + `.execute(tx); }); } } + @GenerateSql({ params: [VectorIndex.CLIP] }) async shouldReindex(name: VectorIndex): Promise { if (this.vectorExtension !== DatabaseExtension.VECTORS) { return false; } try { - const query = `SELECT idx_status FROM pg_vector_index_stat WHERE indexname = $1`; - const res = await this.dataSource.query(query, [name]); - return res[0]?.['idx_status'] === 'UPGRADE'; + const { rows } = await sql<{ + idx_status: string; + }>`SELECT idx_status FROM pg_vector_index_stat WHERE indexname = ${name}`.execute(this.db); + return rows[0]?.idx_status === 'UPGRADE'; } catch (error) { const message: string = (error as any).message; if (message.includes('index is not existing')) { @@ -165,44 +149,45 @@ export class DatabaseRepository { } } - private async setSearchPath(manager: EntityManager): Promise { - await manager.query(`SET search_path TO "$user", public, vectors`); + private async setSearchPath(tx: Transaction): Promise { + await sql`SET search_path TO "$user", public, vectors`.execute(tx); } - private async setExtVersion(manager: EntityManager, extName: DatabaseExtension, version: string): Promise { - const query = `UPDATE pg_catalog.pg_extension SET extversion = $1 WHERE extname = $2`; - await manager.query(query, [version, extName]); + private async setExtVersion(tx: Transaction, extName: DatabaseExtension, version: string): Promise { + await sql`UPDATE pg_catalog.pg_extension SET extversion = ${version} WHERE extname = ${extName}`.execute(tx); } private async getIndexTable(index: VectorIndex): Promise { - const tableQuery = `SELECT relname FROM pg_stat_all_indexes WHERE indexrelname = $1`; - const [res]: { relname: string | null }[] = await this.dataSource.manager.query(tableQuery, [index]); - const table = res?.relname; + const { rows } = await sql<{ + relname: string | null; + }>`SELECT relname FROM pg_stat_all_indexes WHERE indexrelname = ${index}`.execute(this.db); + const table = rows[0]?.relname; if (!table) { throw new Error(`Could not find table for index ${index}`); } return table; } - private async updateVectorsSchema(manager: EntityManager): Promise { + private async updateVectorsSchema(tx: Transaction): Promise { const extension = DatabaseExtension.VECTORS; - await manager.query(`CREATE SCHEMA IF NOT EXISTS ${extension}`); - await manager.query('UPDATE pg_catalog.pg_extension SET extrelocatable = true WHERE extname = $1', [extension]); - await manager.query('ALTER EXTENSION vectors SET SCHEMA vectors'); - await manager.query('UPDATE pg_catalog.pg_extension SET extrelocatable = false WHERE extname = $1', [extension]); + await sql`CREATE SCHEMA IF NOT EXISTS ${extension}`.execute(tx); + await sql`UPDATE pg_catalog.pg_extension SET extrelocatable = true WHERE extname = ${extension}`.execute(tx); + await sql`ALTER EXTENSION vectors SET SCHEMA vectors`.execute(tx); + await sql`UPDATE pg_catalog.pg_extension SET extrelocatable = false WHERE extname = ${extension}`.execute(tx); } private async getDimSize(table: string, column = 'embedding'): Promise { - const res = await this.dataSource.query(` + const { rows } = await sql<{ dimsize: number }>` SELECT atttypmod as dimsize FROM pg_attribute f JOIN pg_class c ON c.oid = f.attrelid WHERE c.relkind = 'r'::char AND f.attnum > 0 - AND c.relname = '${table}' - AND f.attname = '${column}'`); + AND c.relname = ${table} + AND f.attname = '${column}' + `.execute(this.db); - const dimSize = res[0]['dimsize']; + const dimSize = rows[0]?.dimsize; if (!isValidInteger(dimSize, { min: 1, max: 2 ** 16 })) { throw new Error(`Could not retrieve dimension size`); } @@ -210,31 +195,34 @@ export class DatabaseRepository { } async runMigrations(options?: { transaction?: 'all' | 'none' | 'each' }): Promise { - await this.dataSource.runMigrations(options); + const { database } = this.configRepository.getEnv(); + const dataSource = new DataSource(database.config.typeorm); + + this.logger.log('Running migrations, this may take a while'); + + await dataSource.initialize(); + await dataSource.runMigrations(options); + await dataSource.destroy(); } async withLock(lock: DatabaseLock, callback: () => Promise): Promise { let res; await this.asyncLock.acquire(DatabaseLock[lock], async () => { - const queryRunner = this.dataSource.createQueryRunner(); - try { - await this.acquireLock(lock, queryRunner); - res = await callback(); - } finally { + await this.db.connection().execute(async (connection) => { try { - await this.releaseLock(lock, queryRunner); + await this.acquireLock(lock, connection); + res = await callback(); } finally { - await queryRunner.release(); + await this.releaseLock(lock, connection); } - } + }); }); return res as R; } - async tryLock(lock: DatabaseLock): Promise { - const queryRunner = this.dataSource.createQueryRunner(); - return await this.acquireTryLock(lock, queryRunner); + tryLock(lock: DatabaseLock): Promise { + return this.db.connection().execute(async (connection) => this.acquireTryLock(lock, connection)); } isBusy(lock: DatabaseLock): boolean { @@ -245,22 +233,18 @@ export class DatabaseRepository { await this.asyncLock.acquire(DatabaseLock[lock], () => {}); } - private async acquireLock(lock: DatabaseLock, queryRunner: QueryRunner): Promise { - return queryRunner.query('SELECT pg_advisory_lock($1)', [lock]); + private async acquireLock(lock: DatabaseLock, connection: Kysely): Promise { + await sql`SELECT pg_advisory_lock(${lock})`.execute(connection); } - private async acquireTryLock(lock: DatabaseLock, queryRunner: QueryRunner): Promise { - const lockResult = await queryRunner.query('SELECT pg_try_advisory_lock($1)', [lock]); - return lockResult[0].pg_try_advisory_lock; + private async acquireTryLock(lock: DatabaseLock, connection: Kysely): Promise { + const { rows } = await sql<{ + pg_try_advisory_lock: boolean; + }>`SELECT pg_try_advisory_lock(${lock})`.execute(connection); + return rows[0].pg_try_advisory_lock; } - private async releaseLock(lock: DatabaseLock, queryRunner: QueryRunner): Promise { - return queryRunner.query('SELECT pg_advisory_unlock($1)', [lock]); - } - - private getUpsertColumns(metadata: EntityMetadata) { - return Object.fromEntries( - metadata.ownColumns.map((column) => [column.propertyName, sql`excluded.${sql.ref(column.propertyName)}`]), - ) as any; + private async releaseLock(lock: DatabaseLock, connection: Kysely): Promise { + await sql`SELECT pg_advisory_unlock(${lock})`.execute(connection); } } diff --git a/server/src/repositories/download.repository.ts b/server/src/repositories/download.repository.ts new file mode 100644 index 0000000000..c9c62c90ce --- /dev/null +++ b/server/src/repositories/download.repository.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { Kysely } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB } from 'src/db'; +import { anyUuid } from 'src/utils/database'; + +const builder = (db: Kysely) => + db + .selectFrom('assets') + .innerJoin('exif', 'assetId', 'id') + .select(['assets.id', 'assets.livePhotoVideoId', 'exif.fileSizeInByte as size']) + .where('assets.deletedAt', 'is', null); + +@Injectable() +export class DownloadRepository { + constructor(@InjectKysely() private db: Kysely) {} + + downloadAssetIds(ids: string[]) { + return builder(this.db).where('assets.id', '=', anyUuid(ids)).stream(); + } + + downloadMotionAssetIds(ids: string[]) { + return builder(this.db).select(['assets.originalPath']).where('assets.id', '=', anyUuid(ids)).stream(); + } + + downloadAlbumId(albumId: string) { + return builder(this.db) + .innerJoin('albums_assets_assets', 'assets.id', 'albums_assets_assets.assetsId') + .where('albums_assets_assets.albumsId', '=', albumId) + .stream(); + } + + downloadUserId(userId: string) { + return builder(this.db).where('assets.ownerId', '=', userId).where('assets.isVisible', '=', true).stream(); + } +} diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index 180d8ccd4f..8c262edcde 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -9,6 +9,7 @@ import { ConfigRepository } from 'src/repositories/config.repository'; import { CronRepository } from 'src/repositories/cron.repository'; import { CryptoRepository } from 'src/repositories/crypto.repository'; import { DatabaseRepository } from 'src/repositories/database.repository'; +import { DownloadRepository } from 'src/repositories/download.repository'; import { EventRepository } from 'src/repositories/event.repository'; import { JobRepository } from 'src/repositories/job.repository'; import { LibraryRepository } from 'src/repositories/library.repository'; @@ -51,6 +52,7 @@ export const repositories = [ CronRepository, CryptoRepository, DatabaseRepository, + DownloadRepository, EventRepository, JobRepository, LibraryRepository, diff --git a/server/src/repositories/library.repository.ts b/server/src/repositories/library.repository.ts index f748600fbb..efa6e880d1 100644 --- a/server/src/repositories/library.repository.ts +++ b/server/src/repositories/library.repository.ts @@ -1,97 +1,71 @@ import { Injectable } from '@nestjs/common'; -import { ExpressionBuilder, Insertable, Kysely, sql, Updateable } from 'kysely'; -import { jsonObjectFrom } from 'kysely/helpers/postgres'; +import { Insertable, Kysely, sql, Updateable } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; import { DB, Libraries } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { LibraryStatsResponseDto } from 'src/dtos/library.dto'; -import { LibraryEntity } from 'src/entities/library.entity'; import { AssetType } from 'src/enum'; -const userColumns = [ - 'users.id', - 'users.email', - 'users.createdAt', - 'users.profileImagePath', - 'users.isAdmin', - 'users.shouldChangePassword', - 'users.deletedAt', - 'users.oauthId', - 'users.updatedAt', - 'users.storageLabel', - 'users.name', - 'users.quotaSizeInBytes', - 'users.quotaUsageInBytes', - 'users.status', - 'users.profileChangedAt', -] as const; - -const withOwner = (eb: ExpressionBuilder) => { - return jsonObjectFrom(eb.selectFrom('users').whereRef('users.id', '=', 'libraries.ownerId').select(userColumns)).as( - 'owner', - ); -}; +export enum AssetSyncResult { + DO_NOTHING, + UPDATE, + OFFLINE, + CHECK_OFFLINE, +} @Injectable() export class LibraryRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID] }) - get(id: string, withDeleted = false): Promise { + get(id: string, withDeleted = false) { return this.db .selectFrom('libraries') .selectAll('libraries') - .select(withOwner) .where('libraries.id', '=', id) .$if(!withDeleted, (qb) => qb.where('libraries.deletedAt', 'is', null)) - .executeTakeFirst() as Promise; + .executeTakeFirst(); } @GenerateSql({ params: [] }) - getAll(withDeleted = false): Promise { + getAll(withDeleted = false) { return this.db .selectFrom('libraries') .selectAll('libraries') - .select(withOwner) .orderBy('createdAt', 'asc') .$if(!withDeleted, (qb) => qb.where('libraries.deletedAt', 'is', null)) - .execute() as unknown as Promise; + .execute(); } @GenerateSql() - getAllDeleted(): Promise { + getAllDeleted() { return this.db .selectFrom('libraries') .selectAll('libraries') - .select(withOwner) .where('libraries.deletedAt', 'is not', null) .orderBy('createdAt', 'asc') - .execute() as unknown as Promise; + .execute(); } - create(library: Insertable): Promise { - return this.db - .insertInto('libraries') - .values(library) - .returningAll() - .executeTakeFirstOrThrow() as Promise; + create(library: Insertable) { + return this.db.insertInto('libraries').values(library).returningAll().executeTakeFirstOrThrow(); } - async delete(id: string): Promise { + async delete(id: string) { await this.db.deleteFrom('libraries').where('libraries.id', '=', id).execute(); } - async softDelete(id: string): Promise { + async softDelete(id: string) { await this.db.updateTable('libraries').set({ deletedAt: new Date() }).where('libraries.id', '=', id).execute(); } - update(id: string, library: Updateable): Promise { + update(id: string, library: Updateable) { return this.db .updateTable('libraries') .set(library) .where('libraries.id', '=', id) .returningAll() - .executeTakeFirstOrThrow() as Promise; + .executeTakeFirstOrThrow(); } @GenerateSql({ params: [DummyValue.UUID] }) @@ -137,4 +111,8 @@ export class LibraryRepository { total: Number(stats.photos) + Number(stats.videos), }; } + + streamAssetIds(libraryId: string) { + return this.db.selectFrom('assets').select(['id']).where('libraryId', '=', libraryId).stream(); + } } diff --git a/server/src/repositories/metadata.repository.ts b/server/src/repositories/metadata.repository.ts index 5df37a5ea7..bf3a96f21f 100644 --- a/server/src/repositories/metadata.repository.ts +++ b/server/src/repositories/metadata.repository.ts @@ -63,6 +63,14 @@ export interface ImmichTags extends Omit { Name?: string; }[]; }; + + Device?: { + Manufacturer?: string; + ModelName?: string; + }; + + AndroidMake?: string; + AndroidModel?: string; } @Injectable() @@ -73,7 +81,7 @@ export class MetadataRepository { inferTimezoneFromDatestamps: true, inferTimezoneFromTimeStamp: true, useMWG: true, - numericTags: [...DefaultReadTaskOptions.numericTags, 'FocalLength'], + numericTags: [...DefaultReadTaskOptions.numericTags, 'FocalLength', 'FileSize'], /* eslint unicorn/no-array-callback-reference: off, unicorn/no-array-method-this-argument: off */ geoTz: (lat, lon) => geotz.find(lat, lon)[0], // Enable exiftool LFS to parse metadata for files larger than 2GB. diff --git a/server/src/repositories/move.repository.ts b/server/src/repositories/move.repository.ts index c46259fa9b..706e23cef7 100644 --- a/server/src/repositories/move.repository.ts +++ b/server/src/repositories/move.repository.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, Updateable } from 'kysely'; +import { Insertable, Kysely, sql, Updateable } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; import { DB, MoveHistory } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { MoveEntity } from 'src/entities/move.entity'; -import { PathType } from 'src/enum'; +import { AssetPathType, PathType } from 'src/enum'; export type MoveCreate = Pick & Partial; @@ -47,4 +47,28 @@ export class MoveRepository { .returningAll() .executeTakeFirstOrThrow() as unknown as Promise; } + + @GenerateSql() + async cleanMoveHistory(): Promise { + await this.db + .deleteFrom('move_history') + .where((eb) => + eb( + 'move_history.entityId', + 'not in', + eb.selectFrom('assets').select('id').whereRef('assets.id', '=', 'move_history.entityId'), + ), + ) + .where('move_history.pathType', '=', sql.lit(AssetPathType.ORIGINAL)) + .execute(); + } + + @GenerateSql() + async cleanMoveHistorySingle(assetId: string): Promise { + await this.db + .deleteFrom('move_history') + .where('move_history.pathType', '=', sql.lit(AssetPathType.ORIGINAL)) + .where('entityId', '=', assetId) + .execute(); + } } diff --git a/server/src/repositories/notification.repository.spec.ts b/server/src/repositories/notification.repository.spec.ts index 6253697087..6d17501d03 100644 --- a/server/src/repositories/notification.repository.spec.ts +++ b/server/src/repositories/notification.repository.spec.ts @@ -1,11 +1,12 @@ +import { LoggingRepository } from 'src/repositories/logging.repository'; import { EmailRenderRequest, EmailTemplate, NotificationRepository } from 'src/repositories/notification.repository'; -import { newFakeLoggingRepository } from 'test/repositories/logger.repository.mock'; +import { automock } from 'test/utils'; describe(NotificationRepository.name, () => { let sut: NotificationRepository; beforeEach(() => { - sut = new NotificationRepository(newFakeLoggingRepository()); + sut = new NotificationRepository(automock(LoggingRepository, { args: [, { getEnv: () => ({}) }], strict: false })); }); describe('renderEmail', () => { diff --git a/server/src/repositories/partner.repository.ts b/server/src/repositories/partner.repository.ts index f799ff56f2..8877185d31 100644 --- a/server/src/repositories/partner.repository.ts +++ b/server/src/repositories/partner.repository.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; -import { ExpressionBuilder, Insertable, JoinBuilder, Kysely, Updateable } from 'kysely'; +import { ExpressionBuilder, Insertable, Kysely, Updateable } from 'kysely'; import { jsonObjectFrom } from 'kysely/helpers/postgres'; import { InjectKysely } from 'nestjs-kysely'; -import { DB, Partners, Users } from 'src/db'; +import { columns, Partner } from 'src/database'; +import { DB, Partners } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { PartnerEntity } from 'src/entities/partner.entity'; export interface PartnerIds { sharedById: string; @@ -16,23 +16,18 @@ export enum PartnerDirection { SharedWith = 'shared-with', } -const columns = ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'] as const; - -const onSharedBy = (join: JoinBuilder) => - join.onRef('partners.sharedById', '=', 'sharedBy.id').on('sharedBy.deletedAt', 'is', null); - -const onSharedWith = (join: JoinBuilder) => - join.onRef('partners.sharedWithId', '=', 'sharedWith.id').on('sharedWith.deletedAt', 'is', null); - const withSharedBy = (eb: ExpressionBuilder) => { return jsonObjectFrom( - eb.selectFrom('users as sharedBy').select(columns).whereRef('sharedBy.id', '=', 'partners.sharedById'), + eb.selectFrom('users as sharedBy').select(columns.userDto).whereRef('sharedBy.id', '=', 'partners.sharedById'), ).as('sharedBy'); }; const withSharedWith = (eb: ExpressionBuilder) => { return jsonObjectFrom( - eb.selectFrom('users as sharedWith').select(columns).whereRef('sharedWith.id', '=', 'partners.sharedWithId'), + eb + .selectFrom('users as sharedWith') + .select(columns.userDto) + .whereRef('sharedWith.id', '=', 'partners.sharedWithId'), ).as('sharedWith'); }; @@ -41,45 +36,33 @@ export class PartnerRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID] }) - getAll(userId: string): Promise { - return this.db - .selectFrom('partners') - .innerJoin('users as sharedBy', onSharedBy) - .innerJoin('users as sharedWith', onSharedWith) - .selectAll('partners') - .select(withSharedBy) - .select(withSharedWith) + getAll(userId: string) { + return this.builder() .where((eb) => eb.or([eb('sharedWithId', '=', userId), eb('sharedById', '=', userId)])) - .execute() as Promise; + .execute(); } @GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] }) - get({ sharedWithId, sharedById }: PartnerIds): Promise { - return this.db - .selectFrom('partners') - .innerJoin('users as sharedBy', onSharedBy) - .innerJoin('users as sharedWith', onSharedWith) - .selectAll('partners') - .select(withSharedBy) - .select(withSharedWith) + get({ sharedWithId, sharedById }: PartnerIds) { + return this.builder() .where('sharedWithId', '=', sharedWithId) .where('sharedById', '=', sharedById) - .executeTakeFirst() as unknown as Promise; + .executeTakeFirst() as Promise; } @GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] }) - create(values: Insertable): Promise { + create(values: Insertable) { return this.db .insertInto('partners') .values(values) .returningAll() .returning(withSharedBy) .returning(withSharedWith) - .executeTakeFirstOrThrow() as unknown as Promise; + .executeTakeFirstOrThrow() as Promise; } @GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }, { inTimeline: true }] }) - update({ sharedWithId, sharedById }: PartnerIds, values: Updateable): Promise { + update({ sharedWithId, sharedById }: PartnerIds, values: Updateable) { return this.db .updateTable('partners') .set(values) @@ -88,15 +71,29 @@ export class PartnerRepository { .returningAll() .returning(withSharedBy) .returning(withSharedWith) - .executeTakeFirstOrThrow() as unknown as Promise; + .executeTakeFirstOrThrow() as Promise; } @GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] }) - async remove({ sharedWithId, sharedById }: PartnerIds): Promise { + async remove({ sharedWithId, sharedById }: PartnerIds) { await this.db .deleteFrom('partners') .where('sharedWithId', '=', sharedWithId) .where('sharedById', '=', sharedById) .execute(); } + + private builder() { + return this.db + .selectFrom('partners') + .innerJoin('users as sharedBy', (join) => + join.onRef('partners.sharedById', '=', 'sharedBy.id').on('sharedBy.deletedAt', 'is', null), + ) + .innerJoin('users as sharedWith', (join) => + join.onRef('partners.sharedWithId', '=', 'sharedWith.id').on('sharedWith.deletedAt', 'is', null), + ) + .selectAll('partners') + .select(withSharedBy) + .select(withSharedWith); + } } diff --git a/server/src/repositories/person.repository.ts b/server/src/repositories/person.repository.ts index 2ac2a59cf1..d5855d3b91 100644 --- a/server/src/repositories/person.repository.ts +++ b/server/src/repositories/person.repository.ts @@ -7,7 +7,7 @@ import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { SourceType } from 'src/enum'; -import { mapUpsertColumns } from 'src/utils/database'; +import { removeUndefinedKeys } from 'src/utils/database'; import { Paginated, PaginationOptions } from 'src/utils/pagination'; import { FindOptionsRelations } from 'typeorm'; @@ -417,7 +417,22 @@ export class PersonRepository { await this.db .insertInto('person') .values(people) - .onConflict((oc) => oc.column('id').doUpdateSet(() => mapUpsertColumns('person', people[0], ['id']))) + .onConflict((oc) => + oc.column('id').doUpdateSet((eb) => + removeUndefinedKeys( + { + name: eb.ref('excluded.name'), + birthDate: eb.ref('excluded.birthDate'), + thumbnailPath: eb.ref('excluded.thumbnailPath'), + faceAssetId: eb.ref('excluded.faceAssetId'), + isHidden: eb.ref('excluded.isHidden'), + isFavorite: eb.ref('excluded.isFavorite'), + color: eb.ref('excluded.color'), + }, + people[0], + ), + ), + ) .execute(); } diff --git a/server/src/repositories/queries.vb b/server/src/repositories/queries.vb new file mode 100644 index 0000000000..4a6410f67e --- /dev/null +++ b/server/src/repositories/queries.vb @@ -0,0 +1 @@ +explain analyze select "assets".*, "assets"."id", json_build_object('first', "exif"."exifImageHeight", 'last', "exif"."exifImageWidth") as "dimensions", to_json("stacked_assets") as "stack" from "assets" left join "exif" on "assets"."id" = "exif"."assetId" left join "asset_stack" on "asset_stack"."id" = "assets"."stackId" left join lateral (select "asset_stack".*, count("stacked") as "assetCount" from "assets" as "stacked" where "stacked"."stackId" = "asset_stack"."id" and "stacked"."deletedAt" is null and "stacked"."isArchived" = false group by "asset_stack"."id") as "stacked_assets" on "asset_stack"."id" is not null where "assets"."ownerId" = any(array['aac6bd26-37a2-4223-947a-2b67b0a63cb6']::uuid[]) and "assets"."isArchived" = false and ("asset_stack"."primaryAssetId" = "assets"."id" or "assets"."stackId" is null) and "assets"."deletedAt" is null and "assets"."isVisible" = true and date_trunc('MONTH', "localDateTime" at time zone 'UTC') at time zone 'UTC' = '2025-03-01T00:00:00.000Z' order by "assets"."localDateTime" desc \ No newline at end of file diff --git a/server/src/repositories/search.repository.ts b/server/src/repositories/search.repository.ts index 46f38db55f..e2e389f47c 100644 --- a/server/src/repositories/search.repository.ts +++ b/server/src/repositories/search.repository.ts @@ -404,7 +404,7 @@ export class SearchRepository { .where('assets.ownerId', '=', anyUuid(userIds)) .where('assets.isVisible', '=', true) .where('assets.isArchived', '=', false) - .where('assets.type', '=', 'IMAGE') + .where('assets.type', '=', AssetType.IMAGE) .where('assets.deletedAt', 'is', null) .orderBy('city') .limit(1); @@ -421,7 +421,7 @@ export class SearchRepository { .where('assets.ownerId', '=', anyUuid(userIds)) .where('assets.isVisible', '=', true) .where('assets.isArchived', '=', false) - .where('assets.type', '=', 'IMAGE') + .where('assets.type', '=', AssetType.IMAGE) .where('assets.deletedAt', 'is', null) .whereRef('exif.city', '>', 'cte.city') .orderBy('city') diff --git a/server/src/repositories/storage.repository.spec.ts b/server/src/repositories/storage.repository.spec.ts index 93b21a7f9b..85ff4a746f 100644 --- a/server/src/repositories/storage.repository.spec.ts +++ b/server/src/repositories/storage.repository.spec.ts @@ -1,7 +1,8 @@ import mockfs from 'mock-fs'; import { CrawlOptionsDto } from 'src/dtos/library.dto'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { StorageRepository } from 'src/repositories/storage.repository'; -import { newFakeLoggingRepository } from 'test/repositories/logger.repository.mock'; +import { automock } from 'test/utils'; interface Test { test: string; @@ -182,7 +183,7 @@ describe(StorageRepository.name, () => { let sut: StorageRepository; beforeEach(() => { - sut = new StorageRepository(newFakeLoggingRepository()); + sut = new StorageRepository(automock(LoggingRepository, { args: [, { getEnv: () => ({}) }], strict: false })); }); afterEach(() => { diff --git a/server/src/repositories/sync.repository.ts b/server/src/repositories/sync.repository.ts index f2c5a1fc16..bc3205c0a3 100644 --- a/server/src/repositories/sync.repository.ts +++ b/server/src/repositories/sync.repository.ts @@ -1,10 +1,14 @@ import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, sql } from 'kysely'; +import { Insertable, Kysely, SelectQueryBuilder, sql } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; +import { columns } from 'src/database'; import { DB, SessionSyncCheckpoints } from 'src/db'; import { SyncEntityType } from 'src/enum'; import { SyncAck } from 'src/types'; +type auditTables = 'users_audit' | 'partners_audit' | 'assets_audit'; +type upsertTables = 'users' | 'partners' | 'assets' | 'exif'; + @Injectable() export class SyncRepository { constructor(@InjectKysely() private db: Kysely) {} @@ -41,9 +45,7 @@ export class SyncRepository { return this.db .selectFrom('users') .select(['id', 'name', 'email', 'deletedAt', 'updateId']) - .$if(!!ack, (qb) => qb.where('updateId', '>', ack!.updateId)) - .where('updatedAt', '<', sql.raw("now() - interval '1 millisecond'")) - .orderBy(['updateId asc']) + .$call((qb) => this.upsertTableFilters(qb, ack)) .stream(); } @@ -51,9 +53,7 @@ export class SyncRepository { return this.db .selectFrom('users_audit') .select(['id', 'userId']) - .$if(!!ack, (qb) => qb.where('id', '>', ack!.updateId)) - .where('deletedAt', '<', sql.raw("now() - interval '1 millisecond'")) - .orderBy(['id asc']) + .$call((qb) => this.auditTableFilters(qb, ack)) .stream(); } @@ -61,10 +61,8 @@ export class SyncRepository { return this.db .selectFrom('partners') .select(['sharedById', 'sharedWithId', 'inTimeline', 'updateId']) - .$if(!!ack, (qb) => qb.where('updateId', '>', ack!.updateId)) .where((eb) => eb.or([eb('sharedById', '=', userId), eb('sharedWithId', '=', userId)])) - .where('updatedAt', '<', sql.raw("now() - interval '1 millisecond'")) - .orderBy(['updateId asc']) + .$call((qb) => this.upsertTableFilters(qb, ack)) .stream(); } @@ -72,10 +70,93 @@ export class SyncRepository { return this.db .selectFrom('partners_audit') .select(['id', 'sharedById', 'sharedWithId']) - .$if(!!ack, (qb) => qb.where('id', '>', ack!.updateId)) .where((eb) => eb.or([eb('sharedById', '=', userId), eb('sharedWithId', '=', userId)])) - .where('deletedAt', '<', sql.raw("now() - interval '1 millisecond'")) - .orderBy(['id asc']) + .$call((qb) => this.auditTableFilters(qb, ack)) .stream(); } + + getAssetUpserts(userId: string, ack?: SyncAck) { + return this.db + .selectFrom('assets') + .select(columns.syncAsset) + .where('ownerId', '=', userId) + .$call((qb) => this.upsertTableFilters(qb, ack)) + .stream(); + } + + getPartnerAssetsUpserts(userId: string, ack?: SyncAck) { + return this.db + .selectFrom('assets') + .select(columns.syncAsset) + .where('ownerId', 'in', (eb) => + eb.selectFrom('partners').select(['sharedById']).where('sharedWithId', '=', userId), + ) + .$call((qb) => this.upsertTableFilters(qb, ack)) + .stream(); + } + + getAssetDeletes(userId: string, ack?: SyncAck) { + return this.db + .selectFrom('assets_audit') + .select(['id', 'assetId']) + .where('ownerId', '=', userId) + .$if(!!ack, (qb) => qb.where('id', '>', ack!.updateId)) + .$call((qb) => this.auditTableFilters(qb, ack)) + .stream(); + } + + getPartnerAssetDeletes(userId: string, ack?: SyncAck) { + return this.db + .selectFrom('assets_audit') + .select(['id', 'assetId']) + .where('ownerId', 'in', (eb) => + eb.selectFrom('partners').select(['sharedById']).where('sharedWithId', '=', userId), + ) + .$call((qb) => this.auditTableFilters(qb, ack)) + .stream(); + } + + getAssetExifsUpserts(userId: string, ack?: SyncAck) { + return this.db + .selectFrom('exif') + .select(columns.syncAssetExif) + .where('assetId', 'in', (eb) => eb.selectFrom('assets').select('id').where('ownerId', '=', userId)) + .$call((qb) => this.upsertTableFilters(qb, ack)) + .stream(); + } + + getPartnerAssetExifsUpserts(userId: string, ack?: SyncAck) { + return this.db + .selectFrom('exif') + .select(columns.syncAssetExif) + .where('assetId', 'in', (eb) => + eb + .selectFrom('assets') + .select('id') + .where('ownerId', 'in', (eb) => + eb.selectFrom('partners').select(['sharedById']).where('sharedWithId', '=', userId), + ), + ) + .$call((qb) => this.upsertTableFilters(qb, ack)) + .stream(); + } + + private auditTableFilters, D>(qb: SelectQueryBuilder, ack?: SyncAck) { + const builder = qb as SelectQueryBuilder; + return builder + .where('deletedAt', '<', sql.raw("now() - interval '1 millisecond'")) + .$if(!!ack, (qb) => qb.where('id', '>', ack!.updateId)) + .orderBy(['id asc']) as SelectQueryBuilder; + } + + private upsertTableFilters, D>( + qb: SelectQueryBuilder, + ack?: SyncAck, + ) { + const builder = qb as SelectQueryBuilder; + return builder + .where('updatedAt', '<', sql.raw("now() - interval '1 millisecond'")) + .$if(!!ack, (qb) => qb.where('updateId', '>', ack!.updateId)) + .orderBy(['updateId asc']) as SelectQueryBuilder; + } } diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index 302f868971..1387828be6 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -5,7 +5,7 @@ import { DB, UserMetadata as DbUserMetadata, Users } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { UserMetadata, UserMetadataItem } from 'src/entities/user-metadata.entity'; import { UserEntity, withMetadata } from 'src/entities/user.entity'; -import { UserStatus } from 'src/enum'; +import { AssetType, UserStatus } from 'src/enum'; import { asUuid } from 'src/utils/database'; const columns = [ @@ -209,11 +209,11 @@ export class UserRepository { .select((eb) => [ eb.fn .countAll() - .filterWhere((eb) => eb.and([eb('assets.type', '=', 'IMAGE'), eb('assets.isVisible', '=', true)])) + .filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.IMAGE), eb('assets.isVisible', '=', true)])) .as('photos'), eb.fn .countAll() - .filterWhere((eb) => eb.and([eb('assets.type', '=', 'VIDEO'), eb('assets.isVisible', '=', true)])) + .filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.VIDEO), eb('assets.isVisible', '=', true)])) .as('videos'), eb.fn .coalesce(eb.fn.sum('exif.fileSizeInByte').filterWhere('assets.libraryId', 'is', null), eb.lit(0)) @@ -222,7 +222,9 @@ export class UserRepository { .coalesce( eb.fn .sum('exif.fileSizeInByte') - .filterWhere((eb) => eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', 'IMAGE')])), + .filterWhere((eb) => + eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', AssetType.IMAGE)]), + ), eb.lit(0), ) .as('usagePhotos'), @@ -230,7 +232,9 @@ export class UserRepository { .coalesce( eb.fn .sum('exif.fileSizeInByte') - .filterWhere((eb) => eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', 'VIDEO')])), + .filterWhere((eb) => + eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', AssetType.VIDEO)]), + ), eb.lit(0), ) .as('usageVideos'), diff --git a/server/src/services/activity.service.spec.ts b/server/src/services/activity.service.spec.ts index bb63c7bf7b..1e5bb3b505 100644 --- a/server/src/services/activity.service.spec.ts +++ b/server/src/services/activity.service.spec.ts @@ -1,8 +1,7 @@ import { BadRequestException } from '@nestjs/common'; import { ReactionType } from 'src/dtos/activity.dto'; import { ActivityService } from 'src/services/activity.service'; -import { activityStub } from 'test/fixtures/activity.stub'; -import { authStub } from 'test/fixtures/auth.stub'; +import { factory, newUuid, newUuids } from 'test/small.factory'; import { newTestService, ServiceMocks } from 'test/utils'; describe(ActivityService.name, () => { @@ -19,137 +18,118 @@ describe(ActivityService.name, () => { describe('getAll', () => { it('should get all', async () => { - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id'])); + const [albumId, assetId, userId] = newUuids(); + + mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId])); mocks.activity.search.mockResolvedValue([]); - await expect(sut.getAll(authStub.admin, { assetId: 'asset-id', albumId: 'album-id' })).resolves.toEqual([]); + await expect(sut.getAll(factory.auth({ id: userId }), { assetId, albumId })).resolves.toEqual([]); - expect(mocks.activity.search).toHaveBeenCalledWith({ - assetId: 'asset-id', - albumId: 'album-id', - isLiked: undefined, - }); + expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: undefined }); }); it('should filter by type=like', async () => { - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id'])); + const [albumId, assetId, userId] = newUuids(); + + mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId])); mocks.activity.search.mockResolvedValue([]); await expect( - sut.getAll(authStub.admin, { assetId: 'asset-id', albumId: 'album-id', type: ReactionType.LIKE }), + sut.getAll(factory.auth({ id: userId }), { assetId, albumId, type: ReactionType.LIKE }), ).resolves.toEqual([]); - expect(mocks.activity.search).toHaveBeenCalledWith({ - assetId: 'asset-id', - albumId: 'album-id', - isLiked: true, - }); + expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: true }); }); it('should filter by type=comment', async () => { - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id'])); + const [albumId, assetId] = newUuids(); + + mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId])); mocks.activity.search.mockResolvedValue([]); - await expect( - sut.getAll(authStub.admin, { assetId: 'asset-id', albumId: 'album-id', type: ReactionType.COMMENT }), - ).resolves.toEqual([]); + await expect(sut.getAll(factory.auth(), { assetId, albumId, type: ReactionType.COMMENT })).resolves.toEqual([]); - expect(mocks.activity.search).toHaveBeenCalledWith({ - assetId: 'asset-id', - albumId: 'album-id', - isLiked: false, - }); + expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: false }); }); }); describe('getStatistics', () => { it('should get the comment count', async () => { + const [albumId, assetId] = newUuids(); + mocks.activity.getStatistics.mockResolvedValue(1); - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([activityStub.oneComment.albumId])); - await expect( - sut.getStatistics(authStub.admin, { - assetId: 'asset-id', - albumId: activityStub.oneComment.albumId, - }), - ).resolves.toEqual({ comments: 1 }); + mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId])); + + await expect(sut.getStatistics(factory.auth(), { assetId, albumId })).resolves.toEqual({ comments: 1 }); }); }); describe('addComment', () => { it('should require access to the album', async () => { + const [albumId, assetId] = newUuids(); + await expect( - sut.create(authStub.admin, { - albumId: 'album-id', - assetId: 'asset-id', - type: ReactionType.COMMENT, - comment: 'comment', - }), + sut.create(factory.auth(), { albumId, assetId, type: ReactionType.COMMENT, comment: 'comment' }), ).rejects.toBeInstanceOf(BadRequestException); }); it('should create a comment', async () => { - mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set(['album-id'])); - mocks.activity.create.mockResolvedValue(activityStub.oneComment); + const [albumId, assetId, userId] = newUuids(); + const activity = factory.activity({ albumId, assetId, userId }); - await sut.create(authStub.admin, { - albumId: 'album-id', - assetId: 'asset-id', + mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set([albumId])); + mocks.activity.create.mockResolvedValue(activity); + + await sut.create(factory.auth({ id: userId }), { + albumId, + assetId, type: ReactionType.COMMENT, comment: 'comment', }); expect(mocks.activity.create).toHaveBeenCalledWith({ - userId: 'admin_id', - albumId: 'album-id', - assetId: 'asset-id', + userId: activity.userId, + albumId: activity.albumId, + assetId: activity.assetId, comment: 'comment', isLiked: false, }); }); it('should fail because activity is disabled for the album', async () => { - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id'])); - mocks.activity.create.mockResolvedValue(activityStub.oneComment); + const [albumId, assetId] = newUuids(); + const activity = factory.activity({ albumId, assetId }); + + mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId])); + mocks.activity.create.mockResolvedValue(activity); await expect( - sut.create(authStub.admin, { - albumId: 'album-id', - assetId: 'asset-id', - type: ReactionType.COMMENT, - comment: 'comment', - }), + sut.create(factory.auth(), { albumId, assetId, type: ReactionType.COMMENT, comment: 'comment' }), ).rejects.toBeInstanceOf(BadRequestException); }); it('should create a like', async () => { - mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set(['album-id'])); - mocks.activity.create.mockResolvedValue(activityStub.liked); + const [albumId, assetId, userId] = newUuids(); + const activity = factory.activity({ userId, albumId, assetId, isLiked: true }); + + mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set([albumId])); + mocks.activity.create.mockResolvedValue(activity); mocks.activity.search.mockResolvedValue([]); - await sut.create(authStub.admin, { - albumId: 'album-id', - assetId: 'asset-id', - type: ReactionType.LIKE, - }); + await sut.create(factory.auth({ id: userId }), { albumId, assetId, type: ReactionType.LIKE }); - expect(mocks.activity.create).toHaveBeenCalledWith({ - userId: 'admin_id', - albumId: 'album-id', - assetId: 'asset-id', - isLiked: true, - }); + expect(mocks.activity.create).toHaveBeenCalledWith({ userId: activity.userId, albumId, assetId, isLiked: true }); }); it('should skip if like exists', async () => { - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id'])); - mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set(['album-id'])); - mocks.activity.search.mockResolvedValue([activityStub.liked]); + const [albumId, assetId] = newUuids(); + const activity = factory.activity({ albumId, assetId, isLiked: true }); - await sut.create(authStub.admin, { - albumId: 'album-id', - assetId: 'asset-id', - type: ReactionType.LIKE, - }); + mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId])); + mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set([albumId])); + mocks.activity.search.mockResolvedValue([activity]); + + await sut.create(factory.auth(), { albumId, assetId, type: ReactionType.LIKE }); expect(mocks.activity.create).not.toHaveBeenCalled(); }); @@ -157,20 +137,31 @@ describe(ActivityService.name, () => { describe('delete', () => { it('should require access', async () => { - await expect(sut.delete(authStub.admin, activityStub.oneComment.id)).rejects.toBeInstanceOf(BadRequestException); + await expect(sut.delete(factory.auth(), newUuid())).rejects.toBeInstanceOf(BadRequestException); + expect(mocks.activity.delete).not.toHaveBeenCalled(); }); it('should let the activity owner delete a comment', async () => { - mocks.access.activity.checkOwnerAccess.mockResolvedValue(new Set(['activity-id'])); - await sut.delete(authStub.admin, 'activity-id'); - expect(mocks.activity.delete).toHaveBeenCalledWith('activity-id'); + const activity = factory.activity(); + + mocks.access.activity.checkOwnerAccess.mockResolvedValue(new Set([activity.id])); + mocks.activity.delete.mockResolvedValue(); + + await sut.delete(factory.auth(), activity.id); + + expect(mocks.activity.delete).toHaveBeenCalledWith(activity.id); }); it('should let the album owner delete a comment', async () => { - mocks.access.activity.checkAlbumOwnerAccess.mockResolvedValue(new Set(['activity-id'])); - await sut.delete(authStub.admin, 'activity-id'); - expect(mocks.activity.delete).toHaveBeenCalledWith('activity-id'); + const activity = factory.activity(); + + mocks.access.activity.checkAlbumOwnerAccess.mockResolvedValue(new Set([activity.id])); + mocks.activity.delete.mockResolvedValue(); + + await sut.delete(factory.auth(), activity.id); + + expect(mocks.activity.delete).toHaveBeenCalledWith(activity.id); }); }); }); diff --git a/server/src/services/album.service.spec.ts b/server/src/services/album.service.spec.ts index 832ed59dd5..62d1326cab 100644 --- a/server/src/services/album.service.spec.ts +++ b/server/src/services/album.service.spec.ts @@ -347,6 +347,7 @@ describe(AlbumService.name, () => { it('should remove a shared user from an owned album', async () => { mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumStub.sharedWithUser.id])); mocks.album.getById.mockResolvedValue(albumStub.sharedWithUser); + mocks.albumUser.delete.mockResolvedValue(); await expect( sut.removeUser(authStub.admin, albumStub.sharedWithUser.id, userStub.user1.id), @@ -376,6 +377,7 @@ describe(AlbumService.name, () => { it('should allow a shared user to remove themselves', async () => { mocks.album.getById.mockResolvedValue(albumStub.sharedWithUser); + mocks.albumUser.delete.mockResolvedValue(); await sut.removeUser(authStub.user1, albumStub.sharedWithUser.id, authStub.user1.user.id); @@ -388,6 +390,7 @@ describe(AlbumService.name, () => { it('should allow a shared user to remove themselves using "me"', async () => { mocks.album.getById.mockResolvedValue(albumStub.sharedWithUser); + mocks.albumUser.delete.mockResolvedValue(); await sut.removeUser(authStub.user1, albumStub.sharedWithUser.id, 'me'); @@ -422,6 +425,8 @@ describe(AlbumService.name, () => { describe('updateUser', () => { it('should update user role', async () => { mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumStub.sharedWithAdmin.id])); + mocks.albumUser.update.mockResolvedValue(null as any); + await sut.updateUser(authStub.user1, albumStub.sharedWithAdmin.id, userStub.admin.id, { role: AlbumUserRole.EDITOR, }); diff --git a/server/src/services/api-key.service.spec.ts b/server/src/services/api-key.service.spec.ts index 905bbede2a..0a89c04b0d 100644 --- a/server/src/services/api-key.service.spec.ts +++ b/server/src/services/api-key.service.spec.ts @@ -1,112 +1,152 @@ import { BadRequestException } from '@nestjs/common'; import { Permission } from 'src/enum'; -import { APIKeyService } from 'src/services/api-key.service'; -import { keyStub } from 'test/fixtures/api-key.stub'; -import { authStub } from 'test/fixtures/auth.stub'; +import { ApiKeyService } from 'src/services/api-key.service'; +import { factory, newUuid } from 'test/small.factory'; import { newTestService, ServiceMocks } from 'test/utils'; -describe(APIKeyService.name, () => { - let sut: APIKeyService; +describe(ApiKeyService.name, () => { + let sut: ApiKeyService; let mocks: ServiceMocks; beforeEach(() => { - ({ sut, mocks } = newTestService(APIKeyService)); + ({ sut, mocks } = newTestService(ApiKeyService)); }); describe('create', () => { it('should create a new key', async () => { - mocks.apiKey.create.mockResolvedValue(keyStub.admin); - await sut.create(authStub.admin, { name: 'Test Key', permissions: [Permission.ALL] }); + const auth = factory.auth(); + const apiKey = factory.apiKey({ userId: auth.user.id, permissions: [Permission.ALL] }); + const key = 'super-secret'; + + mocks.crypto.newPassword.mockReturnValue(key); + mocks.apiKey.create.mockResolvedValue(apiKey); + + await sut.create(auth, { name: apiKey.name, permissions: apiKey.permissions }); + expect(mocks.apiKey.create).toHaveBeenCalledWith({ - key: 'cmFuZG9tLWJ5dGVz (hashed)', - name: 'Test Key', - permissions: [Permission.ALL], - userId: authStub.admin.user.id, + key: 'super-secret (hashed)', + name: apiKey.name, + permissions: apiKey.permissions, + userId: apiKey.userId, }); expect(mocks.crypto.newPassword).toHaveBeenCalled(); expect(mocks.crypto.hashSha256).toHaveBeenCalled(); }); it('should not require a name', async () => { - mocks.apiKey.create.mockResolvedValue(keyStub.admin); + const auth = factory.auth(); + const apiKey = factory.apiKey({ userId: auth.user.id }); + const key = 'super-secret'; - await sut.create(authStub.admin, { permissions: [Permission.ALL] }); + mocks.crypto.newPassword.mockReturnValue(key); + mocks.apiKey.create.mockResolvedValue(apiKey); + + await sut.create(auth, { permissions: [Permission.ALL] }); expect(mocks.apiKey.create).toHaveBeenCalledWith({ - key: 'cmFuZG9tLWJ5dGVz (hashed)', + key: 'super-secret (hashed)', name: 'API Key', permissions: [Permission.ALL], - userId: authStub.admin.user.id, + userId: auth.user.id, }); expect(mocks.crypto.newPassword).toHaveBeenCalled(); expect(mocks.crypto.hashSha256).toHaveBeenCalled(); }); it('should throw an error if the api key does not have sufficient permissions', async () => { - await expect( - sut.create({ ...authStub.admin, apiKey: keyStub.authKey }, { permissions: [Permission.ASSET_READ] }), - ).rejects.toBeInstanceOf(BadRequestException); + const auth = factory.auth({ apiKey: factory.authApiKey({ permissions: [Permission.ASSET_READ] }) }); + + await expect(sut.create(auth, { permissions: [Permission.ASSET_UPDATE] })).rejects.toBeInstanceOf( + BadRequestException, + ); }); }); describe('update', () => { it('should throw an error if the key is not found', async () => { - await expect(sut.update(authStub.admin, 'random-guid', { name: 'New Name' })).rejects.toBeInstanceOf( - BadRequestException, - ); + const id = newUuid(); + const auth = factory.auth(); - expect(mocks.apiKey.update).not.toHaveBeenCalledWith('random-guid'); + mocks.apiKey.getById.mockResolvedValue(void 0); + + await expect(sut.update(auth, id, { name: 'New Name' })).rejects.toBeInstanceOf(BadRequestException); + + expect(mocks.apiKey.update).not.toHaveBeenCalledWith(id); }); it('should update a key', async () => { - mocks.apiKey.getById.mockResolvedValue(keyStub.admin); - mocks.apiKey.update.mockResolvedValue(keyStub.admin); + const auth = factory.auth(); + const apiKey = factory.apiKey({ userId: auth.user.id }); + const newName = 'New name'; - await sut.update(authStub.admin, 'random-guid', { name: 'New Name' }); + mocks.apiKey.getById.mockResolvedValue(apiKey); + mocks.apiKey.update.mockResolvedValue(apiKey); - expect(mocks.apiKey.update).toHaveBeenCalledWith(authStub.admin.user.id, 'random-guid', { name: 'New Name' }); + await sut.update(auth, apiKey.id, { name: newName }); + + expect(mocks.apiKey.update).toHaveBeenCalledWith(auth.user.id, apiKey.id, { name: newName }); }); }); describe('delete', () => { it('should throw an error if the key is not found', async () => { - await expect(sut.delete(authStub.admin, 'random-guid')).rejects.toBeInstanceOf(BadRequestException); + const auth = factory.auth(); + const id = newUuid(); - expect(mocks.apiKey.delete).not.toHaveBeenCalledWith('random-guid'); + mocks.apiKey.getById.mockResolvedValue(void 0); + + await expect(sut.delete(auth, id)).rejects.toBeInstanceOf(BadRequestException); + + expect(mocks.apiKey.delete).not.toHaveBeenCalledWith(id); }); it('should delete a key', async () => { - mocks.apiKey.getById.mockResolvedValue(keyStub.admin); + const auth = factory.auth(); + const apiKey = factory.apiKey({ userId: auth.user.id }); - await sut.delete(authStub.admin, 'random-guid'); + mocks.apiKey.getById.mockResolvedValue(apiKey); + mocks.apiKey.delete.mockResolvedValue(); - expect(mocks.apiKey.delete).toHaveBeenCalledWith(authStub.admin.user.id, 'random-guid'); + await sut.delete(auth, apiKey.id); + + expect(mocks.apiKey.delete).toHaveBeenCalledWith(auth.user.id, apiKey.id); }); }); describe('getById', () => { it('should throw an error if the key is not found', async () => { - await expect(sut.getById(authStub.admin, 'random-guid')).rejects.toBeInstanceOf(BadRequestException); + const auth = factory.auth(); + const id = newUuid(); - expect(mocks.apiKey.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'random-guid'); + mocks.apiKey.getById.mockResolvedValue(void 0); + + await expect(sut.getById(auth, id)).rejects.toBeInstanceOf(BadRequestException); + + expect(mocks.apiKey.getById).toHaveBeenCalledWith(auth.user.id, id); }); it('should get a key by id', async () => { - mocks.apiKey.getById.mockResolvedValue(keyStub.admin); + const auth = factory.auth(); + const apiKey = factory.apiKey({ userId: auth.user.id }); - await sut.getById(authStub.admin, 'random-guid'); + mocks.apiKey.getById.mockResolvedValue(apiKey); - expect(mocks.apiKey.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'random-guid'); + await sut.getById(auth, apiKey.id); + + expect(mocks.apiKey.getById).toHaveBeenCalledWith(auth.user.id, apiKey.id); }); }); describe('getAll', () => { it('should return all the keys for a user', async () => { - mocks.apiKey.getByUserId.mockResolvedValue([keyStub.admin]); + const auth = factory.auth(); + const apiKey = factory.apiKey({ userId: auth.user.id }); - await expect(sut.getAll(authStub.admin)).resolves.toHaveLength(1); + mocks.apiKey.getByUserId.mockResolvedValue([apiKey]); - expect(mocks.apiKey.getByUserId).toHaveBeenCalledWith(authStub.admin.user.id); + await expect(sut.getAll(auth)).resolves.toHaveLength(1); + + expect(mocks.apiKey.getByUserId).toHaveBeenCalledWith(auth.user.id); }); }); }); diff --git a/server/src/services/api-key.service.ts b/server/src/services/api-key.service.ts index 44c3015330..5459b56889 100644 --- a/server/src/services/api-key.service.ts +++ b/server/src/services/api-key.service.ts @@ -7,7 +7,7 @@ import { ApiKeyItem } from 'src/types'; import { isGranted } from 'src/utils/access'; @Injectable() -export class APIKeyService extends BaseService { +export class ApiKeyService extends BaseService { async create(auth: AuthDto, dto: APIKeyCreateDto): Promise { const secret = this.cryptoRepository.newPassword(32); diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index f91f600bb1..5e7e2d79d0 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -9,9 +9,9 @@ import { AssetService } from 'src/services/asset.service'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { faceStub } from 'test/fixtures/face.stub'; -import { partnerStub } from 'test/fixtures/partner.stub'; import { userStub } from 'test/fixtures/user.stub'; -import { newTestService, ServiceMocks } from 'test/utils'; +import { factory } from 'test/small.factory'; +import { makeStream, newTestService, ServiceMocks } from 'test/utils'; import { vitest } from 'vitest'; const stats: AssetStats = { @@ -87,13 +87,16 @@ describe(AssetService.name, () => { }); it('should get memories with partners with inTimeline enabled', async () => { - mocks.partner.getAll.mockResolvedValue([partnerStub.user1ToAdmin1]); + const partner = factory.partner(); + const auth = factory.auth({ id: partner.sharedWithId }); + + mocks.partner.getAll.mockResolvedValue([partner]); mocks.asset.getByDayOfYear.mockResolvedValue([]); - await sut.getMemoryLane(authStub.admin, { day: 15, month: 1 }); + await sut.getMemoryLane(auth, { day: 15, month: 1 }); expect(mocks.asset.getByDayOfYear.mock.calls).toEqual([ - [[authStub.admin.user.id, userStub.user1.id], { day: 15, month: 1 }], + [[auth.user.id, partner.sharedById], { day: 15, month: 1 }], ]); }); }); @@ -126,23 +129,36 @@ describe(AssetService.name, () => { describe('getRandom', () => { it('should get own random assets', async () => { + mocks.partner.getAll.mockResolvedValue([]); mocks.asset.getRandom.mockResolvedValue([assetStub.image]); + await sut.getRandom(authStub.admin, 1); + expect(mocks.asset.getRandom).toHaveBeenCalledWith([authStub.admin.user.id], 1); }); it('should not include partner assets if not in timeline', async () => { + const partner = factory.partner({ inTimeline: false }); + const auth = factory.auth({ id: partner.sharedWithId }); + mocks.asset.getRandom.mockResolvedValue([assetStub.image]); - mocks.partner.getAll.mockResolvedValue([{ ...partnerStub.user1ToAdmin1, inTimeline: false }]); - await sut.getRandom(authStub.admin, 1); - expect(mocks.asset.getRandom).toHaveBeenCalledWith([authStub.admin.user.id], 1); + mocks.partner.getAll.mockResolvedValue([partner]); + + await sut.getRandom(auth, 1); + + expect(mocks.asset.getRandom).toHaveBeenCalledWith([auth.user.id], 1); }); it('should include partner assets if in timeline', async () => { + const partner = factory.partner({ inTimeline: true }); + const auth = factory.auth({ id: partner.sharedWithId }); + mocks.asset.getRandom.mockResolvedValue([assetStub.image]); - mocks.partner.getAll.mockResolvedValue([partnerStub.user1ToAdmin1]); - await sut.getRandom(authStub.admin, 1); - expect(mocks.asset.getRandom).toHaveBeenCalledWith([userStub.admin.id, userStub.user1.id], 1); + mocks.partner.getAll.mockResolvedValue([partner]); + + await sut.getRandom(auth, 1); + + expect(mocks.asset.getRandom).toHaveBeenCalledWith([auth.user.id, partner.sharedById], 1); }); }); @@ -150,7 +166,9 @@ describe(AssetService.name, () => { it('should allow owner access', async () => { mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); mocks.asset.getById.mockResolvedValue(assetStub.image); + await sut.get(authStub.admin, assetStub.image.id); + expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith( authStub.admin.user.id, new Set([assetStub.image.id]), @@ -160,7 +178,9 @@ describe(AssetService.name, () => { it('should allow shared link access', async () => { mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([assetStub.image.id])); mocks.asset.getById.mockResolvedValue(assetStub.image); + await sut.get(authStub.adminSharedLink, assetStub.image.id); + expect(mocks.access.asset.checkSharedLinkAccess).toHaveBeenCalledWith( authStub.adminSharedLink.sharedLink?.id, new Set([assetStub.image.id]), @@ -187,7 +207,9 @@ describe(AssetService.name, () => { it('should allow partner sharing access', async () => { mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set([assetStub.image.id])); mocks.asset.getById.mockResolvedValue(assetStub.image); + await sut.get(authStub.admin, assetStub.image.id); + expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith( authStub.admin.user.id, new Set([assetStub.image.id]), @@ -197,7 +219,9 @@ describe(AssetService.name, () => { it('should allow shared album access', async () => { mocks.access.asset.checkAlbumAccess.mockResolvedValue(new Set([assetStub.image.id])); mocks.asset.getById.mockResolvedValue(assetStub.image); + await sut.get(authStub.admin, assetStub.image.id); + expect(mocks.access.asset.checkAlbumAccess).toHaveBeenCalledWith( authStub.admin.user.id, new Set([assetStub.image.id]), @@ -206,17 +230,20 @@ describe(AssetService.name, () => { it('should throw an error for no access', async () => { await expect(sut.get(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException); + expect(mocks.asset.getById).not.toHaveBeenCalled(); }); it('should throw an error for an invalid shared link', async () => { await expect(sut.get(authStub.adminSharedLink, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException); + expect(mocks.access.asset.checkOwnerAccess).not.toHaveBeenCalled(); expect(mocks.asset.getById).not.toHaveBeenCalled(); }); it('should throw an error if the asset could not be found', async () => { mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); + await expect(sut.get(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException); }); }); @@ -226,6 +253,7 @@ describe(AssetService.name, () => { await expect(sut.update(authStub.admin, 'asset-1', { isArchived: false })).rejects.toBeInstanceOf( BadRequestException, ); + expect(mocks.asset.update).not.toHaveBeenCalled(); }); @@ -255,6 +283,7 @@ describe(AssetService.name, () => { mocks.asset.update.mockResolvedValueOnce(assetStub.image); await sut.update(authStub.admin, 'asset-1', { rating: 3 }); + expect(mocks.asset.upsertExif).toHaveBeenCalledWith({ assetId: 'asset-1', rating: 3 }); }); @@ -397,12 +426,15 @@ describe(AssetService.name, () => { it('should update all assets', async () => { mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2'])); + await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], isArchived: true }); + expect(mocks.asset.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true }); }); it('should not update Assets table if no relevant fields are provided', async () => { mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); + await sut.updateAll(authStub.admin, { ids: ['asset-1'], latitude: 0, @@ -417,6 +449,7 @@ describe(AssetService.name, () => { it('should update Assets table if isArchived field is provided', async () => { mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); + await sut.updateAll(authStub.admin, { ids: ['asset-1'], latitude: 0, @@ -473,28 +506,30 @@ describe(AssetService.name, () => { }); it('should immediately queue assets for deletion if trash is disabled', async () => { - mocks.asset.getAll.mockResolvedValue({ hasNextPage: false, items: [assetStub.image] }); + const asset = factory.asset({ isOffline: false }); + + mocks.asset.streamDeletedAssets.mockReturnValue(makeStream([asset])); mocks.systemMetadata.get.mockResolvedValue({ trash: { enabled: false } }); await expect(sut.handleAssetDeletionCheck()).resolves.toBe(JobStatus.SUCCESS); - expect(mocks.asset.getAll).toHaveBeenCalledWith(expect.anything(), { trashedBefore: new Date() }); + expect(mocks.asset.streamDeletedAssets).toHaveBeenCalledWith(new Date()); expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.ASSET_DELETION, data: { id: assetStub.image.id, deleteOnDisk: true } }, + { name: JobName.ASSET_DELETION, data: { id: asset.id, deleteOnDisk: true } }, ]); }); it('should queue assets for deletion after trash duration', async () => { - mocks.asset.getAll.mockResolvedValue({ hasNextPage: false, items: [assetStub.image] }); + const asset = factory.asset({ isOffline: false }); + + mocks.asset.streamDeletedAssets.mockReturnValue(makeStream([asset])); mocks.systemMetadata.get.mockResolvedValue({ trash: { enabled: true, days: 7 } }); await expect(sut.handleAssetDeletionCheck()).resolves.toBe(JobStatus.SUCCESS); - expect(mocks.asset.getAll).toHaveBeenCalledWith(expect.anything(), { - trashedBefore: DateTime.now().minus({ days: 7 }).toJSDate(), - }); + expect(mocks.asset.streamDeletedAssets).toHaveBeenCalledWith(DateTime.now().minus({ days: 7 }).toJSDate()); expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.ASSET_DELETION, data: { id: assetStub.image.id, deleteOnDisk: true } }, + { name: JobName.ASSET_DELETION, data: { id: asset.id, deleteOnDisk: true } }, ]); }); }); @@ -528,6 +563,7 @@ describe(AssetService.name, () => { }); it('should update stack primary asset if deleted asset was primary asset in a stack', async () => { + mocks.stack.update.mockResolvedValue(factory.stack() as unknown as any); mocks.asset.getById.mockResolvedValue(assetStub.primaryImage as AssetEntity); await sut.handleAssetDeletion({ id: assetStub.primaryImage.id, deleteOnDisk: true }); @@ -539,6 +575,7 @@ describe(AssetService.name, () => { }); it('should delete the entire stack if deleted asset was the primary asset and the stack would only contain one asset afterwards', async () => { + mocks.stack.delete.mockResolvedValue(); mocks.asset.getById.mockResolvedValue({ ...assetStub.primaryImage, stack: { ...assetStub.primaryImage.stack, assets: assetStub.primaryImage.stack!.assets.slice(0, 2) }, @@ -616,25 +653,33 @@ describe(AssetService.name, () => { describe('run', () => { it('should run the refresh faces job', async () => { mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); + await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_FACES }); + expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.FACE_DETECTION, data: { id: 'asset-1' } }]); }); it('should run the refresh metadata job', async () => { mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); + await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_METADATA }); + expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.METADATA_EXTRACTION, data: { id: 'asset-1' } }]); }); it('should run the refresh thumbnails job', async () => { mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); + await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REGENERATE_THUMBNAIL }); + expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.GENERATE_THUMBNAILS, data: { id: 'asset-1' } }]); }); it('should run the transcode video', async () => { mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); + await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.TRANSCODE_VIDEO }); + expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.VIDEO_CONVERSION, data: { id: 'asset-1' } }]); }); }); diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index df66d405b7..56b7f7743c 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -25,7 +25,6 @@ import { AssetStatus, JobName, JobStatus, Permission, QueueName } from 'src/enum import { BaseService } from 'src/services/base.service'; import { ISidecarWriteJob, JobItem, JobOf } from 'src/types'; import { getAssetFiles, getMyPartnerIds, onAfterUnlink, onBeforeLink, onBeforeUnlink } from 'src/utils/asset.util'; -import { usePagination } from 'src/utils/pagination'; @Injectable() export class AssetService extends BaseService { @@ -156,22 +155,30 @@ export class AssetService extends BaseService { const trashedBefore = DateTime.now() .minus(Duration.fromObject({ days: trashedDays })) .toJSDate(); - const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => - this.assetRepository.getAll(pagination, { trashedBefore }), - ); - for await (const assets of assetPagination) { - await this.jobRepository.queueAll( - assets.map((asset) => ({ - name: JobName.ASSET_DELETION, - data: { - id: asset.id, - deleteOnDisk: !asset.isOffline, - }, - })), - ); + let chunk: Array<{ id: string; isOffline: boolean }> = []; + const queueChunk = async () => { + if (chunk.length > 0) { + await this.jobRepository.queueAll( + chunk.map(({ id, isOffline }) => ({ + name: JobName.ASSET_DELETION, + data: { id, deleteOnDisk: !isOffline }, + })), + ); + chunk = []; + } + }; + + const assets = this.assetRepository.streamDeletedAssets(trashedBefore); + for await (const asset of assets) { + chunk.push(asset); + if (chunk.length >= JOBS_ASSET_PAGINATION_SIZE) { + await queueChunk(); + } } + await queueChunk(); + return JobStatus.SUCCESS; } diff --git a/server/src/services/audit.service.spec.ts b/server/src/services/audit.service.spec.ts index f5e588cdd0..6ef139f506 100644 --- a/server/src/services/audit.service.spec.ts +++ b/server/src/services/audit.service.spec.ts @@ -18,7 +18,10 @@ describe(AuditService.name, () => { describe('handleCleanup', () => { it('should delete old audit entries', async () => { + mocks.audit.removeBefore.mockResolvedValue(); + await expect(sut.handleCleanup()).resolves.toBe(JobStatus.SUCCESS); + expect(mocks.audit.removeBefore).toHaveBeenCalledWith(expect.any(Date)); }); }); diff --git a/server/src/services/auth.service.spec.ts b/server/src/services/auth.service.spec.ts index 9fb1af128e..b1bd3332bf 100644 --- a/server/src/services/auth.service.spec.ts +++ b/server/src/services/auth.service.spec.ts @@ -4,12 +4,11 @@ import { UserMetadataEntity } from 'src/entities/user-metadata.entity'; import { UserEntity } from 'src/entities/user.entity'; import { AuthType, Permission } from 'src/enum'; import { AuthService } from 'src/services/auth.service'; -import { keyStub } from 'test/fixtures/api-key.stub'; -import { authStub } from 'test/fixtures/auth.stub'; import { sessionStub } from 'test/fixtures/session.stub'; import { sharedLinkStub } from 'test/fixtures/shared-link.stub'; import { systemConfigStub } from 'test/fixtures/system-config.stub'; import { userStub } from 'test/fixtures/user.stub'; +import { factory } from 'test/small.factory'; import { newTestService, ServiceMocks } from 'test/utils'; const oauthResponse = { @@ -66,7 +65,10 @@ describe('AuthService', () => { describe('onBootstrap', () => { it('should init the repo', () => { + mocks.oauth.init.mockResolvedValue(); + sut.onBootstrap(); + expect(mocks.oauth.init).toHaveBeenCalled(); }); }); @@ -74,24 +76,30 @@ describe('AuthService', () => { describe('login', () => { it('should throw an error if password login is disabled', async () => { mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.disabled); + await expect(sut.login(fixtures.login, loginDetails)).rejects.toBeInstanceOf(UnauthorizedException); }); it('should check the user exists', async () => { mocks.user.getByEmail.mockResolvedValue(void 0); + await expect(sut.login(fixtures.login, loginDetails)).rejects.toBeInstanceOf(UnauthorizedException); + expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1); }); it('should check the user has a password', async () => { mocks.user.getByEmail.mockResolvedValue({} as UserEntity); + await expect(sut.login(fixtures.login, loginDetails)).rejects.toBeInstanceOf(UnauthorizedException); + expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1); }); it('should successfully log the user in', async () => { mocks.user.getByEmail.mockResolvedValue(userStub.user1); mocks.session.create.mockResolvedValue(sessionStub.valid); + await expect(sut.login(fixtures.login, loginDetails)).resolves.toEqual({ accessToken: 'cmFuZG9tLWJ5dGVz', userId: 'user-id', @@ -101,6 +109,7 @@ describe('AuthService', () => { isAdmin: false, shouldChangePassword: false, }); + expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1); }); }); @@ -160,8 +169,10 @@ describe('AuthService', () => { describe('logout', () => { it('should return the end session endpoint', async () => { + const auth = factory.auth(); + mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled); - const auth = { user: { id: '123' } } as AuthDto; + await expect(sut.logout(auth, AuthType.OAUTH)).resolves.toEqual({ successful: true, redirectUri: 'http://end-session-endpoint', @@ -169,7 +180,7 @@ describe('AuthService', () => { }); it('should return the default redirect', async () => { - const auth = { user: { id: '123' } } as AuthDto; + const auth = factory.auth(); await expect(sut.logout(auth, AuthType.PASSWORD)).resolves.toEqual({ successful: true, @@ -179,6 +190,7 @@ describe('AuthService', () => { it('should delete the access token', async () => { const auth = { user: { id: '123' }, session: { id: 'token123' } } as AuthDto; + mocks.session.delete.mockResolvedValue(); await expect(sut.logout(auth, AuthType.PASSWORD)).resolves.toEqual({ successful: true, @@ -204,7 +216,9 @@ describe('AuthService', () => { it('should only allow one admin', async () => { mocks.user.getAdmin.mockResolvedValue({} as UserEntity); + await expect(sut.adminSignUp(dto)).rejects.toBeInstanceOf(BadRequestException); + expect(mocks.user.getAdmin).toHaveBeenCalled(); }); @@ -216,6 +230,7 @@ describe('AuthService', () => { createdAt: new Date('2021-01-01'), metadata: [] as UserMetadataEntity[], } as UserEntity); + await expect(sut.adminSignUp(dto)).resolves.toMatchObject({ avatarColor: expect.any(String), id: 'admin', @@ -223,6 +238,7 @@ describe('AuthService', () => { email: 'test@immich.com', name: 'immich admin', }); + expect(mocks.user.getAdmin).toHaveBeenCalled(); expect(mocks.user.create).toHaveBeenCalled(); }); @@ -242,6 +258,7 @@ describe('AuthService', () => { it('should validate using authorization header', async () => { mocks.user.get.mockResolvedValue(userStub.user1); mocks.session.getByToken.mockResolvedValue(sessionStub.valid as any); + await expect( sut.authenticate({ headers: { authorization: 'Bearer auth_token' }, @@ -257,6 +274,8 @@ describe('AuthService', () => { describe('validate - shared key', () => { it('should not accept a non-existent key', async () => { + mocks.sharedLink.getByKey.mockResolvedValue(void 0); + await expect( sut.authenticate({ headers: { 'x-immich-share-key': 'key' }, @@ -268,6 +287,7 @@ describe('AuthService', () => { it('should not accept an expired key', async () => { mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.expired); + await expect( sut.authenticate({ headers: { 'x-immich-share-key': 'key' }, @@ -279,6 +299,7 @@ describe('AuthService', () => { it('should not accept a key on a non-shared route', async () => { mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid); + await expect( sut.authenticate({ headers: { 'x-immich-share-key': 'key' }, @@ -291,6 +312,7 @@ describe('AuthService', () => { it('should not accept a key without a user', async () => { mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.expired); mocks.user.get.mockResolvedValue(void 0); + await expect( sut.authenticate({ headers: { 'x-immich-share-key': 'key' }, @@ -303,6 +325,7 @@ describe('AuthService', () => { it('should accept a base64url key', async () => { mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid); mocks.user.get.mockResolvedValue(userStub.admin); + await expect( sut.authenticate({ headers: { 'x-immich-share-key': sharedLinkStub.valid.key.toString('base64url') }, @@ -319,6 +342,7 @@ describe('AuthService', () => { it('should accept a hex key', async () => { mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid); mocks.user.get.mockResolvedValue(userStub.admin); + await expect( sut.authenticate({ headers: { 'x-immich-share-key': sharedLinkStub.valid.key.toString('hex') }, @@ -336,6 +360,7 @@ describe('AuthService', () => { describe('validate - user token', () => { it('should throw if no token is found', async () => { mocks.session.getByToken.mockResolvedValue(void 0); + await expect( sut.authenticate({ headers: { 'x-immich-user-token': 'auth_token' }, @@ -347,6 +372,7 @@ describe('AuthService', () => { it('should return an auth dto', async () => { mocks.session.getByToken.mockResolvedValue(sessionStub.valid as any); + await expect( sut.authenticate({ headers: { cookie: 'immich_access_token=auth_token' }, @@ -361,6 +387,7 @@ describe('AuthService', () => { it('should throw if admin route and not an admin', async () => { mocks.session.getByToken.mockResolvedValue(sessionStub.valid as any); + await expect( sut.authenticate({ headers: { cookie: 'immich_access_token=auth_token' }, @@ -373,6 +400,7 @@ describe('AuthService', () => { it('should update when access time exceeds an hour', async () => { mocks.session.getByToken.mockResolvedValue(sessionStub.inactive as any); mocks.session.update.mockResolvedValue(sessionStub.valid); + await expect( sut.authenticate({ headers: { cookie: 'immich_access_token=auth_token' }, @@ -387,6 +415,7 @@ describe('AuthService', () => { describe('validate - api key', () => { it('should throw an error if no api key is found', async () => { mocks.apiKey.getKey.mockResolvedValue(void 0); + await expect( sut.authenticate({ headers: { 'x-api-key': 'auth_token' }, @@ -398,7 +427,11 @@ describe('AuthService', () => { }); it('should throw an error if api key has insufficient permissions', async () => { - mocks.apiKey.getKey.mockResolvedValue(keyStub.authKey); + const authUser = factory.authUser(); + const authApiKey = factory.authApiKey({ permissions: [] }); + + mocks.apiKey.getKey.mockResolvedValue({ ...authApiKey, user: authUser }); + await expect( sut.authenticate({ headers: { 'x-api-key': 'auth_token' }, @@ -409,14 +442,18 @@ describe('AuthService', () => { }); it('should return an auth dto', async () => { - mocks.apiKey.getKey.mockResolvedValue(keyStub.authKey); + const authUser = factory.authUser(); + const authApiKey = factory.authApiKey({ permissions: [] }); + + mocks.apiKey.getKey.mockResolvedValue({ ...authApiKey, user: authUser }); + await expect( sut.authenticate({ headers: { 'x-api-key': 'auth_token' }, queryParams: {}, metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, }), - ).resolves.toEqual({ user: userStub.admin, apiKey: keyStub.authKey }); + ).resolves.toEqual({ user: authUser, apiKey: expect.objectContaining(authApiKey) }); expect(mocks.apiKey.getKey).toHaveBeenCalledWith('auth_token (hashed)'); }); }); @@ -436,6 +473,7 @@ describe('AuthService', () => { describe('authorize', () => { it('should fail if oauth is disabled', async () => { mocks.systemMetadata.get.mockResolvedValue({ oauth: { enabled: false } }); + await expect(sut.authorize({ redirectUri: 'https://demo.immich.app' })).rejects.toBeInstanceOf( BadRequestException, ); @@ -443,6 +481,7 @@ describe('AuthService', () => { it('should authorize the user', async () => { mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithMobileOverride); + await sut.authorize({ redirectUri: 'https://demo.immich.app' }); }); }); @@ -455,9 +494,11 @@ describe('AuthService', () => { it('should not allow auto registering', async () => { mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled); mocks.user.getByEmail.mockResolvedValue(void 0); + await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).rejects.toBeInstanceOf( BadRequestException, ); + expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1); }); @@ -534,6 +575,7 @@ describe('AuthService', () => { mocks.session.create.mockResolvedValue(sessionStub.valid); await sut.callback({ url }, loginDetails); + expect(mocks.oauth.getProfile).toHaveBeenCalledWith(expect.objectContaining({}), url, 'http://mobile-redirect'); }); } @@ -543,6 +585,7 @@ describe('AuthService', () => { mocks.user.getByEmail.mockResolvedValue(void 0); mocks.user.getAdmin.mockResolvedValue(userStub.user1); mocks.user.create.mockResolvedValue(userStub.user1); + mocks.session.create.mockResolvedValue(factory.session()); await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual( oauthResponse, @@ -557,6 +600,7 @@ describe('AuthService', () => { mocks.user.getAdmin.mockResolvedValue(userStub.user1); mocks.user.create.mockResolvedValue(userStub.user1); mocks.oauth.getProfile.mockResolvedValue({ sub, email, immich_quota: 'abc' }); + mocks.session.create.mockResolvedValue(factory.session()); await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual( oauthResponse, @@ -571,6 +615,7 @@ describe('AuthService', () => { mocks.user.getAdmin.mockResolvedValue(userStub.user1); mocks.user.create.mockResolvedValue(userStub.user1); mocks.oauth.getProfile.mockResolvedValue({ sub, email, immich_quota: -5 }); + mocks.session.create.mockResolvedValue(factory.session()); await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual( oauthResponse, @@ -585,6 +630,7 @@ describe('AuthService', () => { mocks.user.getAdmin.mockResolvedValue(userStub.user1); mocks.user.create.mockResolvedValue(userStub.user1); mocks.oauth.getProfile.mockResolvedValue({ sub, email, immich_quota: 0 }); + mocks.session.create.mockResolvedValue(factory.session()); await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual( oauthResponse, @@ -605,6 +651,7 @@ describe('AuthService', () => { mocks.user.getAdmin.mockResolvedValue(userStub.user1); mocks.user.create.mockResolvedValue(userStub.user1); mocks.oauth.getProfile.mockResolvedValue({ sub, email, immich_quota: 5 }); + mocks.session.create.mockResolvedValue(factory.session()); await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual( oauthResponse, @@ -622,19 +669,27 @@ describe('AuthService', () => { describe('link', () => { it('should link an account', async () => { + const authUser = factory.authUser(); + const authApiKey = factory.authApiKey({ permissions: [] }); + const auth = { user: authUser, apiKey: authApiKey }; + mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled); mocks.user.update.mockResolvedValue(userStub.user1); - await sut.link(authStub.user1, { url: 'http://immich/user-settings?code=abc123' }); + await sut.link(auth, { url: 'http://immich/user-settings?code=abc123' }); - expect(mocks.user.update).toHaveBeenCalledWith(authStub.user1.user.id, { oauthId: sub }); + expect(mocks.user.update).toHaveBeenCalledWith(auth.user.id, { oauthId: sub }); }); it('should not link an already linked oauth.sub', async () => { + const authUser = factory.authUser(); + const authApiKey = factory.authApiKey({ permissions: [] }); + const auth = { user: authUser, apiKey: authApiKey }; + mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled); mocks.user.getByOAuthId.mockResolvedValue({ id: 'other-user' } as UserEntity); - await expect(sut.link(authStub.user1, { url: 'http://immich/user-settings?code=abc123' })).rejects.toBeInstanceOf( + await expect(sut.link(auth, { url: 'http://immich/user-settings?code=abc123' })).rejects.toBeInstanceOf( BadRequestException, ); @@ -644,12 +699,16 @@ describe('AuthService', () => { describe('unlink', () => { it('should unlink an account', async () => { + const authUser = factory.authUser(); + const authApiKey = factory.authApiKey({ permissions: [] }); + const auth = { user: authUser, apiKey: authApiKey }; + mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled); mocks.user.update.mockResolvedValue(userStub.user1); - await sut.unlink(authStub.user1); + await sut.unlink(auth); - expect(mocks.user.update).toHaveBeenCalledWith(authStub.user1.user.id, { oauthId: '' }); + expect(mocks.user.update).toHaveBeenCalledWith(auth.user.id, { oauthId: '' }); }); }); }); diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index 4ed3db8419..235f20e705 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -1,6 +1,6 @@ import { BadRequestException, ForbiddenException, Injectable, UnauthorizedException } from '@nestjs/common'; import { isString } from 'class-validator'; -import cookieParser from 'cookie'; +import { parse } from 'cookie'; import { DateTime } from 'luxon'; import { IncomingHttpHeaders } from 'node:http'; import { LOGIN_URL, MOBILE_REDIRECT, SALT_ROUNDS } from 'src/constants'; @@ -287,7 +287,7 @@ export class AuthService extends BaseService { } private getCookieToken(headers: IncomingHttpHeaders): string | null { - const cookies = cookieParser.parse(headers.cookie || ''); + const cookies = parse(headers.cookie || ''); return cookies[ImmichCookie.ACCESS_TOKEN] || null; } diff --git a/server/src/services/backup.service.spec.ts b/server/src/services/backup.service.spec.ts index 567859ac01..704087ab05 100644 --- a/server/src/services/backup.service.spec.ts +++ b/server/src/services/backup.service.spec.ts @@ -22,6 +22,7 @@ describe(BackupService.name, () => { describe('onBootstrapEvent', () => { it('should init cron job and handle config changes', async () => { mocks.database.tryLock.mockResolvedValue(true); + mocks.cron.create.mockResolvedValue(); await sut.onConfigInit({ newConfig: systemConfigStub.backupEnabled as SystemConfig }); @@ -47,10 +48,14 @@ describe(BackupService.name, () => { describe('onConfigUpdateEvent', () => { beforeEach(async () => { mocks.database.tryLock.mockResolvedValue(true); + mocks.cron.create.mockResolvedValue(); + await sut.onConfigInit({ newConfig: defaults }); }); it('should update cron job if backup is enabled', () => { + mocks.cron.update.mockResolvedValue(); + sut.onConfigUpdate({ oldConfig: defaults, newConfig: { diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index a1720aeb31..f8c995c007 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -17,6 +17,7 @@ import { ConfigRepository } from 'src/repositories/config.repository'; import { CronRepository } from 'src/repositories/cron.repository'; import { CryptoRepository } from 'src/repositories/crypto.repository'; import { DatabaseRepository } from 'src/repositories/database.repository'; +import { DownloadRepository } from 'src/repositories/download.repository'; import { EventRepository } from 'src/repositories/event.repository'; import { JobRepository } from 'src/repositories/job.repository'; import { LibraryRepository } from 'src/repositories/library.repository'; @@ -66,6 +67,7 @@ export class BaseService { protected cronRepository: CronRepository, protected cryptoRepository: CryptoRepository, protected databaseRepository: DatabaseRepository, + protected downloadRepository: DownloadRepository, protected eventRepository: EventRepository, protected jobRepository: JobRepository, protected libraryRepository: LibraryRepository, diff --git a/server/src/services/cli.service.spec.ts b/server/src/services/cli.service.spec.ts index c585142cbf..ce591a7e62 100644 --- a/server/src/services/cli.service.spec.ts +++ b/server/src/services/cli.service.spec.ts @@ -31,6 +31,8 @@ describe(CliService.name, () => { it('should default to a random password', async () => { mocks.user.getAdmin.mockResolvedValue(userStub.admin); + mocks.user.update.mockResolvedValue(userStub.admin); + const ask = vitest.fn().mockImplementation(() => {}); const response = await sut.resetAdminPassword(ask); @@ -45,6 +47,8 @@ describe(CliService.name, () => { it('should use the supplied password', async () => { mocks.user.getAdmin.mockResolvedValue(userStub.admin); + mocks.user.update.mockResolvedValue(userStub.admin); + const ask = vitest.fn().mockResolvedValue('new-password'); const response = await sut.resetAdminPassword(ask); diff --git a/server/src/services/database.service.spec.ts b/server/src/services/database.service.spec.ts index 1314d5327e..4e45ec3ae0 100644 --- a/server/src/services/database.service.spec.ts +++ b/server/src/services/database.service.spec.ts @@ -384,50 +384,4 @@ describe(DatabaseService.name, () => { expect(mocks.database.runMigrations).not.toHaveBeenCalled(); }); }); - - describe('handleConnectionError', () => { - beforeAll(() => { - vi.useFakeTimers(); - }); - - afterAll(() => { - vi.useRealTimers(); - }); - - it('should not override interval', () => { - sut.handleConnectionError(new Error('Error')); - expect(mocks.logger.error).toHaveBeenCalled(); - - sut.handleConnectionError(new Error('foo')); - expect(mocks.logger.error).toHaveBeenCalledTimes(1); - }); - - it('should reconnect when interval elapses', async () => { - mocks.database.reconnect.mockResolvedValue(true); - - sut.handleConnectionError(new Error('error')); - await vi.advanceTimersByTimeAsync(5000); - - expect(mocks.database.reconnect).toHaveBeenCalledTimes(1); - expect(mocks.logger.log).toHaveBeenCalledWith('Database reconnected'); - - await vi.advanceTimersByTimeAsync(5000); - expect(mocks.database.reconnect).toHaveBeenCalledTimes(1); - }); - - it('should try again when reconnection fails', async () => { - mocks.database.reconnect.mockResolvedValueOnce(false); - - sut.handleConnectionError(new Error('error')); - await vi.advanceTimersByTimeAsync(5000); - - expect(mocks.database.reconnect).toHaveBeenCalledTimes(1); - expect(mocks.logger.warn).toHaveBeenCalledWith(expect.stringContaining('Database connection failed')); - - mocks.database.reconnect.mockResolvedValueOnce(true); - await vi.advanceTimersByTimeAsync(5000); - expect(mocks.database.reconnect).toHaveBeenCalledTimes(2); - expect(mocks.logger.log).toHaveBeenCalledWith('Database reconnected'); - }); - }); }); diff --git a/server/src/services/database.service.ts b/server/src/services/database.service.ts index 36f4c09177..d71dc25104 100644 --- a/server/src/services/database.service.ts +++ b/server/src/services/database.service.ts @@ -1,5 +1,4 @@ import { Injectable } from '@nestjs/common'; -import { Duration } from 'luxon'; import semver from 'semver'; import { EXTENSION_NAMES } from 'src/constants'; import { OnEvent } from 'src/decorators'; @@ -54,12 +53,8 @@ const messages = { If ${name} ${installedVersion} is compatible with Immich, please ensure the Postgres instance has this available.`, }; -const RETRY_DURATION = Duration.fromObject({ seconds: 5 }); - @Injectable() export class DatabaseService extends BaseService { - private reconnection?: NodeJS.Timeout; - @OnEvent({ name: 'app.bootstrap', priority: BootstrapEventPriority.DatabaseService }) async onBootstrap() { const version = await this.databaseRepository.getPostgresVersion(); @@ -108,30 +103,9 @@ export class DatabaseService extends BaseService { if (!database.skipMigrations) { await this.databaseRepository.runMigrations(); } - this.databaseRepository.init(); }); } - handleConnectionError(error: Error) { - if (this.reconnection) { - return; - } - - this.logger.error(`Database disconnected: ${error}`); - this.reconnection = setInterval(() => void this.reconnect(), RETRY_DURATION.toMillis()); - } - - private async reconnect() { - const isConnected = await this.databaseRepository.reconnect(); - if (isConnected) { - this.logger.log('Database reconnected'); - clearInterval(this.reconnection); - delete this.reconnection; - } else { - this.logger.warn(`Database connection failed, retrying in ${RETRY_DURATION.toHuman()}`); - } - } - private async createExtension(extension: DatabaseExtension) { try { await this.databaseRepository.createExtension(extension); diff --git a/server/src/services/download.service.spec.ts b/server/src/services/download.service.spec.ts index d9e60dfdb4..7646637093 100644 --- a/server/src/services/download.service.spec.ts +++ b/server/src/services/download.service.spec.ts @@ -1,10 +1,9 @@ import { BadRequestException } from '@nestjs/common'; import { DownloadResponseDto } from 'src/dtos/download.dto'; -import { AssetEntity } from 'src/entities/asset.entity'; import { DownloadService } from 'src/services/download.service'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; -import { newTestService, ServiceMocks } from 'test/utils'; +import { makeStream, newTestService, ServiceMocks } from 'test/utils'; import { Readable } from 'typeorm/platform/PlatformTools.js'; import { vitest } from 'vitest'; @@ -12,7 +11,7 @@ const downloadResponse: DownloadResponseDto = { totalSize: 105_000, archives: [ { - assetIds: ['asset-id', 'asset-id'], + assetIds: ['asset-1', 'asset-2'], size: 105_000, }, ], @@ -172,53 +171,64 @@ describe(DownloadService.name, () => { }); it('should return a list of archives (assetIds)', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2'])); - mocks.asset.getByIds.mockResolvedValue([assetStub.image, assetStub.video]); - const assetIds = ['asset-1', 'asset-2']; + + mocks.user.getMetadata.mockResolvedValue([]); + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(assetIds)); + mocks.downloadRepository.downloadAssetIds.mockReturnValue( + makeStream([ + { id: 'asset-1', livePhotoVideoId: null, size: 100_000 }, + { id: 'asset-2', livePhotoVideoId: null, size: 5000 }, + ]), + ); + await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual(downloadResponse); - expect(mocks.asset.getByIds).toHaveBeenCalledWith(['asset-1', 'asset-2'], { exifInfo: true }); + expect(mocks.downloadRepository.downloadAssetIds).toHaveBeenCalledWith(['asset-1', 'asset-2']); }); it('should return a list of archives (albumId)', async () => { + mocks.user.getMetadata.mockResolvedValue([]); mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-1'])); - mocks.asset.getByAlbumId.mockResolvedValue({ - items: [assetStub.image, assetStub.video], - hasNextPage: false, - }); + mocks.downloadRepository.downloadAlbumId.mockReturnValue( + makeStream([ + { id: 'asset-1', livePhotoVideoId: null, size: 100_000 }, + { id: 'asset-2', livePhotoVideoId: null, size: 5000 }, + ]), + ); await expect(sut.getDownloadInfo(authStub.admin, { albumId: 'album-1' })).resolves.toEqual(downloadResponse); expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['album-1'])); - expect(mocks.asset.getByAlbumId).toHaveBeenCalledWith({ take: 2500, skip: 0 }, 'album-1'); + expect(mocks.downloadRepository.downloadAlbumId).toHaveBeenCalledWith('album-1'); }); it('should return a list of archives (userId)', async () => { - mocks.asset.getByUserId.mockResolvedValue({ - items: [assetStub.image, assetStub.video], - hasNextPage: false, - }); + mocks.user.getMetadata.mockResolvedValue([]); + mocks.downloadRepository.downloadUserId.mockReturnValue( + makeStream([ + { id: 'asset-1', livePhotoVideoId: null, size: 100_000 }, + { id: 'asset-2', livePhotoVideoId: null, size: 5000 }, + ]), + ); await expect(sut.getDownloadInfo(authStub.admin, { userId: authStub.admin.user.id })).resolves.toEqual( downloadResponse, ); - expect(mocks.asset.getByUserId).toHaveBeenCalledWith({ take: 2500, skip: 0 }, authStub.admin.user.id, { - isVisible: true, - }); + expect(mocks.downloadRepository.downloadUserId).toHaveBeenCalledWith(authStub.admin.user.id); }); it('should split archives by size', async () => { - mocks.asset.getByUserId.mockResolvedValue({ - items: [ - { ...assetStub.image, id: 'asset-1' }, - { ...assetStub.video, id: 'asset-2' }, - { ...assetStub.withLocation, id: 'asset-3' }, - { ...assetStub.noWebpPath, id: 'asset-4' }, - ], - hasNextPage: false, - }); + mocks.user.getMetadata.mockResolvedValue([]); + mocks.downloadRepository.downloadUserId.mockReturnValue( + makeStream([ + { id: 'asset-1', livePhotoVideoId: null, size: 5000 }, + { id: 'asset-2', livePhotoVideoId: null, size: 100_000 }, + { id: 'asset-3', livePhotoVideoId: null, size: 23_456 }, + { id: 'asset-4', livePhotoVideoId: null, size: 123_000 }, + ]), + ); await expect( sut.getDownloadInfo(authStub.admin, { @@ -235,49 +245,52 @@ describe(DownloadService.name, () => { }); it('should include the video portion of a live photo', async () => { - const assetIds = [assetStub.livePhotoStillAsset.id]; - const assets = [assetStub.livePhotoStillAsset, assetStub.livePhotoMotionAsset]; + const assetIds = ['asset-1', 'asset-2']; mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(assetIds)); - mocks.asset.getByIds.mockImplementation( - (ids) => - Promise.resolve( - ids.map((id) => assets.find((asset) => asset.id === id)).filter((asset) => !!asset), - ) as Promise, + mocks.user.getMetadata.mockResolvedValue([]); + mocks.downloadRepository.downloadAssetIds.mockReturnValue( + makeStream([ + { id: 'asset-1', livePhotoVideoId: 'asset-3', size: 5000 }, + { id: 'asset-2', livePhotoVideoId: 'asset-4', size: 100_000 }, + ]), + ); + mocks.downloadRepository.downloadMotionAssetIds.mockReturnValue( + makeStream([ + { id: 'asset-3', livePhotoVideoId: null, size: 23_456, originalPath: '/path/to/file.mp4' }, + { id: 'asset-4', livePhotoVideoId: null, size: 123_000, originalPath: '/path/to/file.mp4' }, + ]), ); - await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual({ - totalSize: 125_000, + await expect(sut.getDownloadInfo(authStub.admin, { assetIds, archiveSize: 30_000 })).resolves.toEqual({ + totalSize: 251_456, archives: [ - { - assetIds: [assetStub.livePhotoStillAsset.id, assetStub.livePhotoMotionAsset.id], - size: 125_000, - }, + { assetIds: ['asset-1', 'asset-2'], size: 105_000 }, + { assetIds: ['asset-3', 'asset-4'], size: 146_456 }, ], }); }); it('should skip the video portion of an android live photo by default', async () => { - const assetIds = [assetStub.livePhotoStillAsset.id]; - const assets = [ - assetStub.livePhotoStillAsset, - { ...assetStub.livePhotoMotionAsset, originalPath: 'upload/encoded-video/uuid-MP.mp4' }, - ]; + const assetIds = ['asset-1']; mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(assetIds)); - mocks.asset.getByIds.mockImplementation( - (ids) => - Promise.resolve( - ids.map((id) => assets.find((asset) => asset.id === id)).filter((asset) => !!asset), - ) as Promise, + mocks.user.getMetadata.mockResolvedValue([]); + mocks.downloadRepository.downloadAssetIds.mockReturnValue( + makeStream([{ id: 'asset-1', livePhotoVideoId: 'asset-3', size: 5000 }]), + ); + mocks.downloadRepository.downloadMotionAssetIds.mockReturnValue( + makeStream([ + { id: 'asset-2', livePhotoVideoId: null, size: 23_456, originalPath: 'upload/encoded-video/uuid-MP.mp4' }, + ]), ); await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual({ - totalSize: 25_000, + totalSize: 5000, archives: [ { - assetIds: [assetStub.livePhotoStillAsset.id], - size: 25_000, + assetIds: ['asset-1'], + size: 5000, }, ], }); diff --git a/server/src/services/download.service.ts b/server/src/services/download.service.ts index 8b18bd0a07..cb664aea32 100644 --- a/server/src/services/download.service.ts +++ b/server/src/services/download.service.ts @@ -4,53 +4,72 @@ import { StorageCore } from 'src/cores/storage.core'; import { AssetIdsDto } from 'src/dtos/asset.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { DownloadArchiveInfo, DownloadInfoDto, DownloadResponseDto } from 'src/dtos/download.dto'; -import { AssetEntity } from 'src/entities/asset.entity'; import { Permission } from 'src/enum'; import { ImmichReadStream } from 'src/repositories/storage.repository'; import { BaseService } from 'src/services/base.service'; import { HumanReadableSize } from 'src/utils/bytes'; -import { usePagination } from 'src/utils/pagination'; import { getPreferences } from 'src/utils/preferences'; @Injectable() export class DownloadService extends BaseService { async getDownloadInfo(auth: AuthDto, dto: DownloadInfoDto): Promise { + let assets; + + if (dto.assetIds) { + const assetIds = dto.assetIds; + await this.requireAccess({ auth, permission: Permission.ASSET_DOWNLOAD, ids: assetIds }); + assets = this.downloadRepository.downloadAssetIds(assetIds); + } else if (dto.albumId) { + const albumId = dto.albumId; + await this.requireAccess({ auth, permission: Permission.ALBUM_DOWNLOAD, ids: [albumId] }); + assets = this.downloadRepository.downloadAlbumId(albumId); + } else if (dto.userId) { + const userId = dto.userId; + await this.requireAccess({ auth, permission: Permission.TIMELINE_DOWNLOAD, ids: [userId] }); + assets = this.downloadRepository.downloadUserId(userId); + } else { + throw new BadRequestException('assetIds, albumId, or userId is required'); + } + const targetSize = dto.archiveSize || HumanReadableSize.GiB * 4; + const metadata = await this.userRepository.getMetadata(auth.user.id); + const preferences = getPreferences(auth.user.email, metadata); + const motionIds = new Set(); const archives: DownloadArchiveInfo[] = []; let archive: DownloadArchiveInfo = { size: 0, assetIds: [] }; - const metadata = await this.userRepository.getMetadata(auth.user.id); - const preferences = getPreferences(auth.user.email, metadata); + const addToArchive = ({ id, size }: { id: string; size: number | null }) => { + archive.assetIds.push(id); + archive.size += Number(size || 0); - const assetPagination = await this.getDownloadAssets(auth, dto); - for await (const assets of assetPagination) { - // motion part of live photos - const motionIds = assets.map((asset) => asset.livePhotoVideoId).filter((id): id is string => !!id); - if (motionIds.length > 0) { - const motionAssets = await this.assetRepository.getByIds(motionIds, { exifInfo: true }); - for (const motionAsset of motionAssets) { - if ( - !StorageCore.isAndroidMotionPath(motionAsset.originalPath) || - preferences.download.includeEmbeddedVideos - ) { - assets.push(motionAsset); - } - } - } - - for (const asset of assets) { - archive.size += Number(asset.exifInfo?.fileSizeInByte || 0); - archive.assetIds.push(asset.id); - - if (archive.size > targetSize) { - archives.push(archive); - archive = { size: 0, assetIds: [] }; - } - } - - if (archive.assetIds.length > 0) { + if (archive.size > targetSize) { archives.push(archive); + archive = { size: 0, assetIds: [] }; } + }; + + for await (const asset of assets) { + // motion part of live photos + if (asset.livePhotoVideoId) { + motionIds.add(asset.livePhotoVideoId); + } + + addToArchive(asset); + } + + if (motionIds.size > 0) { + const motionAssets = this.downloadRepository.downloadMotionAssetIds([...motionIds]); + for await (const motionAsset of motionAssets) { + if (StorageCore.isAndroidMotionPath(motionAsset.originalPath) && !preferences.download.includeEmbeddedVideos) { + continue; + } + + addToArchive(motionAsset); + } + } + + if (archive.assetIds.length > 0) { + archives.push(archive); } let totalSize = 0; @@ -99,31 +118,4 @@ export class DownloadService extends BaseService { return { stream: zip.stream }; } - - private async getDownloadAssets(auth: AuthDto, dto: DownloadInfoDto): Promise> { - const PAGINATION_SIZE = 2500; - - if (dto.assetIds) { - const assetIds = dto.assetIds; - await this.requireAccess({ auth, permission: Permission.ASSET_DOWNLOAD, ids: assetIds }); - const assets = await this.assetRepository.getByIds(assetIds, { exifInfo: true }); - return usePagination(PAGINATION_SIZE, () => ({ hasNextPage: false, items: assets })); - } - - if (dto.albumId) { - const albumId = dto.albumId; - await this.requireAccess({ auth, permission: Permission.ALBUM_DOWNLOAD, ids: [albumId] }); - return usePagination(PAGINATION_SIZE, (pagination) => this.assetRepository.getByAlbumId(pagination, albumId)); - } - - if (dto.userId) { - const userId = dto.userId; - await this.requireAccess({ auth, permission: Permission.TIMELINE_DOWNLOAD, ids: [userId] }); - return usePagination(PAGINATION_SIZE, (pagination) => - this.assetRepository.getByUserId(pagination, userId, { isVisible: true }), - ); - } - - throw new BadRequestException('assetIds, albumId, or userId is required'); - } } diff --git a/server/src/services/index.ts b/server/src/services/index.ts index 0dd8bdae66..b214dd14f6 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -1,6 +1,6 @@ import { ActivityService } from 'src/services/activity.service'; import { AlbumService } from 'src/services/album.service'; -import { APIKeyService } from 'src/services/api-key.service'; +import { ApiKeyService } from 'src/services/api-key.service'; import { ApiService } from 'src/services/api.service'; import { AssetMediaService } from 'src/services/asset-media.service'; import { AssetService } from 'src/services/asset.service'; @@ -40,7 +40,7 @@ import { VersionService } from 'src/services/version.service'; import { ViewService } from 'src/services/view.service'; export const services = [ - APIKeyService, + ApiKeyService, ActivityService, AlbumService, ApiService, diff --git a/server/src/services/job.service.spec.ts b/server/src/services/job.service.spec.ts index 42010d598c..134a86b69f 100644 --- a/server/src/services/job.service.spec.ts +++ b/server/src/services/job.service.spec.ts @@ -195,6 +195,14 @@ describe(JobService.name, () => { expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QUEUE_FACIAL_RECOGNITION, data: { force: false } }); }); + it('should handle a start backup database command', async () => { + mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); + + await sut.handleCommand(QueueName.BACKUP_DATABASE, { command: JobCommand.START, force: false }); + + expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.BACKUP_DATABASE, data: { force: false } }); + }); + it('should throw a bad request when an invalid queue is used', async () => { mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index 342aec7a7a..2f180edd40 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -39,6 +39,10 @@ const asJobItem = (dto: JobCreateDto): JobItem => { return { name: JobName.MEMORIES_CREATE }; } + case ManualJobName.BACKUP_DATABASE: { + return { name: JobName.BACKUP_DATABASE }; + } + default: { throw new BadRequestException('Invalid job name'); } diff --git a/server/src/services/library.service.spec.ts b/server/src/services/library.service.spec.ts index c869f803f0..aef02b7244 100644 --- a/server/src/services/library.service.spec.ts +++ b/server/src/services/library.service.spec.ts @@ -3,17 +3,15 @@ import { Stats } from 'node:fs'; import { defaults, SystemConfig } from 'src/config'; import { JOBS_LIBRARY_PAGINATION_SIZE } from 'src/constants'; import { mapLibrary } from 'src/dtos/library.dto'; -import { UserEntity } from 'src/entities/user.entity'; import { AssetType, ImmichWorker, JobName, JobStatus } from 'src/enum'; import { LibraryService } from 'src/services/library.service'; -import { ILibraryAssetJob, ILibraryFileJob } from 'src/types'; +import { ILibraryBulkIdsJob, ILibraryFileJob } from 'src/types'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; -import { libraryStub } from 'test/fixtures/library.stub'; import { systemConfigStub } from 'test/fixtures/system-config.stub'; -import { userStub } from 'test/fixtures/user.stub'; import { makeMockWatcher } from 'test/repositories/storage.repository.mock'; -import { newTestService, ServiceMocks } from 'test/utils'; +import { factory, newUuid } from 'test/small.factory'; +import { makeStream, newTestService, ServiceMocks } from 'test/utils'; import { vitest } from 'vitest'; async function* mockWalk() { @@ -22,10 +20,11 @@ async function* mockWalk() { describe(LibraryService.name, () => { let sut: LibraryService; + let mocks: ServiceMocks; beforeEach(() => { - ({ sut, mocks } = newTestService(LibraryService)); + ({ sut, mocks } = newTestService(LibraryService, {})); mocks.database.tryLock.mockResolvedValue(true); mocks.config.getWorker.mockReturnValue(ImmichWorker.MICROSERVICES); @@ -37,6 +36,9 @@ describe(LibraryService.name, () => { describe('onConfigInit', () => { it('should init cron job and handle config changes', async () => { + mocks.cron.create.mockResolvedValue(); + mocks.cron.update.mockResolvedValue(); + await sut.onConfigInit({ newConfig: defaults }); expect(mocks.cron.create).toHaveBeenCalled(); @@ -58,30 +60,26 @@ describe(LibraryService.name, () => { }); it('should initialize watcher for all external libraries', async () => { - mocks.library.getAll.mockResolvedValue([ - libraryStub.externalLibraryWithImportPaths1, - libraryStub.externalLibraryWithImportPaths2, - ]); + const library1 = factory.library({ importPaths: ['/foo', '/bar'] }); + const library2 = factory.library({ importPaths: ['/xyz', '/asdf'] }); + + mocks.library.getAll.mockResolvedValue([library1, library2]); mocks.library.get.mockImplementation((id) => - Promise.resolve( - [libraryStub.externalLibraryWithImportPaths1, libraryStub.externalLibraryWithImportPaths2].find( - (library) => library.id === id, - ), - ), + Promise.resolve([library1, library2].find((library) => library.id === id)), ); + mocks.cron.create.mockResolvedValue(); await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); expect(mocks.storage.watch.mock.calls).toEqual( - expect.arrayContaining([ - (libraryStub.externalLibrary1.importPaths, expect.anything()), - (libraryStub.externalLibrary2.importPaths, expect.anything()), - ]), + expect.arrayContaining([(library1.importPaths, expect.anything()), (library2.importPaths, expect.anything())]), ); }); it('should not initialize watcher when watching is disabled', async () => { + mocks.cron.create.mockResolvedValue(); + await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchDisabled as SystemConfig }); expect(mocks.storage.watch).not.toHaveBeenCalled(); @@ -107,6 +105,8 @@ describe(LibraryService.name, () => { describe('onConfigUpdateEvent', () => { beforeEach(async () => { mocks.database.tryLock.mockResolvedValue(true); + mocks.cron.create.mockResolvedValue(); + await sut.onConfigInit({ newConfig: defaults }); }); @@ -119,6 +119,9 @@ describe(LibraryService.name, () => { it('should update cron job and enable watching', async () => { mocks.library.getAll.mockResolvedValue([]); + mocks.cron.create.mockResolvedValue(); + mocks.cron.update.mockResolvedValue(); + await sut.onConfigUpdate({ newConfig: systemConfigStub.libraryScanAndWatch as SystemConfig, oldConfig: defaults, @@ -133,6 +136,9 @@ describe(LibraryService.name, () => { it('should update cron job and disable watching', async () => { mocks.library.getAll.mockResolvedValue([]); + mocks.cron.create.mockResolvedValue(); + mocks.cron.update.mockResolvedValue(); + await sut.onConfigUpdate({ newConfig: systemConfigStub.libraryScanAndWatch as SystemConfig, oldConfig: defaults, @@ -152,30 +158,36 @@ describe(LibraryService.name, () => { describe('handleQueueSyncFiles', () => { it('should queue refresh of a new asset', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + mocks.library.get.mockResolvedValue(library); mocks.storage.walk.mockImplementation(mockWalk); + mocks.storage.stat.mockResolvedValue({ isDirectory: () => true } as Stats); + mocks.storage.checkFileExists.mockResolvedValue(true); + mocks.asset.filterNewExternalAssetPaths.mockResolvedValue(['/data/user1/photo.jpg']); - await sut.handleQueueSyncFiles({ id: libraryStub.externalLibrary1.id }); + await sut.handleQueueSyncFiles({ id: library.id }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.LIBRARY_SYNC_FILE, - data: { - id: libraryStub.externalLibrary1.id, - ownerId: libraryStub.externalLibrary1.owner.id, - assetPath: '/data/user1/photo.jpg', - }, + expect(mocks.job.queue).toHaveBeenCalledWith({ + name: JobName.LIBRARY_SYNC_FILES, + data: { + libraryId: library.id, + paths: ['/data/user1/photo.jpg'], + progressCounter: 1, }, - ]); + }); }); - it("should fail when library can't be found", async () => { - await expect(sut.handleQueueSyncFiles({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SKIPPED); + it('should fail when library is not found', async () => { + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + await expect(sut.handleQueueSyncFiles({ id: library.id })).resolves.toBe(JobStatus.SKIPPED); }); it('should ignore import paths that do not exist', async () => { + const library = factory.library({ importPaths: ['/foo', '/bar'] }); mocks.storage.stat.mockImplementation((path): Promise => { - if (path === libraryStub.externalLibraryWithImportPaths1.importPaths[0]) { + if (path === library.importPaths[0]) { const error = { code: 'ENOENT' } as any; throw error; } @@ -186,12 +198,12 @@ describe(LibraryService.name, () => { mocks.storage.checkFileExists.mockResolvedValue(true); - mocks.library.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); + mocks.library.get.mockResolvedValue(library); - await sut.handleQueueSyncFiles({ id: libraryStub.externalLibraryWithImportPaths1.id }); + await sut.handleQueueSyncFiles({ id: library.id }); expect(mocks.storage.walk).toHaveBeenCalledWith({ - pathsToCrawl: [libraryStub.externalLibraryWithImportPaths1.importPaths[1]], + pathsToCrawl: [library.importPaths[1]], exclusionPatterns: [], includeHidden: false, take: JOBS_LIBRARY_PAGINATION_SIZE, @@ -199,142 +211,288 @@ describe(LibraryService.name, () => { }); }); - describe('handleQueueRemoveDeleted', () => { - it('should queue online check of existing assets', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); - mocks.storage.walk.mockImplementation(async function* generator() {}); - mocks.asset.getAll.mockResolvedValue({ items: [assetStub.external], hasNextPage: false }); + describe('handleQueueSyncFiles', () => { + it('should queue refresh of a new asset', async () => { + const library = factory.library({ importPaths: ['/foo', '/bar'] }); - await sut.handleQueueSyncAssets({ id: libraryStub.externalLibrary1.id }); + mocks.library.get.mockResolvedValue(library); + mocks.storage.walk.mockImplementation(mockWalk); + mocks.storage.stat.mockResolvedValue({ isDirectory: () => true } as Stats); + mocks.storage.checkFileExists.mockResolvedValue(true); + mocks.asset.filterNewExternalAssetPaths.mockResolvedValue(['/data/user1/photo.jpg']); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.LIBRARY_SYNC_ASSET, - data: { - id: assetStub.external.id, - importPaths: libraryStub.externalLibrary1.importPaths, - exclusionPatterns: [], - }, + await sut.handleQueueSyncFiles({ id: library.id }); + + expect(mocks.job.queue).toHaveBeenCalledWith({ + name: JobName.LIBRARY_SYNC_FILES, + data: { + libraryId: library.id, + paths: ['/data/user1/photo.jpg'], + progressCounter: 1, }, - ]); + }); }); it("should fail when library can't be found", async () => { - await expect(sut.handleQueueSyncAssets({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SKIPPED); + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + await expect(sut.handleQueueSyncFiles({ id: library.id })).resolves.toBe(JobStatus.SKIPPED); + }); + + it('should ignore import paths that do not exist', async () => { + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + mocks.storage.stat.mockImplementation((path): Promise => { + if (path === library.importPaths[0]) { + const error = { code: 'ENOENT' } as any; + throw error; + } + return Promise.resolve({ + isDirectory: () => true, + } as Stats); + }); + + mocks.storage.checkFileExists.mockResolvedValue(true); + + mocks.library.get.mockResolvedValue(library); + + await sut.handleQueueSyncFiles({ id: library.id }); + + expect(mocks.storage.walk).toHaveBeenCalledWith({ + pathsToCrawl: [library.importPaths[1]], + exclusionPatterns: [], + includeHidden: false, + take: JOBS_LIBRARY_PAGINATION_SIZE, + }); }); }); - describe('handleSyncAsset', () => { - it('should skip missing assets', async () => { - const mockAssetJob: ILibraryAssetJob = { - id: assetStub.external.id, - importPaths: ['/'], - exclusionPatterns: [], - }; + describe('handleQueueSyncAssets', () => { + it('should call the offline check', async () => { + const library = factory.library(); - await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SKIPPED); + mocks.library.get.mockResolvedValue(library); + mocks.storage.walk.mockImplementation(async function* generator() {}); + mocks.asset.getAll.mockResolvedValue({ items: [assetStub.external], hasNextPage: false }); + mocks.asset.getLibraryAssetCount.mockResolvedValue(1); + mocks.asset.detectOfflineExternalAssets.mockResolvedValue({ numUpdatedRows: BigInt(1) }); - expect(mocks.asset.remove).not.toHaveBeenCalled(); + const response = await sut.handleQueueSyncAssets({ id: library.id }); + + expect(response).toBe(JobStatus.SUCCESS); + expect(mocks.asset.detectOfflineExternalAssets).toHaveBeenCalledWith( + library.id, + library.importPaths, + library.exclusionPatterns, + ); }); + it('should skip an empty library', async () => { + const library = factory.library(); + + mocks.library.get.mockResolvedValue(library); + mocks.storage.walk.mockImplementation(async function* generator() {}); + mocks.asset.getAll.mockResolvedValue({ items: [assetStub.external], hasNextPage: false }); + mocks.asset.getLibraryAssetCount.mockResolvedValue(0); + mocks.asset.detectOfflineExternalAssets.mockResolvedValue({ numUpdatedRows: BigInt(1) }); + + const response = await sut.handleQueueSyncAssets({ id: library.id }); + + expect(response).toBe(JobStatus.SUCCESS); + expect(mocks.asset.detectOfflineExternalAssets).not.toHaveBeenCalled(); + }); + + it('should queue asset sync', async () => { + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + mocks.library.get.mockResolvedValue(library); + mocks.storage.walk.mockImplementation(async function* generator() {}); + mocks.library.streamAssetIds.mockReturnValue(makeStream([assetStub.external])); + mocks.asset.getLibraryAssetCount.mockResolvedValue(1); + mocks.asset.detectOfflineExternalAssets.mockResolvedValue({ numUpdatedRows: BigInt(0) }); + mocks.library.streamAssetIds.mockReturnValue(makeStream([assetStub.external])); + + const response = await sut.handleQueueSyncAssets({ id: library.id }); + + expect(mocks.job.queue).toBeCalledWith({ + name: JobName.LIBRARY_SYNC_ASSETS, + data: { + libraryId: library.id, + importPaths: library.importPaths, + exclusionPatterns: library.exclusionPatterns, + assetIds: [assetStub.external.id], + progressCounter: 1, + totalAssets: 1, + }, + }); + + expect(response).toBe(JobStatus.SUCCESS); + expect(mocks.asset.detectOfflineExternalAssets).toHaveBeenCalledWith( + library.id, + library.importPaths, + library.exclusionPatterns, + ); + }); + + it("should fail if library can't be found", async () => { + await expect(sut.handleQueueSyncAssets({ id: newUuid() })).resolves.toBe(JobStatus.SKIPPED); + }); + }); + + describe('handleSyncAssets', () => { it('should offline assets no longer on disk', async () => { - const mockAssetJob: ILibraryAssetJob = { - id: assetStub.external.id, + const mockAssetJob: ILibraryBulkIdsJob = { + assetIds: [assetStub.external.id], + libraryId: newUuid(), importPaths: ['/'], exclusionPatterns: [], + totalAssets: 1, + progressCounter: 0, }; - mocks.asset.getById.mockResolvedValue(assetStub.external); + mocks.asset.getByIds.mockResolvedValue([assetStub.external]); mocks.storage.stat.mockRejectedValue(new Error('ENOENT, no such file or directory')); - await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); + await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); expect(mocks.asset.updateAll).toHaveBeenCalledWith([assetStub.external.id], { isOffline: true, - deletedAt: expect.any(Date), + deletedAt: expect.anything(), }); }); - it('should offline assets matching an exclusion pattern', async () => { - const mockAssetJob: ILibraryAssetJob = { - id: assetStub.external.id, - importPaths: ['/'], - exclusionPatterns: ['**/user1/**'], - }; - - mocks.asset.getById.mockResolvedValue(assetStub.external); - - await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); - expect(mocks.asset.updateAll).toHaveBeenCalledWith([assetStub.external.id], { - isOffline: true, - deletedAt: expect.any(Date), - }); - }); - - it('should set assets outside of import paths as offline', async () => { - const mockAssetJob: ILibraryAssetJob = { - id: assetStub.external.id, + it('should set assets deleted from disk as offline', async () => { + const mockAssetJob: ILibraryBulkIdsJob = { + assetIds: [assetStub.external.id], + libraryId: newUuid(), importPaths: ['/data/user2'], exclusionPatterns: [], + totalAssets: 1, + progressCounter: 0, }; - mocks.asset.getById.mockResolvedValue(assetStub.external); - mocks.storage.checkFileExists.mockResolvedValue(true); + mocks.asset.getByIds.mockResolvedValue([assetStub.external]); + mocks.storage.stat.mockRejectedValue(new Error('Could not read file')); - await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); + await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); expect(mocks.asset.updateAll).toHaveBeenCalledWith([assetStub.external.id], { isOffline: true, - deletedAt: expect.any(Date), + deletedAt: expect.anything(), }); }); - it('should do nothing with online assets', async () => { - const mockAssetJob: ILibraryAssetJob = { - id: assetStub.external.id, - importPaths: ['/'], + it('should do nothing with offline assets deleted from disk', async () => { + const mockAssetJob: ILibraryBulkIdsJob = { + assetIds: [assetStub.trashedOffline.id], + libraryId: newUuid(), + importPaths: ['/data/user2'], exclusionPatterns: [], + totalAssets: 1, + progressCounter: 0, }; - mocks.asset.getById.mockResolvedValue(assetStub.external); - mocks.storage.stat.mockResolvedValue({ mtime: assetStub.external.fileModifiedAt } as Stats); + mocks.asset.getByIds.mockResolvedValue([assetStub.trashedOffline]); + mocks.storage.stat.mockRejectedValue(new Error('Could not read file')); - await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); + await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); expect(mocks.asset.updateAll).not.toHaveBeenCalled(); }); it('should un-trash an asset previously marked as offline', async () => { - const mockAssetJob: ILibraryAssetJob = { - id: assetStub.external.id, - importPaths: ['/'], + const mockAssetJob: ILibraryBulkIdsJob = { + assetIds: [assetStub.trashedOffline.id], + libraryId: newUuid(), + importPaths: ['/original/'], exclusionPatterns: [], + totalAssets: 1, + progressCounter: 0, }; - mocks.asset.getById.mockResolvedValue(assetStub.trashedOffline); - mocks.storage.stat.mockResolvedValue({ mtime: assetStub.trashedOffline.fileModifiedAt } as Stats); + mocks.asset.getByIds.mockResolvedValue([assetStub.trashedOffline]); + mocks.storage.stat.mockResolvedValue({ mtime: assetStub.external.fileModifiedAt } as Stats); - await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); + await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); - expect(mocks.asset.updateAll).toHaveBeenCalledWith([assetStub.trashedOffline.id], { - deletedAt: null, - fileModifiedAt: assetStub.trashedOffline.fileModifiedAt, + expect(mocks.asset.updateAll).toHaveBeenCalledWith([assetStub.external.id], { isOffline: false, - originalFileName: 'path.jpg', + deletedAt: null, }); }); - it('should not touch fileCreatedAt when un-trashing an asset previously marked as offline', async () => { - const mockAssetJob: ILibraryAssetJob = { - id: assetStub.external.id, - importPaths: ['/'], - exclusionPatterns: [], + it('should do nothing with offline asset if covered by exclusion pattern', async () => { + const mockAssetJob: ILibraryBulkIdsJob = { + assetIds: [assetStub.trashedOffline.id], + libraryId: newUuid(), + importPaths: ['/original/'], + exclusionPatterns: ['**/path.jpg'], + totalAssets: 1, + progressCounter: 0, }; - mocks.asset.getById.mockResolvedValue(assetStub.trashedOffline); + mocks.asset.getByIds.mockResolvedValue([assetStub.trashedOffline]); + mocks.storage.stat.mockResolvedValue({ mtime: assetStub.external.fileModifiedAt } as Stats); + + await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); + + expect(mocks.asset.updateAll).not.toHaveBeenCalled(); + + expect(mocks.job.queueAll).not.toHaveBeenCalled(); + }); + + it('should do nothing with offline asset if not in import path', async () => { + const mockAssetJob: ILibraryBulkIdsJob = { + assetIds: [assetStub.trashedOffline.id], + libraryId: newUuid(), + importPaths: ['/import/'], + exclusionPatterns: [], + totalAssets: 1, + progressCounter: 0, + }; + + mocks.asset.getByIds.mockResolvedValue([assetStub.trashedOffline]); + mocks.storage.stat.mockResolvedValue({ mtime: assetStub.external.fileModifiedAt } as Stats); + + await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); + + expect(mocks.asset.updateAll).not.toHaveBeenCalled(); + + expect(mocks.job.queueAll).not.toHaveBeenCalled(); + }); + + it('should do nothing with unchanged online assets', async () => { + const mockAssetJob: ILibraryBulkIdsJob = { + assetIds: [assetStub.external.id], + libraryId: newUuid(), + importPaths: ['/'], + exclusionPatterns: [], + totalAssets: 1, + progressCounter: 0, + }; + + mocks.asset.getByIds.mockResolvedValue([assetStub.external]); + mocks.storage.stat.mockResolvedValue({ mtime: assetStub.external.fileModifiedAt } as Stats); + + await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); + + expect(mocks.asset.updateAll).not.toHaveBeenCalled(); + }); + + it('should not touch fileCreatedAt when un-trashing an asset previously marked as offline', async () => { + const mockAssetJob: ILibraryBulkIdsJob = { + assetIds: [assetStub.trashedOffline.id], + libraryId: newUuid(), + importPaths: ['/'], + exclusionPatterns: [], + totalAssets: 1, + progressCounter: 0, + }; + + mocks.asset.getByIds.mockResolvedValue([assetStub.trashedOffline]); mocks.storage.stat.mockResolvedValue({ mtime: assetStub.trashedOffline.fileModifiedAt } as Stats); - await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); + await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); expect(mocks.asset.updateAll).toHaveBeenCalledWith( [assetStub.trashedOffline.id], @@ -343,35 +501,42 @@ describe(LibraryService.name, () => { }), ); }); - }); - it('should update file when mtime has changed', async () => { - const mockAssetJob: ILibraryAssetJob = { - id: assetStub.external.id, - importPaths: ['/'], - exclusionPatterns: [], - }; + it('should update with online assets that have changed', async () => { + const mockAssetJob: ILibraryBulkIdsJob = { + assetIds: [assetStub.external.id], + libraryId: newUuid(), + importPaths: ['/'], + exclusionPatterns: [], + totalAssets: 1, + progressCounter: 0, + }; - const newMTime = new Date(); - mocks.asset.getById.mockResolvedValue(assetStub.external); - mocks.storage.stat.mockResolvedValue({ mtime: newMTime } as Stats); + if (assetStub.external.fileModifiedAt == null) { + throw new Error('fileModifiedAt is null'); + } - await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); + const mtime = new Date(assetStub.external.fileModifiedAt.getDate() + 1); - expect(mocks.asset.updateAll).toHaveBeenCalledWith([assetStub.external.id], { - fileModifiedAt: newMTime, - isOffline: false, - originalFileName: 'photo.jpg', - deletedAt: null, + mocks.asset.getByIds.mockResolvedValue([assetStub.external]); + mocks.storage.stat.mockResolvedValue({ mtime } as Stats); + + await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); + + expect(mocks.job.queueAll).toHaveBeenCalledWith([ + { + name: JobName.SIDECAR_DISCOVERY, + data: { + id: assetStub.external.id, + source: 'upload', + }, + }, + ]); }); }); - describe('handleSyncFile', () => { - let mockUser: UserEntity; - + describe('handleSyncFiles', () => { beforeEach(() => { - mockUser = userStub.admin; - mocks.storage.stat.mockResolvedValue({ size: 100, mtime: new Date('2023-01-01'), @@ -380,230 +545,99 @@ describe(LibraryService.name, () => { }); it('should import a new asset', async () => { + const library = factory.library(); + const mockLibraryJob: ILibraryFileJob = { - id: libraryStub.externalLibrary1.id, - ownerId: mockUser.id, - assetPath: '/data/user1/photo.jpg', + libraryId: library.id, + paths: ['/data/user1/photo.jpg'], }; - mocks.asset.create.mockResolvedValue(assetStub.image); - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); + mocks.asset.createAll.mockResolvedValue([assetStub.image]); + mocks.library.get.mockResolvedValue(library); - await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS); + await expect(sut.handleSyncFiles(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS); - expect(mocks.asset.create.mock.calls).toEqual([ - [ - { - ownerId: mockUser.id, - libraryId: libraryStub.externalLibrary1.id, - checksum: expect.any(Buffer), - originalPath: '/data/user1/photo.jpg', - deviceAssetId: expect.any(String), - deviceId: 'Library Import', - fileCreatedAt: expect.any(Date), - fileModifiedAt: expect.any(Date), - localDateTime: expect.any(Date), - type: AssetType.IMAGE, - originalFileName: 'photo.jpg', - isExternal: true, - }, - ], + expect(mocks.asset.createAll).toHaveBeenCalledWith([ + expect.objectContaining({ + ownerId: library.ownerId, + libraryId: library.id, + originalPath: '/data/user1/photo.jpg', + deviceId: 'Library Import', + type: AssetType.IMAGE, + originalFileName: 'photo.jpg', + isExternal: true, + }), ]); - expect(mocks.job.queue.mock.calls).toEqual([ - [ - { - name: JobName.SIDECAR_DISCOVERY, - data: { - id: assetStub.image.id, - source: 'upload', - }, + expect(mocks.job.queueAll).toHaveBeenCalledWith([ + { + name: JobName.SIDECAR_DISCOVERY, + data: { + id: assetStub.image.id, + source: 'upload', }, - ], - ]); - }); - - it('should import a new video', async () => { - const mockLibraryJob: ILibraryFileJob = { - id: libraryStub.externalLibrary1.id, - ownerId: mockUser.id, - assetPath: '/data/user1/video.mp4', - }; - - mocks.asset.create.mockResolvedValue(assetStub.video); - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); - - await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS); - - expect(mocks.asset.create.mock.calls).toEqual([ - [ - { - ownerId: mockUser.id, - libraryId: libraryStub.externalLibrary1.id, - checksum: expect.any(Buffer), - originalPath: '/data/user1/video.mp4', - deviceAssetId: expect.any(String), - deviceId: 'Library Import', - fileCreatedAt: expect.any(Date), - fileModifiedAt: expect.any(Date), - localDateTime: expect.any(Date), - type: AssetType.VIDEO, - originalFileName: 'video.mp4', - isExternal: true, - }, - ], - ]); - - expect(mocks.job.queue.mock.calls).toEqual([ - [ - { - name: JobName.SIDECAR_DISCOVERY, - data: { - id: assetStub.image.id, - source: 'upload', - }, - }, - ], + }, ]); }); it('should not import an asset to a soft deleted library', async () => { - const mockLibraryJob: ILibraryFileJob = { - id: libraryStub.externalLibrary1.id, - ownerId: mockUser.id, - assetPath: '/data/user1/photo.jpg', - }; - - mocks.asset.create.mockResolvedValue(assetStub.image); - mocks.library.get.mockResolvedValue({ ...libraryStub.externalLibrary1, deletedAt: new Date() }); - - await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.FAILED); - - expect(mocks.asset.create.mock.calls).toEqual([]); - }); - - it('should not refresh a file whose mtime matches existing asset', async () => { - const mockLibraryJob: ILibraryFileJob = { - id: libraryStub.externalLibrary1.id, - ownerId: mockUser.id, - assetPath: assetStub.hasFileExtension.originalPath, - }; - - mocks.storage.stat.mockResolvedValue({ - size: 100, - mtime: assetStub.hasFileExtension.fileModifiedAt, - ctime: new Date('2023-01-01'), - } as Stats); - - mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.hasFileExtension); - - await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SKIPPED); - - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - }); - - it('should skip existing asset', async () => { - const mockLibraryJob: ILibraryFileJob = { - id: libraryStub.externalLibrary1.id, - ownerId: mockUser.id, - assetPath: '/data/user1/photo.jpg', - }; - - mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - - await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SKIPPED); - }); - - it('should not refresh an asset trashed by user', async () => { - const mockLibraryJob: ILibraryFileJob = { - id: libraryStub.externalLibrary1.id, - ownerId: mockUser.id, - assetPath: assetStub.hasFileExtension.originalPath, - }; - - mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.trashed); - - await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SKIPPED); - - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - }); - - it('should fail when the file could not be read', async () => { - mocks.storage.stat.mockRejectedValue(new Error('Could not read file')); + const library = factory.library({ deletedAt: new Date() }); const mockLibraryJob: ILibraryFileJob = { - id: libraryStub.externalLibrary1.id, - ownerId: userStub.admin.id, - assetPath: '/data/user1/photo.jpg', + libraryId: library.id, + paths: ['/data/user1/photo.jpg'], }; - mocks.asset.create.mockResolvedValue(assetStub.image); + mocks.library.get.mockResolvedValue(library); - await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.FAILED); - expect(mocks.library.get).not.toHaveBeenCalled(); - expect(mocks.asset.create).not.toHaveBeenCalled(); - }); + await expect(sut.handleSyncFiles(mockLibraryJob)).resolves.toBe(JobStatus.FAILED); - it('should skip if the file could not be found', async () => { - const error = new Error('File not found') as any; - error.code = 'ENOENT'; - mocks.storage.stat.mockRejectedValue(error); - - const mockLibraryJob: ILibraryFileJob = { - id: libraryStub.externalLibrary1.id, - ownerId: userStub.admin.id, - assetPath: '/data/user1/photo.jpg', - }; - - mocks.asset.create.mockResolvedValue(assetStub.image); - - await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SKIPPED); - expect(mocks.library.get).not.toHaveBeenCalled(); - expect(mocks.asset.create).not.toHaveBeenCalled(); + expect(mocks.asset.createAll.mock.calls).toEqual([]); }); }); describe('delete', () => { it('should delete a library', async () => { + const library = factory.library(); + mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); + mocks.library.get.mockResolvedValue(library); - await sut.delete(libraryStub.externalLibrary1.id); + await sut.delete(library.id); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.LIBRARY_DELETE, - data: { id: libraryStub.externalLibrary1.id }, - }); - - expect(mocks.library.softDelete).toHaveBeenCalledWith(libraryStub.externalLibrary1.id); + expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.LIBRARY_DELETE, data: { id: library.id } }); + expect(mocks.library.softDelete).toHaveBeenCalledWith(library.id); }); it('should allow an external library to be deleted', async () => { - mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); + const library = factory.library(); - await sut.delete(libraryStub.externalLibrary1.id); + mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); + mocks.library.get.mockResolvedValue(library); + + await sut.delete(library.id); expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.LIBRARY_DELETE, - data: { id: libraryStub.externalLibrary1.id }, + data: { id: library.id }, }); - expect(mocks.library.softDelete).toHaveBeenCalledWith(libraryStub.externalLibrary1.id); + expect(mocks.library.softDelete).toHaveBeenCalledWith(library.id); }); it('should unwatch an external library when deleted', async () => { + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - mocks.library.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - mocks.library.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); + mocks.library.get.mockResolvedValue(library); + mocks.library.getAll.mockResolvedValue([library]); const mockClose = vitest.fn(); mocks.storage.watch.mockImplementation(makeMockWatcher({ close: mockClose })); + mocks.cron.create.mockResolvedValue(); await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); - await sut.delete(libraryStub.externalLibraryWithImportPaths1.id); + await sut.delete(library.id); expect(mockClose).toHaveBeenCalled(); }); @@ -611,56 +645,62 @@ describe(LibraryService.name, () => { describe('get', () => { it('should return a library', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); - await expect(sut.get(libraryStub.externalLibrary1.id)).resolves.toEqual( + const library = factory.library(); + + mocks.library.get.mockResolvedValue(library); + + await expect(sut.get(library.id)).resolves.toEqual( expect.objectContaining({ - id: libraryStub.externalLibrary1.id, - name: libraryStub.externalLibrary1.name, - ownerId: libraryStub.externalLibrary1.ownerId, + id: library.id, + name: library.name, + ownerId: library.ownerId, }), ); - expect(mocks.library.get).toHaveBeenCalledWith(libraryStub.externalLibrary1.id); + expect(mocks.library.get).toHaveBeenCalledWith(library.id); }); it('should throw an error when a library is not found', async () => { - await expect(sut.get(libraryStub.externalLibrary1.id)).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.library.get).toHaveBeenCalledWith(libraryStub.externalLibrary1.id); + const library = factory.library(); + + await expect(sut.get(library.id)).rejects.toBeInstanceOf(BadRequestException); + + expect(mocks.library.get).toHaveBeenCalledWith(library.id); }); }); describe('getStatistics', () => { it('should return library statistics', async () => { + const library = factory.library(); + mocks.library.getStatistics.mockResolvedValue({ photos: 10, videos: 0, total: 10, usage: 1337 }); - await expect(sut.getStatistics(libraryStub.externalLibrary1.id)).resolves.toEqual({ + await expect(sut.getStatistics(library.id)).resolves.toEqual({ photos: 10, videos: 0, total: 10, usage: 1337, }); - expect(mocks.library.getStatistics).toHaveBeenCalledWith(libraryStub.externalLibrary1.id); - }); - - it('should throw an error if the library could not be found', async () => { - await expect(sut.getStatistics('foo')).rejects.toBeInstanceOf(BadRequestException); + expect(mocks.library.getStatistics).toHaveBeenCalledWith(library.id); }); }); describe('create', () => { describe('external library', () => { it('should create with default settings', async () => { - mocks.library.create.mockResolvedValue(libraryStub.externalLibrary1); + const library = factory.library(); + + mocks.library.create.mockResolvedValue(library); await expect(sut.create({ ownerId: authStub.admin.user.id })).resolves.toEqual( expect.objectContaining({ - id: libraryStub.externalLibrary1.id, - name: libraryStub.externalLibrary1.name, - ownerId: libraryStub.externalLibrary1.ownerId, + id: library.id, + name: library.name, + ownerId: library.ownerId, assetCount: 0, importPaths: [], exclusionPatterns: [], - createdAt: libraryStub.externalLibrary1.createdAt, - updatedAt: libraryStub.externalLibrary1.updatedAt, + createdAt: library.createdAt, + updatedAt: library.updatedAt, refreshedAt: null, }), ); @@ -675,17 +715,20 @@ describe(LibraryService.name, () => { }); it('should create with name', async () => { - mocks.library.create.mockResolvedValue(libraryStub.externalLibrary1); + const library = factory.library(); + + mocks.library.create.mockResolvedValue(library); + await expect(sut.create({ ownerId: authStub.admin.user.id, name: 'My Awesome Library' })).resolves.toEqual( expect.objectContaining({ - id: libraryStub.externalLibrary1.id, - name: libraryStub.externalLibrary1.name, - ownerId: libraryStub.externalLibrary1.ownerId, + id: library.id, + name: library.name, + ownerId: library.ownerId, assetCount: 0, importPaths: [], exclusionPatterns: [], - createdAt: libraryStub.externalLibrary1.createdAt, - updatedAt: libraryStub.externalLibrary1.updatedAt, + createdAt: library.createdAt, + updatedAt: library.updatedAt, refreshedAt: null, }), ); @@ -700,7 +743,9 @@ describe(LibraryService.name, () => { }); it('should create with import paths', async () => { - mocks.library.create.mockResolvedValue(libraryStub.externalLibrary1); + const library = factory.library(); + + mocks.library.create.mockResolvedValue(library); await expect( sut.create({ ownerId: authStub.admin.user.id, @@ -708,14 +753,14 @@ describe(LibraryService.name, () => { }), ).resolves.toEqual( expect.objectContaining({ - id: libraryStub.externalLibrary1.id, - name: libraryStub.externalLibrary1.name, - ownerId: libraryStub.externalLibrary1.ownerId, + id: library.id, + name: library.name, + ownerId: library.ownerId, assetCount: 0, importPaths: [], exclusionPatterns: [], - createdAt: libraryStub.externalLibrary1.createdAt, - updatedAt: libraryStub.externalLibrary1.updatedAt, + createdAt: library.createdAt, + updatedAt: library.updatedAt, refreshedAt: null, }), ); @@ -730,19 +775,21 @@ describe(LibraryService.name, () => { }); it('should create watched with import paths', async () => { - mocks.library.create.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - mocks.library.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + mocks.library.create.mockResolvedValue(library); + mocks.library.get.mockResolvedValue(library); mocks.library.getAll.mockResolvedValue([]); + mocks.cron.create.mockResolvedValue(); await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); - await sut.create({ - ownerId: authStub.admin.user.id, - importPaths: libraryStub.externalLibraryWithImportPaths1.importPaths, - }); + await sut.create({ ownerId: authStub.admin.user.id, importPaths: library.importPaths }); }); it('should create with exclusion patterns', async () => { - mocks.library.create.mockResolvedValue(libraryStub.externalLibrary1); + const library = factory.library(); + + mocks.library.create.mockResolvedValue(library); await expect( sut.create({ ownerId: authStub.admin.user.id, @@ -750,14 +797,14 @@ describe(LibraryService.name, () => { }), ).resolves.toEqual( expect.objectContaining({ - id: libraryStub.externalLibrary1.id, - name: libraryStub.externalLibrary1.name, - ownerId: libraryStub.externalLibrary1.ownerId, + id: library.id, + name: library.name, + ownerId: library.ownerId, assetCount: 0, importPaths: [], exclusionPatterns: [], - createdAt: libraryStub.externalLibrary1.createdAt, - updatedAt: libraryStub.externalLibrary1.updatedAt, + createdAt: library.createdAt, + updatedAt: library.updatedAt, refreshedAt: null, }), ); @@ -775,19 +822,25 @@ describe(LibraryService.name, () => { describe('getAll', () => { it('should get all libraries', async () => { - mocks.library.getAll.mockResolvedValue([libraryStub.externalLibrary1]); - await expect(sut.getAll()).resolves.toEqual([expect.objectContaining({ id: libraryStub.externalLibrary1.id })]); + const library = factory.library(); + + mocks.library.getAll.mockResolvedValue([library]); + + await expect(sut.getAll()).resolves.toEqual([expect.objectContaining({ id: library.id })]); }); }); describe('handleQueueCleanup', () => { it('should queue cleanup jobs', async () => { - mocks.library.getAllDeleted.mockResolvedValue([libraryStub.externalLibrary1, libraryStub.externalLibrary2]); + const library1 = factory.library({ deletedAt: new Date() }); + const library2 = factory.library({ deletedAt: new Date() }); + + mocks.library.getAllDeleted.mockResolvedValue([library1, library2]); await expect(sut.handleQueueCleanup()).resolves.toBe(JobStatus.SUCCESS); expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.LIBRARY_DELETE, data: { id: libraryStub.externalLibrary1.id } }, - { name: JobName.LIBRARY_DELETE, data: { id: libraryStub.externalLibrary2.id } }, + { name: JobName.LIBRARY_DELETE, data: { id: library1.id } }, + { name: JobName.LIBRARY_DELETE, data: { id: library2.id } }, ]); }); }); @@ -795,29 +848,33 @@ describe(LibraryService.name, () => { describe('update', () => { beforeEach(async () => { mocks.library.getAll.mockResolvedValue([]); + mocks.cron.create.mockResolvedValue(); await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); }); it('should throw an error if an import path is invalid', async () => { - mocks.library.update.mockResolvedValue(libraryStub.externalLibrary1); - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); + const library = factory.library(); + + mocks.library.update.mockResolvedValue(library); + mocks.library.get.mockResolvedValue(library); await expect(sut.update('library-id', { importPaths: ['foo/bar'] })).rejects.toBeInstanceOf(BadRequestException); + expect(mocks.library.update).not.toHaveBeenCalled(); }); it('should update library', async () => { - mocks.library.update.mockResolvedValue(libraryStub.externalLibrary1); - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); + const library = factory.library(); + + mocks.library.update.mockResolvedValue(library); + mocks.library.get.mockResolvedValue(library); mocks.storage.stat.mockResolvedValue({ isDirectory: () => true } as Stats); mocks.storage.checkFileExists.mockResolvedValue(true); const cwd = process.cwd(); - await expect(sut.update('library-id', { importPaths: [`${cwd}/foo/bar`] })).resolves.toEqual( - mapLibrary(libraryStub.externalLibrary1), - ); + await expect(sut.update('library-id', { importPaths: [`${cwd}/foo/bar`] })).resolves.toEqual(mapLibrary(library)); expect(mocks.library.update).toHaveBeenCalledWith( 'library-id', expect.objectContaining({ importPaths: [`${cwd}/foo/bar`] }), @@ -838,11 +895,15 @@ describe(LibraryService.name, () => { describe('watching disabled', () => { beforeEach(async () => { + mocks.cron.create.mockResolvedValue(); + await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchDisabled as SystemConfig }); }); it('should not watch library', async () => { - mocks.library.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + mocks.library.getAll.mockResolvedValue([library]); await sut.watchAll(); @@ -853,37 +914,41 @@ describe(LibraryService.name, () => { describe('watching enabled', () => { beforeEach(async () => { mocks.library.getAll.mockResolvedValue([]); + mocks.cron.create.mockResolvedValue(); + await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); }); it('should watch library', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - mocks.library.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + mocks.library.get.mockResolvedValue(library); + mocks.library.getAll.mockResolvedValue([library]); await sut.watchAll(); - expect(mocks.storage.watch).toHaveBeenCalledWith( - libraryStub.externalLibraryWithImportPaths1.importPaths, - expect.anything(), - expect.anything(), - ); + expect(mocks.storage.watch).toHaveBeenCalledWith(library.importPaths, expect.anything(), expect.anything()); }); it('should watch and unwatch library', async () => { - mocks.library.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); - mocks.library.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + mocks.library.getAll.mockResolvedValue([library]); + mocks.library.get.mockResolvedValue(library); const mockClose = vitest.fn(); mocks.storage.watch.mockImplementation(makeMockWatcher({ close: mockClose })); await sut.watchAll(); - await sut.unwatch(libraryStub.externalLibraryWithImportPaths1.id); + await sut.unwatch(library.id); expect(mockClose).toHaveBeenCalled(); }); it('should not watch library without import paths', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); - mocks.library.getAll.mockResolvedValue([libraryStub.externalLibrary1]); + const library = factory.library(); + + mocks.library.get.mockResolvedValue(library); + mocks.library.getAll.mockResolvedValue([library]); await sut.watchAll(); @@ -891,31 +956,29 @@ describe(LibraryService.name, () => { }); it('should handle a new file event', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - mocks.library.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + mocks.library.get.mockResolvedValue(library); + mocks.library.getAll.mockResolvedValue([library]); mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); mocks.storage.watch.mockImplementation(makeMockWatcher({ items: [{ event: 'add', value: '/foo/photo.jpg' }] })); await sut.watchAll(); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.LIBRARY_SYNC_FILE, - data: { - id: libraryStub.externalLibraryWithImportPaths1.id, - assetPath: '/foo/photo.jpg', - ownerId: libraryStub.externalLibraryWithImportPaths1.owner.id, - }, + expect(mocks.job.queue).toHaveBeenCalledWith({ + name: JobName.LIBRARY_SYNC_FILES, + data: { + libraryId: library.id, + paths: ['/foo/photo.jpg'], }, - ]); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.LIBRARY_SYNC_ASSET, data: expect.objectContaining({ id: assetStub.image.id }) }, - ]); + }); }); it('should handle a file change event', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - mocks.library.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + mocks.library.get.mockResolvedValue(library); + mocks.library.getAll.mockResolvedValue([library]); mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); mocks.storage.watch.mockImplementation( makeMockWatcher({ items: [{ event: 'change', value: '/foo/photo.jpg' }] }), @@ -923,40 +986,42 @@ describe(LibraryService.name, () => { await sut.watchAll(); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.LIBRARY_SYNC_FILE, - data: { - id: libraryStub.externalLibraryWithImportPaths1.id, - assetPath: '/foo/photo.jpg', - ownerId: libraryStub.externalLibraryWithImportPaths1.owner.id, - }, + expect(mocks.job.queue).toHaveBeenCalledWith({ + name: JobName.LIBRARY_SYNC_FILES, + data: { + libraryId: library.id, + paths: ['/foo/photo.jpg'], }, - ]); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.LIBRARY_SYNC_ASSET, data: expect.objectContaining({ id: assetStub.image.id }) }, - ]); + }); }); it('should handle a file unlink event', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - mocks.library.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + mocks.library.get.mockResolvedValue(library); + mocks.library.getAll.mockResolvedValue([library]); mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); mocks.storage.watch.mockImplementation( - makeMockWatcher({ items: [{ event: 'unlink', value: '/foo/photo.jpg' }] }), + makeMockWatcher({ items: [{ event: 'unlink', value: assetStub.image.originalPath }] }), ); await sut.watchAll(); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.LIBRARY_SYNC_ASSET, data: expect.objectContaining({ id: assetStub.image.id }) }, - ]); + expect(mocks.job.queue).toHaveBeenCalledWith({ + name: JobName.LIBRARY_ASSET_REMOVAL, + data: { + libraryId: library.id, + paths: [assetStub.image.originalPath], + }, + }); }); it('should handle an error event', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + mocks.library.get.mockResolvedValue(library); mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.external); - mocks.library.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); + mocks.library.getAll.mockResolvedValue([library]); mocks.storage.watch.mockImplementation( makeMockWatcher({ items: [{ event: 'error', value: 'Error!' }], @@ -966,10 +1031,12 @@ describe(LibraryService.name, () => { await expect(sut.watchAll()).resolves.toBeUndefined(); }); - it('should ignore unknown extensions', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - mocks.library.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); - mocks.storage.watch.mockImplementation(makeMockWatcher({ items: [{ event: 'add', value: '/foo/photo.jpg' }] })); + it('should not import a file with unknown extension', async () => { + const library = factory.library({ importPaths: ['/foo', '/bar'] }); + + mocks.library.get.mockResolvedValue(library); + mocks.library.getAll.mockResolvedValue([library]); + mocks.storage.watch.mockImplementation(makeMockWatcher({ items: [{ event: 'add', value: '/foo/photo.xyz' }] })); await sut.watchAll(); @@ -977,8 +1044,10 @@ describe(LibraryService.name, () => { }); it('should ignore excluded paths', async () => { - mocks.library.get.mockResolvedValue(libraryStub.patternPath); - mocks.library.getAll.mockResolvedValue([libraryStub.patternPath]); + const library = factory.library({ importPaths: ['/xyz', '/asdf'], exclusionPatterns: ['**/dir1/**'] }); + + mocks.library.get.mockResolvedValue(library); + mocks.library.getAll.mockResolvedValue([library]); mocks.storage.watch.mockImplementation( makeMockWatcher({ items: [{ event: 'add', value: '/dir1/photo.txt' }] }), ); @@ -989,8 +1058,13 @@ describe(LibraryService.name, () => { }); it('should ignore excluded paths without case sensitivity', async () => { - mocks.library.get.mockResolvedValue(libraryStub.patternPath); - mocks.library.getAll.mockResolvedValue([libraryStub.patternPath]); + const library = factory.library({ + importPaths: ['/xyz', '/asdf'], + exclusionPatterns: ['**/dir1/**'], + }); + + mocks.library.get.mockResolvedValue(library); + mocks.library.getAll.mockResolvedValue([library]); mocks.storage.watch.mockImplementation( makeMockWatcher({ items: [{ event: 'add', value: '/DIR1/photo.txt' }] }), ); @@ -1004,23 +1078,17 @@ describe(LibraryService.name, () => { describe('teardown', () => { it('should tear down all watchers', async () => { - mocks.library.getAll.mockResolvedValue([ - libraryStub.externalLibraryWithImportPaths1, - libraryStub.externalLibraryWithImportPaths2, - ]); - - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); + const library1 = factory.library({ importPaths: ['/foo', '/bar'] }); + const library2 = factory.library({ importPaths: ['/xyz', '/asdf'] }); + mocks.library.getAll.mockResolvedValue([library1, library2]); mocks.library.get.mockImplementation((id) => - Promise.resolve( - [libraryStub.externalLibraryWithImportPaths1, libraryStub.externalLibraryWithImportPaths2].find( - (library) => library.id === id, - ), - ), + Promise.resolve([library1, library2].find((library) => library.id === id)), ); const mockClose = vitest.fn(); mocks.storage.watch.mockImplementation(makeMockWatcher({ close: mockClose })); + mocks.cron.create.mockResolvedValue(); await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); await sut.onShutdown(); @@ -1031,92 +1099,62 @@ describe(LibraryService.name, () => { describe('handleDeleteLibrary', () => { it('should delete an empty library', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); - mocks.asset.getAll.mockResolvedValue({ items: [], hasNextPage: false }); + const library = factory.library(); + + mocks.library.get.mockResolvedValue(library); + mocks.library.streamAssetIds.mockReturnValue(makeStream([])); + + await expect(sut.handleDeleteLibrary({ id: library.id })).resolves.toBe(JobStatus.SUCCESS); - await expect(sut.handleDeleteLibrary({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SUCCESS); expect(mocks.library.delete).toHaveBeenCalled(); }); it('should delete all assets in a library', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); - mocks.asset.getAll.mockResolvedValue({ items: [assetStub.image1], hasNextPage: false }); + const library = factory.library(); + + mocks.library.get.mockResolvedValue(library); + mocks.library.streamAssetIds.mockReturnValue(makeStream([assetStub.image1])); mocks.asset.getById.mockResolvedValue(assetStub.image1); - await expect(sut.handleDeleteLibrary({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SUCCESS); + await expect(sut.handleDeleteLibrary({ id: library.id })).resolves.toBe(JobStatus.SUCCESS); }); }); describe('queueScan', () => { it('should queue a library scan', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); + const library = factory.library(); - await sut.queueScan(libraryStub.externalLibrary1.id); + mocks.library.get.mockResolvedValue(library); - expect(mocks.job.queue.mock.calls).toEqual([ - [ - { - name: JobName.LIBRARY_QUEUE_SYNC_FILES, - data: { - id: libraryStub.externalLibrary1.id, - }, - }, - ], - [ - { - name: JobName.LIBRARY_QUEUE_SYNC_ASSETS, - data: { - id: libraryStub.externalLibrary1.id, - }, - }, - ], - ]); + await sut.queueScan(library.id); + + expect(mocks.job.queue).toHaveBeenCalledTimes(2); + expect(mocks.job.queue).toHaveBeenCalledWith({ + name: JobName.LIBRARY_QUEUE_SYNC_FILES, + data: { id: library.id }, + }); + expect(mocks.job.queue).toHaveBeenCalledWith({ + name: JobName.LIBRARY_QUEUE_SYNC_ASSETS, + data: { id: library.id }, + }); }); }); describe('handleQueueAllScan', () => { it('should queue the refresh job', async () => { - mocks.library.getAll.mockResolvedValue([libraryStub.externalLibrary1]); + const library = factory.library(); + + mocks.library.getAll.mockResolvedValue([library]); await expect(sut.handleQueueScanAll()).resolves.toBe(JobStatus.SUCCESS); - expect(mocks.job.queue.mock.calls).toEqual([ - [ - { - name: JobName.LIBRARY_QUEUE_CLEANUP, - data: {}, - }, - ], - ]); + expect(mocks.job.queue).toHaveBeenCalledWith({ + name: JobName.LIBRARY_QUEUE_CLEANUP, + data: {}, + }); expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.LIBRARY_QUEUE_SYNC_FILES, - data: { - id: libraryStub.externalLibrary1.id, - }, - }, - ]); - }); - }); - - describe('handleQueueAssetOfflineCheck', () => { - it('should queue removal jobs', async () => { - mocks.library.get.mockResolvedValue(libraryStub.externalLibrary1); - mocks.asset.getAll.mockResolvedValue({ items: [assetStub.image1], hasNextPage: false }); - mocks.asset.getById.mockResolvedValue(assetStub.image1); - - await expect(sut.handleQueueSyncAssets({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SUCCESS); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.LIBRARY_SYNC_ASSET, - data: { - id: assetStub.image1.id, - importPaths: libraryStub.externalLibrary1.importPaths, - exclusionPatterns: libraryStub.externalLibrary1.exclusionPatterns, - }, - }, + { name: JobName.LIBRARY_QUEUE_SYNC_FILES, data: { id: library.id } }, ]); }); }); @@ -1226,28 +1264,26 @@ describe(LibraryService.name, () => { }); it('should detect when import path is in immich media folder', async () => { + const importPaths = ['upload/thumbs', `${process.cwd()}/xyz`, 'upload/library']; + const library = factory.library({ importPaths }); + mocks.storage.stat.mockResolvedValue({ isDirectory: () => true } as Stats); - const cwd = process.cwd(); - const validImport = `${cwd}/${libraryStub.hasImmichPaths.importPaths[1]}`; - mocks.storage.checkFileExists.mockImplementation((importPath) => Promise.resolve(importPath === validImport)); + mocks.storage.checkFileExists.mockImplementation((importPath) => Promise.resolve(importPath === importPaths[1])); - const pathStubs = libraryStub.hasImmichPaths.importPaths; - const importPaths = [pathStubs[0], validImport, pathStubs[2]]; - - await expect(sut.validate('library-id', { importPaths })).resolves.toEqual({ + await expect(sut.validate(library.id, { importPaths })).resolves.toEqual({ importPaths: [ { - importPath: libraryStub.hasImmichPaths.importPaths[0], + importPath: importPaths[0], isValid: false, message: 'Cannot use media upload folder for external libraries', }, { - importPath: validImport, + importPath: importPaths[1], isValid: true, }, { - importPath: libraryStub.hasImmichPaths.importPaths[2], + importPath: importPaths[2], isValid: false, message: 'Cannot use media upload folder for external libraries', }, diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts index cdd6a3948f..1039cff761 100644 --- a/server/src/services/library.service.ts +++ b/server/src/services/library.service.ts @@ -1,5 +1,6 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { R_OK } from 'node:constants'; +import { Stats } from 'node:fs'; import path, { basename, isAbsolute, parse } from 'node:path'; import picomatch from 'picomatch'; import { JOBS_LIBRARY_PAGINATION_SIZE } from 'src/constants'; @@ -16,14 +17,13 @@ import { ValidateLibraryResponseDto, } from 'src/dtos/library.dto'; import { AssetEntity } from 'src/entities/asset.entity'; -import { LibraryEntity } from 'src/entities/library.entity'; -import { AssetType, DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName } from 'src/enum'; +import { AssetStatus, AssetType, DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName } from 'src/enum'; import { ArgOf } from 'src/repositories/event.repository'; +import { AssetSyncResult } from 'src/repositories/library.repository'; import { BaseService } from 'src/services/base.service'; import { JobOf } from 'src/types'; import { mimeTypes } from 'src/utils/mime-types'; import { handlePromiseError } from 'src/utils/misc'; -import { usePagination } from 'src/utils/pagination'; @Injectable() export class LibraryService extends BaseService { @@ -98,6 +98,26 @@ export class LibraryService extends BaseService { let _resolve: () => void; const ready$ = new Promise((resolve) => (_resolve = resolve)); + const handler = async (event: string, path: string) => { + if (matcher(path)) { + this.logger.debug(`File ${event} event received for ${path} in library ${library.id}}`); + await this.jobRepository.queue({ + name: JobName.LIBRARY_SYNC_FILES, + data: { libraryId: library.id, paths: [path] }, + }); + } else { + this.logger.verbose(`Ignoring file ${event} event for ${path} in library ${library.id}`); + } + }; + + const deletionHandler = async (path: string) => { + this.logger.debug(`File unlink event received for ${path} in library ${library.id}}`); + await this.jobRepository.queue({ + name: JobName.LIBRARY_ASSET_REMOVAL, + data: { libraryId: library.id, paths: [path] }, + }); + }; + this.watchers[id] = this.storageRepository.watch( library.importPaths, { @@ -107,43 +127,13 @@ export class LibraryService extends BaseService { { onReady: () => _resolve(), onAdd: (path) => { - const handler = async () => { - this.logger.debug(`File add event received for ${path} in library ${library.id}}`); - if (matcher(path)) { - const asset = await this.assetRepository.getByLibraryIdAndOriginalPath(library.id, path); - if (asset) { - await this.syncAssets(library, [asset.id]); - } - if (matcher(path)) { - await this.syncFiles(library, [path]); - } - } - }; - return handlePromiseError(handler(), this.logger); + return handlePromiseError(handler('add', path), this.logger); }, onChange: (path) => { - const handler = async () => { - this.logger.debug(`Detected file change for ${path} in library ${library.id}`); - const asset = await this.assetRepository.getByLibraryIdAndOriginalPath(library.id, path); - if (asset) { - await this.syncAssets(library, [asset.id]); - } - if (matcher(path)) { - // Note: if the changed file was not previously imported, it will be imported now. - await this.syncFiles(library, [path]); - } - }; - return handlePromiseError(handler(), this.logger); + return handlePromiseError(handler('change', path), this.logger); }, onUnlink: (path) => { - const handler = async () => { - this.logger.debug(`Detected deleted file at ${path} in library ${library.id}`); - const asset = await this.assetRepository.getByLibraryIdAndOriginalPath(library.id, path); - if (asset) { - await this.syncAssets(library, [asset.id]); - } - }; - return handlePromiseError(handler(), this.logger); + return handlePromiseError(deletionHandler(path), this.logger); }, onError: (error) => { this.logger.error(`Library watcher for library ${library.id} encountered error: ${error}`); @@ -234,26 +224,38 @@ export class LibraryService extends BaseService { return mapLibrary(library); } - private async syncFiles({ id, ownerId }: LibraryEntity, assetPaths: string[]) { - await this.jobRepository.queueAll( - assetPaths.map((assetPath) => ({ - name: JobName.LIBRARY_SYNC_FILE, - data: { - id, - assetPath, - ownerId, - }, - })), - ); - } + @OnJob({ name: JobName.LIBRARY_SYNC_FILES, queue: QueueName.LIBRARY }) + async handleSyncFiles(job: JobOf): Promise { + const library = await this.libraryRepository.get(job.libraryId); + // We need to check if the library still exists as it could have been deleted after the scan was queued + if (!library) { + this.logger.debug(`Library ${job.libraryId} not found, skipping file import`); + return JobStatus.FAILED; + } else if (library.deletedAt) { + this.logger.debug(`Library ${job.libraryId} is deleted, won't import assets into it`); + return JobStatus.FAILED; + } - private async syncAssets({ importPaths, exclusionPatterns }: LibraryEntity, assetIds: string[]) { - await this.jobRepository.queueAll( - assetIds.map((assetId) => ({ - name: JobName.LIBRARY_SYNC_ASSET, - data: { id: assetId, importPaths, exclusionPatterns }, - })), - ); + const assetImports = job.paths.map((assetPath) => this.processEntity(assetPath, library.ownerId, job.libraryId)); + + const assetIds: string[] = []; + + for (let i = 0; i < assetImports.length; i += 5000) { + // Chunk the imports to avoid the postgres limit of max parameters at once + const chunk = assetImports.slice(i, i + 5000); + await this.assetRepository.createAll(chunk).then((assets) => assetIds.push(...assets.map((asset) => asset.id))); + } + + const progressMessage = + job.progressCounter && job.totalAssets + ? `(${job.progressCounter} of ${job.totalAssets})` + : `(${job.progressCounter} done so far)`; + + this.logger.log(`Imported ${assetIds.length} ${progressMessage} file(s) into library ${job.libraryId}`); + + await this.queuePostSyncJobs(assetIds); + + return JobStatus.SUCCESS; } private async validateImportPath(importPath: string): Promise { @@ -336,115 +338,88 @@ export class LibraryService extends BaseService { async handleDeleteLibrary(job: JobOf): Promise { const libraryId = job.id; - const assetPagination = usePagination(JOBS_LIBRARY_PAGINATION_SIZE, (pagination) => - this.assetRepository.getAll(pagination, { libraryId, withDeleted: true }), - ); + await this.assetRepository.updateByLibraryId(libraryId, { deletedAt: new Date() }); let assetsFound = false; + let chunk: string[] = []; + + const queueChunk = async () => { + if (chunk.length > 0) { + assetsFound = true; + this.logger.debug(`Queueing deletion of ${chunk.length} asset(s) in library ${libraryId}`); + await this.jobRepository.queueAll( + chunk.map((id) => ({ name: JobName.ASSET_DELETION, data: { id, deleteOnDisk: false } })), + ); + chunk = []; + } + }; this.logger.debug(`Will delete all assets in library ${libraryId}`); - for await (const assets of assetPagination) { - if (assets.length > 0) { - assetsFound = true; - } + const assets = this.libraryRepository.streamAssetIds(libraryId); + for await (const asset of assets) { + chunk.push(asset.id); - this.logger.debug(`Queueing deletion of ${assets.length} asset(s) in library ${libraryId}`); - await this.jobRepository.queueAll( - assets.map((asset) => ({ - name: JobName.ASSET_DELETION, - data: { - id: asset.id, - deleteOnDisk: false, - }, - })), - ); + if (chunk.length >= JOBS_LIBRARY_PAGINATION_SIZE) { + await queueChunk(); + } } + await queueChunk(); + if (!assetsFound) { this.logger.log(`Deleting library ${libraryId}`); await this.libraryRepository.delete(libraryId); } + return JobStatus.SUCCESS; } - @OnJob({ name: JobName.LIBRARY_SYNC_FILE, queue: QueueName.LIBRARY }) - async handleSyncFile(job: JobOf): Promise { - // Only needs to handle new assets - const assetPath = path.normalize(job.assetPath); + private processEntity(filePath: string, ownerId: string, libraryId: string) { + const assetPath = path.normalize(filePath); - let asset = await this.assetRepository.getByLibraryIdAndOriginalPath(job.id, assetPath); - if (asset) { - return JobStatus.SKIPPED; - } - - let stat; - try { - stat = await this.storageRepository.stat(assetPath); - } catch (error: any) { - if (error.code === 'ENOENT') { - this.logger.error(`File not found: ${assetPath}`); - return JobStatus.SKIPPED; - } - this.logger.error(`Error reading file: ${assetPath}. Error: ${error}`); - return JobStatus.FAILED; - } - - this.logger.log(`Importing new library asset: ${assetPath}`); - - const library = await this.libraryRepository.get(job.id, true); - if (!library || library.deletedAt) { - this.logger.error('Cannot import asset into deleted library'); - return JobStatus.FAILED; - } - - // TODO: device asset id is deprecated, remove it - const deviceAssetId = `${basename(assetPath)}`.replaceAll(/\s+/g, ''); - - const pathHash = this.cryptoRepository.hashSha1(`path:${assetPath}`); - - const assetType = mimeTypes.isVideo(assetPath) ? AssetType.VIDEO : AssetType.IMAGE; - - const mtime = stat.mtime; - - asset = await this.assetRepository.create({ - ownerId: job.ownerId, - libraryId: job.id, - checksum: pathHash, + return { + ownerId, + libraryId, + checksum: this.cryptoRepository.hashSha1(`path:${assetPath}`), originalPath: assetPath, - deviceAssetId, + + fileCreatedAt: null, + fileModifiedAt: null, + localDateTime: null, + // TODO: device asset id is deprecated, remove it + deviceAssetId: `${basename(assetPath)}`.replaceAll(/\s+/g, ''), deviceId: 'Library Import', - fileCreatedAt: mtime, - fileModifiedAt: mtime, - localDateTime: mtime, - type: assetType, + type: mimeTypes.isVideo(assetPath) ? AssetType.VIDEO : AssetType.IMAGE, originalFileName: parse(assetPath).base, isExternal: true, - }); - - await this.queuePostSyncJobs(asset); - - return JobStatus.SUCCESS; + livePhotoVideoId: null, + }; } - async queuePostSyncJobs(asset: AssetEntity) { - this.logger.debug(`Queueing metadata extraction for: ${asset.originalPath}`); + async queuePostSyncJobs(assetIds: string[]) { + this.logger.debug(`Queuing sidecar discovery for ${assetIds.length} asset(s)`); // We queue a sidecar discovery which, in turn, queues metadata extraction - await this.jobRepository.queue({ - name: JobName.SIDECAR_DISCOVERY, - data: { id: asset.id, source: 'upload' }, - }); + await this.jobRepository.queueAll( + assetIds.map((assetId) => ({ + name: JobName.SIDECAR_DISCOVERY, + data: { id: assetId, source: 'upload' }, + })), + ); } async queueScan(id: string) { await this.findOrFail(id); + this.logger.log(`Starting to scan library ${id}`); + await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_FILES, data: { id, }, }); + await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ASSETS, data: { id } }); } @@ -454,11 +429,12 @@ export class LibraryService extends BaseService { @OnJob({ name: JobName.LIBRARY_QUEUE_SCAN_ALL, queue: QueueName.LIBRARY }) async handleQueueScanAll(): Promise { - this.logger.log(`Refreshing all external libraries`); + this.logger.log(`Initiating scan of all external libraries...`); await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_CLEANUP, data: {} }); const libraries = await this.libraryRepository.getAll(true); + await this.jobRepository.queueAll( libraries.map((library) => ({ name: JobName.LIBRARY_QUEUE_SYNC_FILES, @@ -475,64 +451,141 @@ export class LibraryService extends BaseService { }, })), ); + return JobStatus.SUCCESS; } - @OnJob({ name: JobName.LIBRARY_SYNC_ASSET, queue: QueueName.LIBRARY }) - async handleSyncAsset(job: JobOf): Promise { - const asset = await this.assetRepository.getById(job.id); - if (!asset) { - return JobStatus.SKIPPED; - } + @OnJob({ name: JobName.LIBRARY_SYNC_ASSETS, queue: QueueName.LIBRARY }) + async handleSyncAssets(job: JobOf): Promise { + const assets = await this.assetRepository.getByIds(job.assetIds); - const markOffline = async (explanation: string) => { - if (!asset.isOffline) { - this.logger.debug(`${explanation}, removing: ${asset.originalPath}`); - await this.assetRepository.updateAll([asset.id], { isOffline: true, deletedAt: new Date() }); + const assetIdsToOffline: string[] = []; + const trashedAssetIdsToOffline: string[] = []; + const assetIdsToOnline: string[] = []; + const trashedAssetIdsToOnline: string[] = []; + const assetIdsToUpdate: string[] = []; + + this.logger.debug(`Checking batch of ${assets.length} existing asset(s) in library ${job.libraryId}`); + + const stats = await Promise.all( + assets.map((asset) => this.storageRepository.stat(asset.originalPath).catch(() => null)), + ); + + for (let i = 0; i < assets.length; i++) { + const asset = assets[i]; + const stat = stats[i]; + const action = this.checkExistingAsset(asset, stat); + switch (action) { + case AssetSyncResult.OFFLINE: { + if (asset.status === AssetStatus.TRASHED) { + trashedAssetIdsToOffline.push(asset.id); + } else { + assetIdsToOffline.push(asset.id); + } + break; + } + case AssetSyncResult.UPDATE: { + assetIdsToUpdate.push(asset.id); + break; + } + case AssetSyncResult.CHECK_OFFLINE: { + const isInImportPath = job.importPaths.find((path) => asset.originalPath.startsWith(path)); + + if (!isInImportPath) { + this.logger.verbose( + `Offline asset ${asset.originalPath} is still not in any import path, keeping offline in library ${job.libraryId}`, + ); + break; + } + + const isExcluded = job.exclusionPatterns.some((pattern) => picomatch.isMatch(asset.originalPath, pattern)); + + if (!isExcluded) { + this.logger.debug(`Offline asset ${asset.originalPath} is now online in library ${job.libraryId}`); + if (asset.status === AssetStatus.TRASHED) { + trashedAssetIdsToOnline.push(asset.id); + } else { + assetIdsToOnline.push(asset.id); + } + break; + } + + this.logger.verbose( + `Offline asset ${asset.originalPath} is in an import path but still covered by exclusion pattern, keeping offline in library ${job.libraryId}`, + ); + + break; + } } - }; - - const isInPath = job.importPaths.find((path) => asset.originalPath.startsWith(path)); - if (!isInPath) { - await markOffline('Asset is no longer in an import path'); - return JobStatus.SUCCESS; } - const isExcluded = job.exclusionPatterns.some((pattern) => picomatch.isMatch(asset.originalPath, pattern)); - if (isExcluded) { - await markOffline('Asset is covered by an exclusion pattern'); - return JobStatus.SUCCESS; + const promises = []; + if (assetIdsToOffline.length > 0) { + promises.push(this.assetRepository.updateAll(assetIdsToOffline, { isOffline: true, deletedAt: new Date() })); } - let stat; - try { - stat = await this.storageRepository.stat(asset.originalPath); - } catch { - await markOffline('Asset is no longer on disk or is inaccessible because of permissions'); - return JobStatus.SUCCESS; + if (trashedAssetIdsToOffline.length > 0) { + promises.push(this.assetRepository.updateAll(trashedAssetIdsToOffline, { isOffline: true })); } - const mtime = stat.mtime; - const isAssetModified = !asset.fileModifiedAt || mtime.toISOString() !== asset.fileModifiedAt.toISOString(); - - if (asset.isOffline || isAssetModified) { - this.logger.debug(`Asset was offline or modified, updating asset record ${asset.originalPath}`); - //TODO: When we have asset status, we need to leave deletedAt as is when status is trashed - await this.assetRepository.updateAll([asset.id], { - isOffline: false, - deletedAt: null, - fileModifiedAt: mtime, - originalFileName: parse(asset.originalPath).base, - }); + if (assetIdsToOnline.length > 0) { + promises.push(this.assetRepository.updateAll(assetIdsToOnline, { isOffline: false, deletedAt: null })); } - if (isAssetModified) { - this.logger.debug(`Asset was modified, queuing metadata extraction for: ${asset.originalPath}`); - await this.queuePostSyncJobs(asset); + if (trashedAssetIdsToOnline.length > 0) { + promises.push(this.assetRepository.updateAll(trashedAssetIdsToOnline, { isOffline: false })); } + + if (assetIdsToUpdate.length > 0) { + promises.push(this.queuePostSyncJobs(assetIdsToUpdate)); + } + + await Promise.all(promises); + + const remainingCount = assets.length - assetIdsToOffline.length - assetIdsToUpdate.length - assetIdsToOnline.length; + const cumulativePercentage = ((100 * job.progressCounter) / job.totalAssets).toFixed(1); + this.logger.log( + `Checked existing asset(s): ${assetIdsToOffline.length + trashedAssetIdsToOffline.length} offlined, ${assetIdsToOnline.length + trashedAssetIdsToOnline.length} onlined, ${assetIdsToUpdate.length} updated, ${remainingCount} unchanged of current batch of ${assets.length} (Total progress: ${job.progressCounter} of ${job.totalAssets}, ${cumulativePercentage} %) in library ${job.libraryId}.`, + ); + return JobStatus.SUCCESS; } + private checkExistingAsset(asset: AssetEntity, stat: Stats | null): AssetSyncResult { + if (!stat) { + // File not found on disk or permission error + if (asset.isOffline) { + this.logger.verbose( + `Asset ${asset.originalPath} is still not accessible, keeping offline in library ${asset.libraryId}`, + ); + return AssetSyncResult.DO_NOTHING; + } + + this.logger.debug( + `Asset ${asset.originalPath} is no longer on disk or is inaccessible because of permissions, marking offline in library ${asset.libraryId}`, + ); + return AssetSyncResult.OFFLINE; + } + + if (asset.isOffline && asset.status !== AssetStatus.DELETED) { + // Only perform the expensive check if the asset is offline + return AssetSyncResult.CHECK_OFFLINE; + } + + if ( + !asset.fileCreatedAt || + !asset.localDateTime || + !asset.fileModifiedAt || + stat.mtime.valueOf() !== asset.fileModifiedAt.valueOf() + ) { + this.logger.verbose(`Asset ${asset.originalPath} needs metadata extraction in library ${asset.libraryId}`); + + return AssetSyncResult.UPDATE; + } + + return AssetSyncResult.DO_NOTHING; + } + @OnJob({ name: JobName.LIBRARY_QUEUE_SYNC_FILES, queue: QueueName.LIBRARY }) async handleQueueSyncFiles(job: JobOf): Promise { const library = await this.libraryRepository.get(job.id); @@ -541,7 +594,7 @@ export class LibraryService extends BaseService { return JobStatus.SKIPPED; } - this.logger.log(`Refreshing library ${library.id} for new assets`); + this.logger.debug(`Validating import paths for library ${library.id}...`); const validImportPaths: string[] = []; @@ -556,35 +609,67 @@ export class LibraryService extends BaseService { if (validImportPaths.length === 0) { this.logger.warn(`No valid import paths found for library ${library.id}`); + + return JobStatus.SKIPPED; } - const assetsOnDisk = this.storageRepository.walk({ + const pathsOnDisk = this.storageRepository.walk({ pathsToCrawl: validImportPaths, includeHidden: false, exclusionPatterns: library.exclusionPatterns, take: JOBS_LIBRARY_PAGINATION_SIZE, }); - let count = 0; + let importCount = 0; + let crawlCount = 0; - for await (const assetBatch of assetsOnDisk) { - count += assetBatch.length; - this.logger.debug(`Discovered ${count} asset(s) on disk for library ${library.id}...`); - await this.syncFiles(library, assetBatch); - this.logger.verbose(`Queued scan of ${assetBatch.length} crawled asset(s) in library ${library.id}...`); + this.logger.log(`Starting disk crawl of ${validImportPaths.length} import path(s) for library ${library.id}...`); + + for await (const pathBatch of pathsOnDisk) { + crawlCount += pathBatch.length; + const paths = await this.assetRepository.filterNewExternalAssetPaths(library.id, pathBatch); + + if (paths.length > 0) { + importCount += paths.length; + + await this.jobRepository.queue({ + name: JobName.LIBRARY_SYNC_FILES, + data: { + libraryId: library.id, + paths, + progressCounter: crawlCount, + }, + }); + } + + this.logger.log( + `Crawled ${crawlCount} file(s) so far: ${paths.length} of current batch of ${pathBatch.length} will be imported to library ${library.id}...`, + ); } - if (count > 0) { - this.logger.debug(`Finished queueing scan of ${count} assets on disk for library ${library.id}`); - } else if (validImportPaths.length > 0) { - this.logger.debug(`No non-excluded assets found in any import path for library ${library.id}`); - } + this.logger.log( + `Finished disk crawl, ${crawlCount} file(s) found on disk and queued ${importCount} file(s) for import into ${library.id}`, + ); await this.libraryRepository.update(job.id, { refreshedAt: new Date() }); return JobStatus.SUCCESS; } + @OnJob({ name: JobName.LIBRARY_ASSET_REMOVAL, queue: QueueName.LIBRARY }) + async handleAssetRemoval(job: JobOf): Promise { + // This is only for handling file unlink events via the file watcher + this.logger.verbose(`Deleting asset(s) ${job.paths} from library ${job.libraryId}`); + for (const assetPath of job.paths) { + const asset = await this.assetRepository.getByLibraryIdAndOriginalPath(job.libraryId, assetPath); + if (asset) { + await this.assetRepository.remove(asset); + } + } + + return JobStatus.SUCCESS; + } + @OnJob({ name: JobName.LIBRARY_QUEUE_SYNC_ASSETS, queue: QueueName.LIBRARY }) async handleQueueSyncAssets(job: JobOf): Promise { const library = await this.libraryRepository.get(job.id); @@ -592,29 +677,74 @@ export class LibraryService extends BaseService { return JobStatus.SKIPPED; } - this.logger.log(`Scanning library ${library.id} for removed assets`); + const assetCount = await this.assetRepository.getLibraryAssetCount(job.id); + if (!assetCount) { + this.logger.log(`Library ${library.id} is empty, no need to check assets`); + return JobStatus.SUCCESS; + } - const onlineAssets = usePagination(JOBS_LIBRARY_PAGINATION_SIZE, (pagination) => - this.assetRepository.getAll(pagination, { libraryId: job.id, withDeleted: true }), + this.logger.log( + `Checking ${assetCount} asset(s) against import paths and exclusion patterns in library ${library.id}...`, ); - let assetCount = 0; - for await (const assets of onlineAssets) { - assetCount += assets.length; - this.logger.debug(`Discovered ${assetCount} asset(s) in library ${library.id}...`); - await this.jobRepository.queueAll( - assets.map((asset) => ({ - name: JobName.LIBRARY_SYNC_ASSET, - data: { id: asset.id, importPaths: library.importPaths, exclusionPatterns: library.exclusionPatterns }, - })), - ); - this.logger.debug(`Queued check of ${assets.length} asset(s) in library ${library.id}...`); + const offlineResult = await this.assetRepository.detectOfflineExternalAssets( + library.id, + library.importPaths, + library.exclusionPatterns, + ); + + const affectedAssetCount = Number(offlineResult.numUpdatedRows); + + this.logger.log( + `${affectedAssetCount} asset(s) out of ${assetCount} were offlined due to import paths and/or exclusion pattern(s) in library ${library.id}`, + ); + + if (affectedAssetCount === assetCount) { + return JobStatus.SUCCESS; } - if (assetCount) { - this.logger.log(`Finished queueing check of ${assetCount} assets for library ${library.id}`); + let chunk: string[] = []; + let count = 0; + + const queueChunk = async () => { + if (chunk.length > 0) { + count += chunk.length; + + await this.jobRepository.queue({ + name: JobName.LIBRARY_SYNC_ASSETS, + data: { + libraryId: library.id, + importPaths: library.importPaths, + exclusionPatterns: library.exclusionPatterns, + assetIds: chunk.map((id) => id), + progressCounter: count, + totalAssets: assetCount, + }, + }); + chunk = []; + + const completePercentage = ((100 * count) / assetCount).toFixed(1); + + this.logger.log( + `Queued check of ${count} of ${assetCount} (${completePercentage} %) existing asset(s) so far in library ${library.id}`, + ); + } + }; + + this.logger.log(`Scanning library ${library.id} for assets missing from disk...`); + const existingAssets = this.libraryRepository.streamAssetIds(library.id); + + for await (const asset of existingAssets) { + chunk.push(asset.id); + if (chunk.length === JOBS_LIBRARY_PAGINATION_SIZE) { + await queueChunk(); + } } + await queueChunk(); + + this.logger.log(`Finished queuing ${count} asset check(s) for library ${library.id}`); + return JobStatus.SUCCESS; } diff --git a/server/src/services/map.service.spec.ts b/server/src/services/map.service.spec.ts index e86ad92976..95750f5590 100644 --- a/server/src/services/map.service.spec.ts +++ b/server/src/services/map.service.spec.ts @@ -2,7 +2,7 @@ import { MapService } from 'src/services/map.service'; import { albumStub } from 'test/fixtures/album.stub'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; -import { partnerStub } from 'test/fixtures/partner.stub'; +import { factory } from 'test/small.factory'; import { newTestService, ServiceMocks } from 'test/utils'; describe(MapService.name, () => { @@ -34,6 +34,9 @@ describe(MapService.name, () => { }); it('should include partner assets', async () => { + const partner = factory.partner(); + const auth = factory.auth({ id: partner.sharedWithId }); + const asset = assetStub.withLocation; const marker = { id: asset.id, @@ -43,13 +46,13 @@ describe(MapService.name, () => { state: asset.exifInfo!.state, country: asset.exifInfo!.country, }; - mocks.partner.getAll.mockResolvedValue([partnerStub.adminToUser1]); + mocks.partner.getAll.mockResolvedValue([partner]); mocks.map.getMapMarkers.mockResolvedValue([marker]); - const markers = await sut.getMapMarkers(authStub.user1, { withPartners: true }); + const markers = await sut.getMapMarkers(auth, { withPartners: true }); expect(mocks.map.getMapMarkers).toHaveBeenCalledWith( - [authStub.user1.user.id, partnerStub.adminToUser1.sharedById], + [auth.user.id, partner.sharedById], expect.arrayContaining([]), { withPartners: true }, ); diff --git a/server/src/services/memory.service.spec.ts b/server/src/services/memory.service.spec.ts index e3d85133ac..22a4199572 100644 --- a/server/src/services/memory.service.spec.ts +++ b/server/src/services/memory.service.spec.ts @@ -1,9 +1,6 @@ import { BadRequestException } from '@nestjs/common'; -import { MemoryType } from 'src/enum'; 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 { factory, newUuid, newUuids } from 'test/small.factory'; import { newTestService, ServiceMocks } from 'test/utils'; describe(MemoryService.name, () => { @@ -20,185 +17,254 @@ describe(MemoryService.name, () => { describe('search', () => { it('should search memories', async () => { - mocks.memory.search.mockResolvedValue([memoryStub.memory1, memoryStub.empty]); - await expect(sut.search(authStub.admin, {})).resolves.toEqual( + const [userId] = newUuids(); + const asset = factory.asset(); + const memory1 = factory.memory({ ownerId: userId, assets: [asset] }); + const memory2 = factory.memory({ ownerId: userId }); + + mocks.memory.search.mockResolvedValue([memory1, memory2]); + + await expect(sut.search(factory.auth({ id: userId }), {})).resolves.toEqual( expect.arrayContaining([ - expect.objectContaining({ id: 'memory1', assets: expect.any(Array) }), - expect.objectContaining({ id: 'memoryEmpty', assets: [] }), + expect.objectContaining({ id: memory1.id, assets: [expect.objectContaining({ id: asset.id })] }), + expect.objectContaining({ id: memory2.id, assets: [] }), ]), ); }); it('should map ', async () => { - await expect(sut.search(authStub.admin, {})).resolves.toEqual([]); + mocks.memory.search.mockResolvedValue([]); + + await expect(sut.search(factory.auth(), {})).resolves.toEqual([]); }); }); describe('get', () => { it('should throw an error when no access', async () => { - await expect(sut.get(authStub.admin, 'not-found')).rejects.toBeInstanceOf(BadRequestException); + await expect(sut.get(factory.auth(), 'not-found')).rejects.toBeInstanceOf(BadRequestException); }); it('should throw an error when the memory is not found', async () => { - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set(['race-condition'])); - await expect(sut.get(authStub.admin, 'race-condition')).rejects.toBeInstanceOf(BadRequestException); + const [memoryId] = newUuids(); + + mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memoryId])); + mocks.memory.get.mockResolvedValue(void 0); + + await expect(sut.get(factory.auth(), memoryId)).rejects.toBeInstanceOf(BadRequestException); }); it('should get a memory by id', async () => { - mocks.memory.get.mockResolvedValue(memoryStub.memory1); - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); - await expect(sut.get(authStub.admin, 'memory1')).resolves.toMatchObject({ id: 'memory1' }); - expect(mocks.memory.get).toHaveBeenCalledWith('memory1'); - expect(mocks.access.memory.checkOwnerAccess).toHaveBeenCalledWith(userStub.admin.id, new Set(['memory1'])); + const userId = newUuid(); + const memory = factory.memory({ ownerId: userId }); + + mocks.memory.get.mockResolvedValue(memory); + mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); + + await expect(sut.get(factory.auth({ id: userId }), memory.id)).resolves.toMatchObject({ id: memory.id }); + + expect(mocks.memory.get).toHaveBeenCalledWith(memory.id); + expect(mocks.access.memory.checkOwnerAccess).toHaveBeenCalledWith(memory.ownerId, new Set([memory.id])); }); }); describe('create', () => { it('should skip assets the user does not have access to', async () => { - mocks.memory.create.mockResolvedValue(memoryStub.empty); + const [assetId, userId] = newUuids(); + const memory = factory.memory({ ownerId: userId }); + + mocks.memory.create.mockResolvedValue(memory); + await expect( - sut.create(authStub.admin, { - type: MemoryType.ON_THIS_DAY, - data: { year: 2024 }, - assetIds: ['not-mine'], - memoryAt: new Date(2024), + sut.create(factory.auth({ id: userId }), { + type: memory.type, + data: memory.data, + memoryAt: memory.memoryAt, + isSaved: memory.isSaved, + assetIds: [assetId], }), ).resolves.toMatchObject({ assets: [] }); + expect(mocks.memory.create).toHaveBeenCalledWith( { - ownerId: 'admin_id', - memoryAt: expect.any(Date), - type: MemoryType.ON_THIS_DAY, - isSaved: undefined, - sendAt: undefined, - data: { year: 2024 }, + type: memory.type, + data: memory.data, + ownerId: memory.ownerId, + memoryAt: memory.memoryAt, + isSaved: memory.isSaved, }, new Set(), ); }); it('should create a memory', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1'])); - mocks.memory.create.mockResolvedValue(memoryStub.memory1); + const [assetId, userId] = newUuids(); + const asset = factory.asset({ id: assetId, ownerId: userId }); + const memory = factory.memory({ assets: [asset] }); + + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); + mocks.memory.create.mockResolvedValue(memory); + await expect( - sut.create(authStub.admin, { - type: MemoryType.ON_THIS_DAY, - data: { year: 2024 }, - assetIds: ['asset1'], - memoryAt: new Date(2024, 0, 1), + sut.create(factory.auth({ id: userId }), { + type: memory.type, + data: memory.data, + assetIds: memory.assets.map((asset) => asset.id), + memoryAt: memory.memoryAt, }), ).resolves.toBeDefined(); + expect(mocks.memory.create).toHaveBeenCalledWith( - expect.objectContaining({ - ownerId: userStub.admin.id, - }), - new Set(['asset1']), + expect.objectContaining({ ownerId: userId }), + new Set([assetId]), ); }); it('should create a memory without assets', async () => { - mocks.memory.create.mockResolvedValue(memoryStub.memory1); + const memory = factory.memory(); + + mocks.memory.create.mockResolvedValue(memory); + await expect( - sut.create(authStub.admin, { - type: MemoryType.ON_THIS_DAY, - data: { year: 2024 }, - memoryAt: new Date(2024), - }), + sut.create(factory.auth(), { type: memory.type, data: memory.data, memoryAt: memory.memoryAt }), ).resolves.toBeDefined(); }); }); describe('update', () => { it('should require access', async () => { - await expect(sut.update(authStub.admin, 'not-found', { isSaved: true })).rejects.toBeInstanceOf( + await expect(sut.update(factory.auth(), 'not-found', { isSaved: true })).rejects.toBeInstanceOf( BadRequestException, ); + expect(mocks.memory.update).not.toHaveBeenCalled(); }); it('should update a memory', async () => { - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); - mocks.memory.update.mockResolvedValue(memoryStub.memory1); - await expect(sut.update(authStub.admin, 'memory1', { isSaved: true })).resolves.toBeDefined(); - expect(mocks.memory.update).toHaveBeenCalledWith('memory1', expect.objectContaining({ isSaved: true })); + const memory = factory.memory(); + + mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); + mocks.memory.update.mockResolvedValue(memory); + + await expect(sut.update(factory.auth(), memory.id, { isSaved: true })).resolves.toBeDefined(); + + expect(mocks.memory.update).toHaveBeenCalledWith(memory.id, expect.objectContaining({ isSaved: true })); }); }); describe('remove', () => { it('should require access', async () => { - await expect(sut.remove(authStub.admin, 'not-found')).rejects.toBeInstanceOf(BadRequestException); + await expect(sut.remove(factory.auth(), newUuid())).rejects.toBeInstanceOf(BadRequestException); + expect(mocks.memory.delete).not.toHaveBeenCalled(); }); it('should delete a memory', async () => { - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); - await expect(sut.remove(authStub.admin, 'memory1')).resolves.toBeUndefined(); - expect(mocks.memory.delete).toHaveBeenCalledWith('memory1'); + const memoryId = newUuid(); + + mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memoryId])); + mocks.memory.delete.mockResolvedValue(); + + await expect(sut.remove(factory.auth(), memoryId)).resolves.toBeUndefined(); + + expect(mocks.memory.delete).toHaveBeenCalledWith(memoryId); }); }); describe('addAssets', () => { it('should require memory access', async () => { - await expect(sut.addAssets(authStub.admin, 'not-found', { ids: ['asset1'] })).rejects.toBeInstanceOf( + const [memoryId, assetId] = newUuids(); + + await expect(sut.addAssets(factory.auth(), memoryId, { ids: [assetId] })).rejects.toBeInstanceOf( BadRequestException, ); + expect(mocks.memory.addAssetIds).not.toHaveBeenCalled(); }); it('should require asset access', async () => { - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); - mocks.memory.get.mockResolvedValue(memoryStub.memory1); - await expect(sut.addAssets(authStub.admin, 'memory1', { ids: ['not-found'] })).resolves.toEqual([ - { error: 'no_permission', id: 'not-found', success: false }, + const assetId = newUuid(); + const memory = factory.memory(); + + mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); + mocks.memory.get.mockResolvedValue(memory); + mocks.memory.getAssetIds.mockResolvedValue(new Set()); + + await expect(sut.addAssets(factory.auth(), memory.id, { ids: [assetId] })).resolves.toEqual([ + { error: 'no_permission', id: assetId, success: false }, ]); + expect(mocks.memory.addAssetIds).not.toHaveBeenCalled(); }); it('should skip assets already in the memory', async () => { - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); - mocks.memory.get.mockResolvedValue(memoryStub.memory1); - mocks.memory.getAssetIds.mockResolvedValue(new Set(['asset1'])); - await expect(sut.addAssets(authStub.admin, 'memory1', { ids: ['asset1'] })).resolves.toEqual([ - { error: 'duplicate', id: 'asset1', success: false }, + const asset = factory.asset(); + const memory = factory.memory({ assets: [asset] }); + + mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); + mocks.memory.get.mockResolvedValue(memory); + mocks.memory.getAssetIds.mockResolvedValue(new Set([asset.id])); + + await expect(sut.addAssets(factory.auth(), memory.id, { ids: [asset.id] })).resolves.toEqual([ + { error: 'duplicate', id: asset.id, success: false }, ]); + expect(mocks.memory.addAssetIds).not.toHaveBeenCalled(); }); it('should add assets', async () => { - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1'])); - mocks.memory.get.mockResolvedValue(memoryStub.memory1); - await expect(sut.addAssets(authStub.admin, 'memory1', { ids: ['asset1'] })).resolves.toEqual([ - { id: 'asset1', success: true }, + const assetId = newUuid(); + const memory = factory.memory(); + + mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); + mocks.memory.get.mockResolvedValue(memory); + mocks.memory.update.mockResolvedValue(memory); + mocks.memory.getAssetIds.mockResolvedValue(new Set()); + mocks.memory.addAssetIds.mockResolvedValue(); + + await expect(sut.addAssets(factory.auth(), memory.id, { ids: [assetId] })).resolves.toEqual([ + { id: assetId, success: true }, ]); - expect(mocks.memory.addAssetIds).toHaveBeenCalledWith('memory1', ['asset1']); + + expect(mocks.memory.addAssetIds).toHaveBeenCalledWith(memory.id, [assetId]); }); }); describe('removeAssets', () => { it('should require memory access', async () => { - await expect(sut.removeAssets(authStub.admin, 'not-found', { ids: ['asset1'] })).rejects.toBeInstanceOf( + await expect(sut.removeAssets(factory.auth(), 'not-found', { ids: ['asset1'] })).rejects.toBeInstanceOf( BadRequestException, ); + expect(mocks.memory.removeAssetIds).not.toHaveBeenCalled(); }); it('should skip assets not in the memory', async () => { mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); - await expect(sut.removeAssets(authStub.admin, 'memory1', { ids: ['not-found'] })).resolves.toEqual([ + mocks.memory.getAssetIds.mockResolvedValue(new Set()); + + await expect(sut.removeAssets(factory.auth(), 'memory1', { ids: ['not-found'] })).resolves.toEqual([ { error: 'not_found', id: 'not-found', success: false }, ]); + expect(mocks.memory.removeAssetIds).not.toHaveBeenCalled(); }); it('should remove assets', async () => { - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1'])); - mocks.memory.getAssetIds.mockResolvedValue(new Set(['asset1'])); - await expect(sut.removeAssets(authStub.admin, 'memory1', { ids: ['asset1'] })).resolves.toEqual([ - { id: 'asset1', success: true }, + const memory = factory.memory(); + const asset = factory.asset(); + + mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); + mocks.memory.getAssetIds.mockResolvedValue(new Set([asset.id])); + mocks.memory.removeAssetIds.mockResolvedValue(); + mocks.memory.update.mockResolvedValue(memory); + + await expect(sut.removeAssets(factory.auth(), memory.id, { ids: [asset.id] })).resolves.toEqual([ + { id: asset.id, success: true }, ]); - expect(mocks.memory.removeAssetIds).toHaveBeenCalledWith('memory1', ['asset1']); + + expect(mocks.memory.removeAssetIds).toHaveBeenCalledWith(memory.id, [asset.id]); }); }); }); diff --git a/server/src/services/memory.service.ts b/server/src/services/memory.service.ts index 8a46b289c3..28c90f6576 100644 --- a/server/src/services/memory.service.ts +++ b/server/src/services/memory.service.ts @@ -1,6 +1,5 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; -import { JsonObject } from 'src/db'; import { OnJob } from 'src/decorators'; import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; @@ -97,7 +96,7 @@ export class MemoryService extends BaseService { { ownerId: auth.user.id, type: dto.type, - data: dto.data as unknown as JsonObject, + data: dto.data, isSaved: dto.isSaved, memoryAt: dto.memoryAt, seenAt: dto.seenAt, diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index e8ffd4a04f..229b63f20e 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -1,6 +1,5 @@ import { BinaryField, ExifDateTime } from 'exiftool-vendored'; import { randomBytes } from 'node:crypto'; -import { Stats } from 'node:fs'; import { constants } from 'node:fs/promises'; import { defaults } from 'src/config'; import { AssetEntity } from 'src/entities/asset.entity'; @@ -22,8 +21,14 @@ describe(MetadataService.name, () => { let mocks: ServiceMocks; const mockReadTags = (exifData?: Partial, sidecarData?: Partial) => { + exifData = { + FileSize: '123456', + FileCreateDate: '2024-01-01T00:00:00.000Z', + FileModifyDate: '2024-01-01T00:00:00.000Z', + ...exifData, + }; mocks.metadata.readTags.mockReset(); - mocks.metadata.readTags.mockResolvedValueOnce(exifData ?? {}); + mocks.metadata.readTags.mockResolvedValueOnce(exifData); mocks.metadata.readTags.mockResolvedValueOnce(sidecarData ?? {}); }; @@ -47,6 +52,10 @@ describe(MetadataService.name, () => { describe('onBootstrapEvent', () => { it('should pause and resume queue during init', async () => { + mocks.job.pause.mockResolvedValue(); + mocks.map.init.mockResolvedValue(); + mocks.job.resume.mockResolvedValue(); + await sut.onBootstrap(); expect(mocks.job.pause).toHaveBeenCalledTimes(1); @@ -105,10 +114,6 @@ describe(MetadataService.name, () => { }); describe('handleMetadataExtraction', () => { - beforeEach(() => { - mocks.storage.stat.mockResolvedValue({ size: 123_456 } as Stats); - }); - it('should handle an asset that could not be found', async () => { await expect(sut.handleMetadataExtraction({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED); @@ -126,19 +131,24 @@ describe(MetadataService.name, () => { await sut.handleMetadataExtraction({ id: assetStub.image.id }); expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.sidecar.id], { faces: { person: false } }); expect(mocks.asset.upsertExif).toHaveBeenCalledWith(expect.objectContaining({ dateTimeOriginal: sidecarDate })); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.image.id, - duration: null, - fileCreatedAt: sidecarDate, - localDateTime: sidecarDate, - }); + expect(mocks.asset.update).toHaveBeenCalledWith( + expect.objectContaining({ + id: assetStub.image.id, + duration: null, + fileCreatedAt: sidecarDate, + localDateTime: sidecarDate, + }), + ); }); - it('should take the file modification date when missing exif and earliest than creation date', async () => { + it('should take the file modification date when missing exif and earlier than creation date', async () => { const fileCreatedAt = new Date('2022-01-01T00:00:00.000Z'); const fileModifiedAt = new Date('2021-01-01T00:00:00.000Z'); - mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.image, fileCreatedAt, fileModifiedAt }]); - mockReadTags(); + mocks.asset.getByIds.mockResolvedValue([assetStub.image]); + mockReadTags({ + FileCreateDate: fileCreatedAt.toISOString(), + FileModifyDate: fileModifiedAt.toISOString(), + }); await sut.handleMetadataExtraction({ id: assetStub.image.id }); expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } }); @@ -149,15 +159,19 @@ describe(MetadataService.name, () => { id: assetStub.image.id, duration: null, fileCreatedAt: fileModifiedAt, + fileModifiedAt, localDateTime: fileModifiedAt, }); }); - it('should take the file creation date when missing exif and earliest than modification date', async () => { + it('should take the file creation date when missing exif and earlier than modification date', async () => { const fileCreatedAt = new Date('2021-01-01T00:00:00.000Z'); const fileModifiedAt = new Date('2022-01-01T00:00:00.000Z'); - mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.image, fileCreatedAt, fileModifiedAt }]); - mockReadTags(); + mocks.asset.getByIds.mockResolvedValue([assetStub.image]); + mockReadTags({ + FileCreateDate: fileCreatedAt.toISOString(), + FileModifyDate: fileModifiedAt.toISOString(), + }); await sut.handleMetadataExtraction({ id: assetStub.image.id }); expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } }); @@ -166,6 +180,7 @@ describe(MetadataService.name, () => { id: assetStub.image.id, duration: null, fileCreatedAt, + fileModifiedAt, localDateTime: fileCreatedAt, }); }); @@ -191,7 +206,11 @@ describe(MetadataService.name, () => { it('should handle lists of numbers', async () => { mocks.asset.getByIds.mockResolvedValue([assetStub.image]); - mockReadTags({ ISO: [160] }); + mockReadTags({ + ISO: [160], + FileCreateDate: assetStub.image.fileCreatedAt.toISOString(), + FileModifyDate: assetStub.image.fileModifiedAt.toISOString(), + }); await sut.handleMetadataExtraction({ id: assetStub.image.id }); expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } }); @@ -200,6 +219,7 @@ describe(MetadataService.name, () => { id: assetStub.image.id, duration: null, fileCreatedAt: assetStub.image.fileCreatedAt, + fileModifiedAt: assetStub.image.fileCreatedAt, localDateTime: assetStub.image.fileCreatedAt, }); }); @@ -211,6 +231,8 @@ describe(MetadataService.name, () => { mockReadTags({ GPSLatitude: assetStub.withLocation.exifInfo!.latitude!, GPSLongitude: assetStub.withLocation.exifInfo!.longitude!, + FileCreateDate: assetStub.withLocation.fileCreatedAt.toISOString(), + FileModifyDate: assetStub.withLocation.fileModifiedAt.toISOString(), }); await sut.handleMetadataExtraction({ id: assetStub.image.id }); @@ -221,7 +243,8 @@ describe(MetadataService.name, () => { expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.withLocation.id, duration: null, - fileCreatedAt: assetStub.withLocation.createdAt, + fileCreatedAt: assetStub.withLocation.fileCreatedAt, + fileModifiedAt: assetStub.withLocation.fileModifiedAt, localDateTime: new Date('2023-02-22T05:06:29.716Z'), }); }); @@ -454,11 +477,14 @@ describe(MetadataService.name, () => { mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null }]); mockReadTags({ Directory: 'foo/bar/', + MotionPhoto: 1, MotionPhotoVideo: new BinaryField(0, ''), // The below two are included to ensure that the MotionPhotoVideo tag is extracted // instead of the EmbeddedVideoFile, since HEIC MotionPhotos include both EmbeddedVideoFile: new BinaryField(0, ''), EmbeddedVideoType: 'MotionPhoto_Data', + FileCreateDate: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.toISOString(), + FileModifyDate: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.toISOString(), }); mocks.crypto.hashSha1.mockReturnValue(randomBytes(512)); mocks.asset.create.mockResolvedValue(assetStub.livePhotoMotionAsset); @@ -491,10 +517,11 @@ describe(MetadataService.name, () => { }); expect(mocks.user.updateUsage).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.ownerId, 512); expect(mocks.storage.createFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video); - expect(mocks.asset.update).toHaveBeenNthCalledWith(1, { + expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.livePhotoWithOriginalFileName.id, livePhotoVideoId: fileStub.livePhotoMotion.uuid, }); + expect(mocks.asset.update).toHaveBeenCalledTimes(2); }); it('should extract the EmbeddedVideo tag from Samsung JPEG motion photos', async () => { @@ -503,6 +530,9 @@ describe(MetadataService.name, () => { Directory: 'foo/bar/', EmbeddedVideoFile: new BinaryField(0, ''), EmbeddedVideoType: 'MotionPhoto_Data', + MotionPhoto: 1, + FileCreateDate: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.toISOString(), + FileModifyDate: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.toISOString(), }); mocks.crypto.hashSha1.mockReturnValue(randomBytes(512)); mocks.asset.create.mockResolvedValue(assetStub.livePhotoMotionAsset); @@ -535,10 +565,11 @@ describe(MetadataService.name, () => { }); expect(mocks.user.updateUsage).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.ownerId, 512); expect(mocks.storage.createFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video); - expect(mocks.asset.update).toHaveBeenNthCalledWith(1, { + expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.livePhotoWithOriginalFileName.id, livePhotoVideoId: fileStub.livePhotoMotion.uuid, }); + expect(mocks.asset.update).toHaveBeenCalledTimes(2); }); it('should extract the motion photo video from the XMP directory entry ', async () => { @@ -548,6 +579,8 @@ describe(MetadataService.name, () => { MotionPhoto: 1, MicroVideo: 1, MicroVideoOffset: 1, + FileCreateDate: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.toISOString(), + FileModifyDate: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.toISOString(), }); mocks.crypto.hashSha1.mockReturnValue(randomBytes(512)); mocks.asset.create.mockResolvedValue(assetStub.livePhotoMotionAsset); @@ -580,10 +613,11 @@ describe(MetadataService.name, () => { }); expect(mocks.user.updateUsage).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.ownerId, 512); expect(mocks.storage.createFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video); - expect(mocks.asset.update).toHaveBeenNthCalledWith(1, { + expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.livePhotoWithOriginalFileName.id, livePhotoVideoId: fileStub.livePhotoMotion.uuid, }); + expect(mocks.asset.update).toHaveBeenCalledTimes(2); }); it('should delete old motion photo video assets if they do not match what is extracted', async () => { @@ -648,14 +682,15 @@ describe(MetadataService.name, () => { mocks.storage.readFile.mockResolvedValue(video); await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id }); - expect(mocks.asset.update).toHaveBeenNthCalledWith(1, { + expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: false, }); - expect(mocks.asset.update).toHaveBeenNthCalledWith(2, { + expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.livePhotoStillAsset.id, livePhotoVideoId: assetStub.livePhotoMotionAsset.id, }); + expect(mocks.asset.update).toHaveBeenCalledTimes(3); }); it('should not update storage usage if motion photo is external', async () => { @@ -739,12 +774,14 @@ describe(MetadataService.name, () => { state: null, city: null, }); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.image.id, - duration: null, - fileCreatedAt: dateForTest, - localDateTime: dateForTest, - }); + expect(mocks.asset.update).toHaveBeenCalledWith( + expect.objectContaining({ + id: assetStub.image.id, + duration: null, + fileCreatedAt: dateForTest, + localDateTime: dateForTest, + }), + ); }); it('should extract +00:00 timezone from raw value', async () => { @@ -1048,6 +1085,7 @@ describe(MetadataService.name, () => { }), ); }); + it('should handle valid negative rating value', async () => { mocks.asset.getByIds.mockResolvedValue([assetStub.image]); mockReadTags({ Rating: -1 }); @@ -1160,6 +1198,23 @@ describe(MetadataService.name, () => { type: 'VIDEO', }); }); + + it.each([ + { Make: '1', Model: '2', Device: { Manufacturer: '3', ModelName: '4' }, AndroidMake: '4', AndroidModel: '5' }, + { Device: { Manufacturer: '1', ModelName: '2' }, AndroidMake: '3', AndroidModel: '4' }, + { AndroidMake: '1', AndroidModel: '2' }, + ])('should read camera make and model correct place %s', async (metaData) => { + mocks.asset.getByIds.mockResolvedValue([assetStub.image]); + mockReadTags(metaData); + + await sut.handleMetadataExtraction({ id: assetStub.image.id }); + expect(mocks.asset.upsertExif).toHaveBeenCalledWith( + expect.objectContaining({ + make: '1', + model: '2', + }), + ); + }); }); describe('handleQueueSidecar', () => { diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 14513c738a..4bf58a57fa 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -6,7 +6,6 @@ import _ from 'lodash'; import { Duration } from 'luxon'; import { constants } from 'node:fs/promises'; import path from 'node:path'; -import { SystemConfig } from 'src/config'; import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; import { Exif } from 'src/db'; @@ -76,6 +75,8 @@ const validateRange = (value: number | undefined, min: number, max: number): Non return val; }; +type ImmichTagsWithFaces = ImmichTags & { RegionInfo: NonNullable }; + @Injectable() export class MetadataService extends BaseService { @OnEvent({ name: 'app.bootstrap', workers: [ImmichWorker.MICROSERVICES] }) @@ -161,30 +162,39 @@ export class MetadataService extends BaseService { @OnJob({ name: JobName.METADATA_EXTRACTION, queue: QueueName.METADATA_EXTRACTION }) async handleMetadataExtraction(data: JobOf): Promise { - const { metadata, reverseGeocoding } = await this.getConfig({ withCache: true }); - const [asset] = await this.assetRepository.getByIds([data.id], { faces: { person: false } }); + const [{ metadata, reverseGeocoding }, [asset]] = await Promise.all([ + this.getConfig({ withCache: true }), + this.assetRepository.getByIds([data.id], { faces: { person: false } }), + ]); + if (!asset) { return JobStatus.FAILED; } - const stats = await this.storageRepository.stat(asset.originalPath); - const exifTags = await this.getExifTags(asset); + if (!exifTags.FileCreateDate || !exifTags.FileModifyDate || exifTags.FileSize === undefined) { + this.logger.warn(`Missing file creation or modification date for asset ${asset.id}: ${asset.originalPath}`); + const stat = await this.storageRepository.stat(asset.originalPath); + exifTags.FileCreateDate = stat.ctime.toISOString(); + exifTags.FileModifyDate = stat.mtime.toISOString(); + exifTags.FileSize = stat.size.toString(); + } this.logger.verbose('Exif Tags', exifTags); - if (!asset.fileCreatedAt) { - asset.fileCreatedAt = stats.mtime; - } - - if (!asset.fileModifiedAt) { - asset.fileModifiedAt = stats.mtime; - } - const { dateTimeOriginal, localDateTime, timeZone, modifyDate } = this.getDates(asset, exifTags); - const { latitude, longitude, country, state, city } = await this.getGeo(exifTags, reverseGeocoding); const { width, height } = this.getImageDimensions(exifTags); + let geo: ReverseGeocodeResult, latitude: number | null, longitude: number | null; + if (reverseGeocoding.enabled && this.hasGeo(exifTags)) { + latitude = exifTags.GPSLatitude; + longitude = exifTags.GPSLongitude; + geo = await this.mapRepository.reverseGeocode({ latitude, longitude }); + } else { + latitude = null; + longitude = null; + geo = { country: null, state: null, city: null }; + } const exifData: Insertable = { assetId: asset.id, @@ -197,12 +207,12 @@ export class MetadataService extends BaseService { // gps latitude, longitude, - country, - state, - city, + country: geo.country, + state: geo.state, + city: geo.city, // image/file - fileSizeInByte: stats.size, + fileSizeInByte: Number.parseInt(exifTags.FileSize!), exifImageHeight: validate(height), exifImageWidth: validate(width), orientation: validate(exifTags.Orientation)?.toString() ?? null, @@ -211,8 +221,8 @@ export class MetadataService extends BaseService { colorspace: exifTags.ColorSpace ?? null, // camera - make: exifTags.Make ?? null, - model: exifTags.Model ?? null, + make: exifTags.Make ?? exifTags?.Device?.Manufacturer ?? exifTags.AndroidMake ?? null, + model: exifTags.Model ?? exifTags?.Device?.ModelName ?? exifTags.AndroidModel ?? null, fps: validate(Number.parseFloat(exifTags.VideoFrameRate!)), iso: validate(exifTags.ISO) as number, exposureTime: exifTags.ExposureTime ?? null, @@ -230,25 +240,29 @@ export class MetadataService extends BaseService { autoStackId: this.getAutoStackId(exifTags), }; - await this.applyTagList(asset, exifTags); - await this.applyMotionPhotos(asset, exifTags); + const promises: Promise[] = [ + this.assetRepository.upsertExif(exifData), + this.assetRepository.update({ + id: asset.id, + duration: exifTags.Duration?.toString() ?? null, + localDateTime, + fileCreatedAt: exifData.dateTimeOriginal ?? undefined, + fileModifiedAt: exifData.modifyDate ?? undefined, + }), + this.applyTagList(asset, exifTags), + ]; - await this.assetRepository.upsertExif(exifData); - - await this.assetRepository.update({ - id: asset.id, - duration: exifTags.Duration?.toString() ?? null, - localDateTime, - fileCreatedAt: exifData.dateTimeOriginal ?? undefined, - fileModifiedAt: stats.mtime, - }); - - if (exifData.livePhotoCID) { - await this.linkLivePhotos(asset, exifData); + if (this.isMotionPhoto(asset, exifTags)) { + promises.push(this.applyMotionPhotos(asset, exifTags, exifData.fileSizeInByte!)); } - if (isFaceImportEnabled(metadata)) { - await this.applyTaggedFaces(asset, exifTags); + if (isFaceImportEnabled(metadata) && this.hasTaggedFaces(exifTags)) { + promises.push(this.applyTaggedFaces(asset, exifTags)); + } + + await Promise.all(promises); + if (exifData.livePhotoCID) { + await this.linkLivePhotos(asset, exifData); } await this.assetRepository.upsertJobStatus({ assetId: asset.id, metadataExtractedAt: new Date() }); @@ -347,48 +361,66 @@ export class MetadataService extends BaseService { return { width, height }; } - private async getExifTags(asset: AssetEntity): Promise { - const mediaTags = await this.metadataRepository.readTags(asset.originalPath); - const sidecarTags = asset.sidecarPath ? await this.metadataRepository.readTags(asset.sidecarPath) : {}; - const videoTags = asset.type === AssetType.VIDEO ? await this.getVideoTags(asset.originalPath) : {}; + private getExifTags(asset: AssetEntity): Promise { + if (!asset.sidecarPath && asset.type === AssetType.IMAGE) { + return this.metadataRepository.readTags(asset.originalPath); + } + + return this.mergeExifTags(asset); + } + + private async mergeExifTags(asset: AssetEntity): Promise { + const [mediaTags, sidecarTags, videoTags] = await Promise.all([ + this.metadataRepository.readTags(asset.originalPath), + asset.sidecarPath ? this.metadataRepository.readTags(asset.sidecarPath) : null, + asset.type === AssetType.VIDEO ? this.getVideoTags(asset.originalPath) : null, + ]); // prefer dates from sidecar tags - const sidecarDate = firstDateTime(sidecarTags as Tags, EXIF_DATE_TAGS); - if (sidecarDate) { - for (const tag of EXIF_DATE_TAGS) { - delete mediaTags[tag]; + if (sidecarTags) { + const sidecarDate = firstDateTime(sidecarTags as Tags, EXIF_DATE_TAGS); + if (sidecarDate) { + for (const tag of EXIF_DATE_TAGS) { + delete mediaTags[tag]; + } } } // prefer duration from video tags delete mediaTags.Duration; - delete sidecarTags.Duration; + delete sidecarTags?.Duration; return { ...mediaTags, ...videoTags, ...sidecarTags }; } - private async applyTagList(asset: AssetEntity, exifTags: ImmichTags) { - const tags: string[] = []; + private getTagList(exifTags: ImmichTags): string[] { + let tags: string[]; if (exifTags.TagsList) { - tags.push(...exifTags.TagsList.map(String)); + tags = exifTags.TagsList.map(String); } else if (exifTags.HierarchicalSubject) { - tags.push( - ...exifTags.HierarchicalSubject.map((tag) => - String(tag) - // convert | to / - .replaceAll('/', '') - .replaceAll('|', '/') - .replaceAll('', '|'), - ), + tags = exifTags.HierarchicalSubject.map((tag) => + // convert | to / + typeof tag === 'number' + ? String(tag) + : tag + .split('|') + .map((tag) => tag.replaceAll('/', '|')) + .join('/'), ); } else if (exifTags.Keywords) { let keywords = exifTags.Keywords; if (!Array.isArray(keywords)) { keywords = [keywords]; } - tags.push(...keywords.map(String)); + tags = keywords.map(String); + } else { + tags = []; } + return tags; + } + private async applyTagList(asset: AssetEntity, exifTags: ImmichTags) { + const tags = this.getTagList(exifTags); const results = await upsertTags(this.tagRepository, { userId: asset.ownerId, tags }); await this.tagRepository.replaceAssetTags( asset.id, @@ -396,11 +428,11 @@ export class MetadataService extends BaseService { ); } - private async applyMotionPhotos(asset: AssetEntity, tags: ImmichTags) { - if (asset.type !== AssetType.IMAGE) { - return; - } + private isMotionPhoto(asset: AssetEntity, tags: ImmichTags): boolean { + return asset.type === AssetType.IMAGE && !!(tags.MotionPhoto || tags.MicroVideo); + } + private async applyMotionPhotos(asset: AssetEntity, tags: ImmichTags, fileSize: number) { const isMotionPhoto = tags.MotionPhoto; const isMicroVideo = tags.MicroVideo; const videoOffset = tags.MicroVideoOffset; @@ -415,7 +447,7 @@ export class MetadataService extends BaseService { if (isMotionPhoto && directory) { for (const entry of directory) { - if (entry?.Item?.Semantic == 'MotionPhoto') { + if (entry?.Item?.Semantic === 'MotionPhoto') { length = entry.Item.Length ?? 0; padding = entry.Item.Padding ?? 0; break; @@ -434,8 +466,7 @@ export class MetadataService extends BaseService { this.logger.debug(`Starting motion photo video extraction for asset ${asset.id}: ${asset.originalPath}`); try { - const stat = await this.storageRepository.stat(asset.originalPath); - const position = stat.size - length - padding; + const position = fileSize - length - padding; let video: Buffer; // Samsung MotionPhoto video extraction // HEIC-encoded @@ -462,11 +493,10 @@ export class MetadataService extends BaseService { checksum, }); if (motionAsset) { - this.logger.debug( - `Motion photo video with checksum ${checksum.toString( - 'base64', - )} already exists in the repository for asset ${asset.id}: ${asset.originalPath}`, - ); + this.logger.debugFn(() => { + const base64Checksum = checksum.toString('base64'); + return `Motion asset with checksum ${base64Checksum} already exists for asset ${asset.id}: ${asset.originalPath}`; + }); // Hide the motion photo video asset if it's not already hidden to prepare for linking if (motionAsset.isVisible) { @@ -531,6 +561,12 @@ export class MetadataService extends BaseService { } } + private hasTaggedFaces(tags: ImmichTags): tags is ImmichTagsWithFaces { + return ( + tags.RegionInfo !== undefined && tags.RegionInfo.AppliedToDimensions && tags.RegionInfo.RegionList.length > 0 + ); + } + private async applyTaggedFaces(asset: AssetEntity, tags: ImmichTags) { if (!tags.RegionInfo?.AppliedToDimensions || tags.RegionInfo.RegionList.length === 0) { return; @@ -572,7 +608,7 @@ export class MetadataService extends BaseService { } if (missing.length > 0) { - this.logger.debug(`Creating missing persons: ${missing.map((p) => `${p.name}/${p.id}`)}`); + this.logger.debugFn(() => `Creating missing persons: ${missing.map((p) => `${p.name}/${p.id}`)}`); const newPersonIds = await this.personRepository.createAll(missing); const jobs = newPersonIds.map((id) => ({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id } }) as const); await this.jobRepository.queueAll(jobs); @@ -618,13 +654,15 @@ export class MetadataService extends BaseService { this.logger.debug(`No timezone information found for asset ${asset.id}: ${asset.originalPath}`); } + const modifyDate = this.toDate(exifTags.FileModifyDate!); let dateTimeOriginal = dateTime?.toDate(); let localDateTime = dateTime?.toDateTime().setZone('UTC', { keepLocalTime: true }).toJSDate(); if (!localDateTime || !dateTimeOriginal) { + const fileCreatedAt = this.toDate(exifTags.FileCreateDate!); + const earliestDate = this.earliestDate(fileCreatedAt, modifyDate); this.logger.debug( - `No exif date time found, falling back on earliest of file creation and modification for assset ${asset.id}: ${asset.originalPath}`, + `No exif date time found, falling back on ${earliestDate.toISOString()}, earliest of file creation and modification for assset ${asset.id}: ${asset.originalPath}`, ); - const earliestDate = this.earliestDate(asset.fileModifiedAt, asset.fileCreatedAt); dateTimeOriginal = earliestDate; localDateTime = earliestDate; } @@ -633,11 +671,6 @@ export class MetadataService extends BaseService { `Found local date time ${localDateTime.toISOString()} for asset ${asset.id}: ${asset.originalPath}`, ); - let modifyDate = asset.fileModifiedAt; - try { - modifyDate = (exifTags.ModifyDate as ExifDateTime)?.toDate() ?? modifyDate; - } catch {} - return { dateTimeOriginal, timeZone, @@ -646,28 +679,20 @@ export class MetadataService extends BaseService { }; } + private toDate(date: string | ExifDateTime): Date { + return typeof date === 'string' ? new Date(date) : date.toDate(); + } + private earliestDate(a: Date, b: Date) { return new Date(Math.min(a.valueOf(), b.valueOf())); } - private async getGeo(tags: ImmichTags, reverseGeocoding: SystemConfig['reverseGeocoding']) { - let latitude = validate(tags.GPSLatitude); - let longitude = validate(tags.GPSLongitude); - - // TODO take ref into account - - if (latitude === 0 && longitude === 0) { - this.logger.debug('Latitude and longitude of 0, setting to null'); - latitude = null; - longitude = null; - } - - let result: ReverseGeocodeResult = { country: null, state: null, city: null }; - if (reverseGeocoding.enabled && longitude && latitude) { - result = await this.mapRepository.reverseGeocode({ latitude, longitude }); - } - - return { ...result, latitude, longitude }; + private hasGeo(tags: ImmichTags): tags is ImmichTags & { GPSLatitude: number; GPSLongitude: number } { + return ( + tags.GPSLatitude !== undefined && + tags.GPSLongitude !== undefined && + (tags.GPSLatitude !== 0 || tags.GPSLatitude !== 0) + ); } private getAutoStackId(tags: ImmichTags | null): string | null { diff --git a/server/src/services/notification.service.spec.ts b/server/src/services/notification.service.spec.ts index 038ab1180c..0deb3805e5 100644 --- a/server/src/services/notification.service.spec.ts +++ b/server/src/services/notification.service.spec.ts @@ -260,6 +260,7 @@ describe(NotificationService.name, () => { mocks.user.get.mockResolvedValue(userStub.admin); mocks.notification.verifySmtp.mockResolvedValue(true); mocks.notification.renderEmail.mockResolvedValue({ html: '', text: '' }); + mocks.notification.sendEmail.mockResolvedValue({ messageId: 'message-1', response: '' }); await expect(sut.sendTestEmail('', configs.smtpTransport.notifications.smtp)).resolves.not.toThrow(); expect(mocks.notification.renderEmail).toHaveBeenCalledWith({ @@ -279,6 +280,7 @@ describe(NotificationService.name, () => { mocks.notification.verifySmtp.mockResolvedValue(true); mocks.notification.renderEmail.mockResolvedValue({ html: '', text: '' }); mocks.systemMetadata.get.mockResolvedValue({ server: { externalDomain: 'https://demo.immich.app' } }); + mocks.notification.sendEmail.mockResolvedValue({ messageId: 'message-1', response: '' }); await expect(sut.sendTestEmail('', configs.smtpTransport.notifications.smtp)).resolves.not.toThrow(); expect(mocks.notification.renderEmail).toHaveBeenCalledWith({ @@ -297,6 +299,7 @@ describe(NotificationService.name, () => { mocks.user.get.mockResolvedValue(userStub.admin); mocks.notification.verifySmtp.mockResolvedValue(true); mocks.notification.renderEmail.mockResolvedValue({ html: '', text: '' }); + mocks.notification.sendEmail.mockResolvedValue({ messageId: 'message-1', response: '' }); await expect( sut.sendTestEmail('', { ...configs.smtpTransport.notifications.smtp, replyTo: 'demo@immich.app' }), diff --git a/server/src/services/partner.service.spec.ts b/server/src/services/partner.service.spec.ts index 9c29afaeaa..6c3460666e 100644 --- a/server/src/services/partner.service.spec.ts +++ b/server/src/services/partner.service.spec.ts @@ -1,8 +1,7 @@ import { BadRequestException } from '@nestjs/common'; import { PartnerDirection } from 'src/repositories/partner.repository'; import { PartnerService } from 'src/services/partner.service'; -import { authStub } from 'test/fixtures/auth.stub'; -import { partnerStub } from 'test/fixtures/partner.stub'; +import { factory } from 'test/small.factory'; import { newTestService, ServiceMocks } from 'test/utils'; describe(PartnerService.name, () => { @@ -19,35 +18,58 @@ describe(PartnerService.name, () => { describe('search', () => { it("should return a list of partners with whom I've shared my library", async () => { - mocks.partner.getAll.mockResolvedValue([partnerStub.adminToUser1, partnerStub.user1ToAdmin1]); - await expect(sut.search(authStub.user1, { direction: PartnerDirection.SharedBy })).resolves.toBeDefined(); - expect(mocks.partner.getAll).toHaveBeenCalledWith(authStub.user1.user.id); + const user1 = factory.user(); + const user2 = factory.user(); + const sharedWithUser2 = factory.partner({ sharedBy: user1, sharedWith: user2 }); + const sharedWithUser1 = factory.partner({ sharedBy: user2, sharedWith: user1 }); + const auth = factory.auth({ id: user1.id }); + + mocks.partner.getAll.mockResolvedValue([sharedWithUser1, sharedWithUser2]); + + await expect(sut.search(auth, { direction: PartnerDirection.SharedBy })).resolves.toBeDefined(); + expect(mocks.partner.getAll).toHaveBeenCalledWith(user1.id); }); it('should return a list of partners who have shared their libraries with me', async () => { - mocks.partner.getAll.mockResolvedValue([partnerStub.adminToUser1, partnerStub.user1ToAdmin1]); - await expect(sut.search(authStub.user1, { direction: PartnerDirection.SharedWith })).resolves.toBeDefined(); - expect(mocks.partner.getAll).toHaveBeenCalledWith(authStub.user1.user.id); + const user1 = factory.user(); + const user2 = factory.user(); + const sharedWithUser2 = factory.partner({ sharedBy: user1, sharedWith: user2 }); + const sharedWithUser1 = factory.partner({ sharedBy: user2, sharedWith: user1 }); + const auth = factory.auth({ id: user1.id }); + + mocks.partner.getAll.mockResolvedValue([sharedWithUser1, sharedWithUser2]); + await expect(sut.search(auth, { direction: PartnerDirection.SharedWith })).resolves.toBeDefined(); + expect(mocks.partner.getAll).toHaveBeenCalledWith(user1.id); }); }); describe('create', () => { it('should create a new partner', async () => { - mocks.partner.get.mockResolvedValue(void 0); - mocks.partner.create.mockResolvedValue(partnerStub.adminToUser1); + const user1 = factory.user(); + const user2 = factory.user(); + const partner = factory.partner({ sharedBy: user1, sharedWith: user2 }); + const auth = factory.auth({ id: user1.id }); - await expect(sut.create(authStub.admin, authStub.user1.user.id)).resolves.toBeDefined(); + mocks.partner.get.mockResolvedValue(void 0); + mocks.partner.create.mockResolvedValue(partner); + + await expect(sut.create(auth, user2.id)).resolves.toBeDefined(); expect(mocks.partner.create).toHaveBeenCalledWith({ - sharedById: authStub.admin.user.id, - sharedWithId: authStub.user1.user.id, + sharedById: partner.sharedById, + sharedWithId: partner.sharedWithId, }); }); it('should throw an error when the partner already exists', async () => { - mocks.partner.get.mockResolvedValue(partnerStub.adminToUser1); + const user1 = factory.user(); + const user2 = factory.user(); + const partner = factory.partner({ sharedBy: user1, sharedWith: user2 }); + const auth = factory.auth({ id: user1.id }); - await expect(sut.create(authStub.admin, authStub.user1.user.id)).rejects.toBeInstanceOf(BadRequestException); + mocks.partner.get.mockResolvedValue(partner); + + await expect(sut.create(auth, user2.id)).rejects.toBeInstanceOf(BadRequestException); expect(mocks.partner.create).not.toHaveBeenCalled(); }); @@ -55,17 +77,25 @@ describe(PartnerService.name, () => { describe('remove', () => { it('should remove a partner', async () => { - mocks.partner.get.mockResolvedValue(partnerStub.adminToUser1); + const user1 = factory.user(); + const user2 = factory.user(); + const partner = factory.partner({ sharedBy: user1, sharedWith: user2 }); + const auth = factory.auth({ id: user1.id }); - await sut.remove(authStub.admin, authStub.user1.user.id); + mocks.partner.get.mockResolvedValue(partner); - expect(mocks.partner.remove).toHaveBeenCalledWith(partnerStub.adminToUser1); + await sut.remove(auth, user2.id); + + expect(mocks.partner.remove).toHaveBeenCalledWith({ sharedById: user1.id, sharedWithId: user2.id }); }); it('should throw an error when the partner does not exist', async () => { + const user2 = factory.user(); + const auth = factory.auth(); + mocks.partner.get.mockResolvedValue(void 0); - await expect(sut.remove(authStub.admin, authStub.user1.user.id)).rejects.toBeInstanceOf(BadRequestException); + await expect(sut.remove(auth, user2.id)).rejects.toBeInstanceOf(BadRequestException); expect(mocks.partner.remove).not.toHaveBeenCalled(); }); @@ -73,18 +103,24 @@ describe(PartnerService.name, () => { describe('update', () => { it('should require access', async () => { - await expect(sut.update(authStub.admin, 'shared-by-id', { inTimeline: false })).rejects.toBeInstanceOf( - BadRequestException, - ); + const user2 = factory.user(); + const auth = factory.auth(); + + await expect(sut.update(auth, user2.id, { inTimeline: false })).rejects.toBeInstanceOf(BadRequestException); }); it('should update partner', async () => { - mocks.access.partner.checkUpdateAccess.mockResolvedValue(new Set(['shared-by-id'])); - mocks.partner.update.mockResolvedValue(partnerStub.adminToUser1); + const user1 = factory.user(); + const user2 = factory.user(); + const partner = factory.partner({ sharedBy: user1, sharedWith: user2 }); + const auth = factory.auth({ id: user1.id }); - await expect(sut.update(authStub.admin, 'shared-by-id', { inTimeline: true })).resolves.toBeDefined(); + mocks.access.partner.checkUpdateAccess.mockResolvedValue(new Set([user2.id])); + mocks.partner.update.mockResolvedValue(partner); + + await expect(sut.update(auth, user2.id, { inTimeline: true })).resolves.toBeDefined(); expect(mocks.partner.update).toHaveBeenCalledWith( - { sharedById: 'shared-by-id', sharedWithId: authStub.admin.user.id }, + { sharedById: user2.id, sharedWithId: user1.id }, { inTimeline: true }, ); }); diff --git a/server/src/services/partner.service.ts b/server/src/services/partner.service.ts index 32b3ae3d3f..28ceab470e 100644 --- a/server/src/services/partner.service.ts +++ b/server/src/services/partner.service.ts @@ -1,8 +1,8 @@ import { BadRequestException, Injectable } from '@nestjs/common'; +import { Partner } from 'src/database'; import { AuthDto } from 'src/dtos/auth.dto'; import { PartnerResponseDto, PartnerSearchDto, UpdatePartnerDto } from 'src/dtos/partner.dto'; -import { mapUser } from 'src/dtos/user.dto'; -import { PartnerEntity } from 'src/entities/partner.entity'; +import { mapDatabaseUser } from 'src/dtos/user.dto'; import { Permission } from 'src/enum'; import { PartnerDirection, PartnerIds } from 'src/repositories/partner.repository'; import { BaseService } from 'src/services/base.service'; @@ -27,14 +27,14 @@ export class PartnerService extends BaseService { throw new BadRequestException('Partner not found'); } - await this.partnerRepository.remove(partner); + await this.partnerRepository.remove(partnerId); } async search(auth: AuthDto, { direction }: PartnerSearchDto): Promise { const partners = await this.partnerRepository.getAll(auth.user.id); const key = direction === PartnerDirection.SharedBy ? 'sharedById' : 'sharedWithId'; return partners - .filter((partner) => partner.sharedBy && partner.sharedWith) // Filter out soft deleted users + .filter((partner): partner is Partner => !!(partner.sharedBy && partner.sharedWith)) // Filter out soft deleted users .filter((partner) => partner[key] === auth.user.id) .map((partner) => this.mapPartner(partner, direction)); } @@ -47,14 +47,12 @@ export class PartnerService extends BaseService { return this.mapPartner(entity, PartnerDirection.SharedWith); } - private mapPartner(partner: PartnerEntity, direction: PartnerDirection): PartnerResponseDto { + private mapPartner(partner: Partner, direction: PartnerDirection): PartnerResponseDto { // this is opposite to return the non-me user of the "partner" - const user = mapUser( + const user = mapDatabaseUser( direction === PartnerDirection.SharedBy ? partner.sharedWith : partner.sharedBy, ) as PartnerResponseDto; - user.inTimeline = partner.inTimeline; - - return user; + return { ...user, inTimeline: partner.inTimeline }; } } diff --git a/server/src/services/person.service.spec.ts b/server/src/services/person.service.spec.ts index ba327ddae9..073cf71247 100644 --- a/server/src/services/person.service.spec.ts +++ b/server/src/services/person.service.spec.ts @@ -222,7 +222,7 @@ describe(PersonService.name, () => { mocks.person.update.mockResolvedValue(personStub.withBirthDate); mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - await expect(sut.update(authStub.admin, 'person-1', { birthDate: '1976-06-30' })).resolves.toEqual({ + await expect(sut.update(authStub.admin, 'person-1', { birthDate: new Date('1976-06-30') })).resolves.toEqual({ id: 'person-1', name: 'Person 1', birthDate: '1976-06-30', @@ -231,7 +231,7 @@ describe(PersonService.name, () => { isFavorite: false, updatedAt: expect.any(Date), }); - expect(mocks.person.update).toHaveBeenCalledWith({ id: 'person-1', birthDate: '1976-06-30' }); + expect(mocks.person.update).toHaveBeenCalledWith({ id: 'person-1', birthDate: new Date('1976-06-30') }); expect(mocks.job.queue).not.toHaveBeenCalled(); expect(mocks.job.queueAll).not.toHaveBeenCalled(); expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); @@ -324,6 +324,10 @@ describe(PersonService.name, () => { mocks.person.getFacesByIds.mockResolvedValue([faceStub.face1]); mocks.person.reassignFace.mockResolvedValue(1); mocks.person.getRandomFace.mockResolvedValue(faceStub.primaryFace1); + mocks.person.refreshFaces.mockResolvedValue(); + mocks.person.reassignFace.mockResolvedValue(5); + mocks.person.update.mockResolvedValue(personStub.noName); + await expect( sut.reassignFaces(authStub.admin, personStub.noName.id, { data: [{ personId: personStub.withName.id, assetId: assetStub.image.id }], @@ -515,6 +519,7 @@ describe(PersonService.name, () => { hasNextPage: false, }); mocks.person.getAllWithoutFaces.mockResolvedValue([personStub.randomPerson]); + mocks.person.deleteFaces.mockResolvedValue(); await sut.handleQueueDetectFaces({ force: true }); @@ -633,6 +638,7 @@ describe(PersonService.name, () => { mocks.person.getAll.mockReturnValue(makeStream()); mocks.person.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); mocks.person.getAllWithoutFaces.mockResolvedValue([]); + mocks.person.unassignFaces.mockResolvedValue(); await sut.handleQueueRecognizeFaces({ force: true, nightly: true }); @@ -679,6 +685,7 @@ describe(PersonService.name, () => { mocks.person.getAll.mockReturnValue(makeStream([faceStub.face1.person, personStub.randomPerson])); mocks.person.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); mocks.person.getAllWithoutFaces.mockResolvedValue([personStub.randomPerson]); + mocks.person.unassignFaces.mockResolvedValue(); await sut.handleQueueRecognizeFaces({ force: true }); @@ -757,6 +764,7 @@ describe(PersonService.name, () => { mocks.machineLearning.detectFaces.mockResolvedValue(detectFaceMock); mocks.search.searchFaces.mockResolvedValue([{ ...faceStub.face1, distance: 0.7 }]); mocks.asset.getByIds.mockResolvedValue([assetStub.image]); + mocks.person.refreshFaces.mockResolvedValue(); await sut.handleDetectFaces({ id: assetStub.image.id }); @@ -784,6 +792,7 @@ describe(PersonService.name, () => { it('should add new face and delete an existing face not among the new detected faces', async () => { mocks.machineLearning.detectFaces.mockResolvedValue(detectFaceMock); mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.image, faces: [faceStub.primaryFace1] }]); + mocks.person.refreshFaces.mockResolvedValue(); await sut.handleDetectFaces({ id: assetStub.image.id }); @@ -799,6 +808,7 @@ describe(PersonService.name, () => { it('should add embedding to matching metadata face', async () => { mocks.machineLearning.detectFaces.mockResolvedValue(detectFaceMock); mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.image, faces: [faceStub.fromExif1] }]); + mocks.person.refreshFaces.mockResolvedValue(); await sut.handleDetectFaces({ id: assetStub.image.id }); @@ -1006,6 +1016,7 @@ describe(PersonService.name, () => { mocks.person.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.middle.assetId }); mocks.person.getFaceByIdWithAssets.mockResolvedValue(faceStub.middle); mocks.asset.getById.mockResolvedValue(assetStub.primaryImage); + mocks.media.generateThumbnail.mockResolvedValue(); await sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id }); @@ -1038,6 +1049,7 @@ describe(PersonService.name, () => { mocks.person.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.start.assetId }); mocks.person.getFaceByIdWithAssets.mockResolvedValue(faceStub.start); mocks.asset.getById.mockResolvedValue(assetStub.image); + mocks.media.generateThumbnail.mockResolvedValue(); await sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id }); @@ -1063,7 +1075,9 @@ describe(PersonService.name, () => { it('should generate a thumbnail without overflowing', async () => { mocks.person.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.end.assetId }); mocks.person.getFaceByIdWithAssets.mockResolvedValue(faceStub.end); + mocks.person.update.mockResolvedValue(personStub.primaryPerson); mocks.asset.getById.mockResolvedValue(assetStub.primaryImage); + mocks.media.generateThumbnail.mockResolvedValue(); await sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id }); diff --git a/server/src/services/search.service.spec.ts b/server/src/services/search.service.spec.ts index 34f4c7b39f..79f3a77ebe 100644 --- a/server/src/services/search.service.spec.ts +++ b/server/src/services/search.service.spec.ts @@ -57,6 +57,8 @@ describe(SearchService.name, () => { describe('getSearchSuggestions', () => { it('should return search suggestions for country', async () => { mocks.search.getCountries.mockResolvedValue(['USA']); + mocks.partner.getAll.mockResolvedValue([]); + await expect( sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.COUNTRY }), ).resolves.toEqual(['USA']); @@ -65,6 +67,8 @@ describe(SearchService.name, () => { it('should return search suggestions for country (including null)', async () => { mocks.search.getCountries.mockResolvedValue(['USA']); + mocks.partner.getAll.mockResolvedValue([]); + await expect( sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.COUNTRY }), ).resolves.toEqual(['USA', null]); @@ -73,6 +77,8 @@ describe(SearchService.name, () => { it('should return search suggestions for state', async () => { mocks.search.getStates.mockResolvedValue(['California']); + mocks.partner.getAll.mockResolvedValue([]); + await expect( sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.STATE }), ).resolves.toEqual(['California']); @@ -81,6 +87,8 @@ describe(SearchService.name, () => { it('should return search suggestions for state (including null)', async () => { mocks.search.getStates.mockResolvedValue(['California']); + mocks.partner.getAll.mockResolvedValue([]); + await expect( sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.STATE }), ).resolves.toEqual(['California', null]); @@ -89,6 +97,8 @@ describe(SearchService.name, () => { it('should return search suggestions for city', async () => { mocks.search.getCities.mockResolvedValue(['Denver']); + mocks.partner.getAll.mockResolvedValue([]); + await expect( sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.CITY }), ).resolves.toEqual(['Denver']); @@ -97,6 +107,8 @@ describe(SearchService.name, () => { it('should return search suggestions for city (including null)', async () => { mocks.search.getCities.mockResolvedValue(['Denver']); + mocks.partner.getAll.mockResolvedValue([]); + await expect( sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.CITY }), ).resolves.toEqual(['Denver', null]); @@ -105,6 +117,8 @@ describe(SearchService.name, () => { it('should return search suggestions for camera make', async () => { mocks.search.getCameraMakes.mockResolvedValue(['Nikon']); + mocks.partner.getAll.mockResolvedValue([]); + await expect( sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.CAMERA_MAKE }), ).resolves.toEqual(['Nikon']); @@ -113,6 +127,8 @@ describe(SearchService.name, () => { it('should return search suggestions for camera make (including null)', async () => { mocks.search.getCameraMakes.mockResolvedValue(['Nikon']); + mocks.partner.getAll.mockResolvedValue([]); + await expect( sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.CAMERA_MAKE }), ).resolves.toEqual(['Nikon', null]); @@ -121,6 +137,8 @@ describe(SearchService.name, () => { it('should return search suggestions for camera model', async () => { mocks.search.getCameraModels.mockResolvedValue(['Fujifilm X100VI']); + mocks.partner.getAll.mockResolvedValue([]); + await expect( sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.CAMERA_MODEL }), ).resolves.toEqual(['Fujifilm X100VI']); @@ -129,6 +147,8 @@ describe(SearchService.name, () => { it('should return search suggestions for camera model (including null)', async () => { mocks.search.getCameraModels.mockResolvedValue(['Fujifilm X100VI']); + mocks.partner.getAll.mockResolvedValue([]); + await expect( sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.CAMERA_MODEL }), ).resolves.toEqual(['Fujifilm X100VI', null]); diff --git a/server/src/services/session.service.spec.ts b/server/src/services/session.service.spec.ts index 96a1dacf64..3d1b09a39d 100644 --- a/server/src/services/session.service.spec.ts +++ b/server/src/services/session.service.spec.ts @@ -36,6 +36,7 @@ describe('SessionService', () => { updateId: 'uuid-v7', }, ]); + mocks.session.delete.mockResolvedValue(); await expect(sut.handleCleanup()).resolves.toEqual(JobStatus.SUCCESS); expect(mocks.session.delete).toHaveBeenCalledWith('123'); @@ -71,6 +72,7 @@ describe('SessionService', () => { describe('logoutDevices', () => { it('should logout all devices', async () => { mocks.session.getByUserId.mockResolvedValue([sessionStub.inactive, sessionStub.valid] as any[]); + mocks.session.delete.mockResolvedValue(); await sut.deleteAll(authStub.user1); @@ -83,6 +85,7 @@ describe('SessionService', () => { describe('logoutDevice', () => { it('should logout the device', async () => { mocks.access.authDevice.checkOwnerAccess.mockResolvedValue(new Set(['token-1'])); + mocks.session.delete.mockResolvedValue(); await sut.delete(authStub.user1, 'token-1'); diff --git a/server/src/services/shared-link.service.spec.ts b/server/src/services/shared-link.service.spec.ts index 4d6cdee6cb..557fdd5780 100644 --- a/server/src/services/shared-link.service.spec.ts +++ b/server/src/services/shared-link.service.spec.ts @@ -71,7 +71,10 @@ describe(SharedLinkService.name, () => { describe('get', () => { it('should throw an error for an invalid shared link', async () => { + mocks.sharedLink.get.mockResolvedValue(void 0); + await expect(sut.get(authStub.user1, 'missing-id')).rejects.toBeInstanceOf(BadRequestException); + expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id'); expect(mocks.sharedLink.update).not.toHaveBeenCalled(); }); @@ -194,7 +197,10 @@ describe(SharedLinkService.name, () => { describe('update', () => { it('should throw an error for an invalid shared link', async () => { + mocks.sharedLink.get.mockResolvedValue(void 0); + await expect(sut.update(authStub.user1, 'missing-id', {})).rejects.toBeInstanceOf(BadRequestException); + expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id'); expect(mocks.sharedLink.update).not.toHaveBeenCalled(); }); @@ -214,14 +220,20 @@ describe(SharedLinkService.name, () => { describe('remove', () => { it('should throw an error for an invalid shared link', async () => { + mocks.sharedLink.get.mockResolvedValue(void 0); + await expect(sut.remove(authStub.user1, 'missing-id')).rejects.toBeInstanceOf(BadRequestException); + expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id'); expect(mocks.sharedLink.update).not.toHaveBeenCalled(); }); it('should remove a key', async () => { mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.valid); + mocks.sharedLink.remove.mockResolvedValue(); + await sut.remove(authStub.user1, sharedLinkStub.valid.id); + expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id); expect(mocks.sharedLink.remove).toHaveBeenCalledWith(sharedLinkStub.valid); }); @@ -238,6 +250,7 @@ describe(SharedLinkService.name, () => { it('should add assets to a shared link', async () => { mocks.sharedLink.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual)); mocks.sharedLink.create.mockResolvedValue(sharedLinkStub.individual); + mocks.sharedLink.update.mockResolvedValue(sharedLinkStub.individual); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-3'])); await expect( @@ -268,6 +281,7 @@ describe(SharedLinkService.name, () => { it('should remove assets from a shared link', async () => { mocks.sharedLink.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual)); mocks.sharedLink.create.mockResolvedValue(sharedLinkStub.individual); + mocks.sharedLink.update.mockResolvedValue(sharedLinkStub.individual); await expect( sut.removeAssets(authStub.admin, 'link-1', { assetIds: [assetStub.image.id, 'asset-2'] }), diff --git a/server/src/services/stack.service.spec.ts b/server/src/services/stack.service.spec.ts index 5fbc0e185d..f6da8bcac7 100644 --- a/server/src/services/stack.service.spec.ts +++ b/server/src/services/stack.service.spec.ts @@ -155,6 +155,7 @@ describe(StackService.name, () => { it('should delete stack', async () => { mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id'])); + mocks.stack.delete.mockResolvedValue(); await sut.delete(authStub.admin, 'stack-id'); @@ -176,6 +177,7 @@ describe(StackService.name, () => { it('should delete all stacks', async () => { mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id'])); + mocks.stack.deleteAll.mockResolvedValue(); await sut.deleteAll(authStub.admin, { ids: ['stack-id'] }); diff --git a/server/src/services/storage-template.service.spec.ts b/server/src/services/storage-template.service.spec.ts index 0a055d0e6d..6bfabe2e8c 100644 --- a/server/src/services/storage-template.service.spec.ts +++ b/server/src/services/storage-template.service.spec.ts @@ -1,12 +1,14 @@ import { Stats } from 'node:fs'; import { defaults, SystemConfig } from 'src/config'; -import { AssetEntity } from 'src/entities/asset.entity'; import { AssetPathType, JobStatus } from 'src/enum'; import { StorageTemplateService } from 'src/services/storage-template.service'; import { albumStub } from 'test/fixtures/album.stub'; import { assetStub } from 'test/fixtures/asset.stub'; import { userStub } from 'test/fixtures/user.stub'; -import { newTestService, ServiceMocks } from 'test/utils'; +import { makeStream, newTestService, ServiceMocks } from 'test/utils'; + +const motionAsset = assetStub.storageAsset({}); +const stillAsset = assetStub.storageAsset({ livePhotoVideoId: motionAsset.id }); describe(StorageTemplateService.name, () => { let sut: StorageTemplateService; @@ -91,7 +93,9 @@ describe(StorageTemplateService.name, () => { describe('handleMigrationSingle', () => { it('should skip when storage template is disabled', async () => { mocks.systemMetadata.get.mockResolvedValue({ storageTemplate: { enabled: false } }); - await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SKIPPED); + + await expect(sut.handleMigrationSingle({ id: testAsset.id })).resolves.toBe(JobStatus.SKIPPED); + expect(mocks.asset.getByIds).not.toHaveBeenCalled(); expect(mocks.storage.checkFileExists).not.toHaveBeenCalled(); expect(mocks.storage.rename).not.toHaveBeenCalled(); @@ -104,51 +108,37 @@ describe(StorageTemplateService.name, () => { it('should migrate single moving picture', async () => { mocks.user.get.mockResolvedValue(userStub.user1); - const newMotionPicturePath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${assetStub.livePhotoStillAsset.id}.mp4`; - const newStillPicturePath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${assetStub.livePhotoStillAsset.id}.jpeg`; + const newMotionPicturePath = `upload/library/${motionAsset.ownerId}/2022/2022-06-19/${motionAsset.originalFileName}`; + const newStillPicturePath = `upload/library/${stillAsset.ownerId}/2022/2022-06-19/${stillAsset.originalFileName}`; - mocks.asset.getByIds.mockImplementation((ids) => { - const assets = [assetStub.livePhotoStillAsset, assetStub.livePhotoMotionAsset]; - return Promise.resolve( - ids.map((id) => assets.find((asset) => asset.id === id)).filter((asset) => !!asset), - ) as Promise; - }); + mocks.asset.getStorageTemplateAsset.mockResolvedValueOnce(stillAsset); + mocks.asset.getStorageTemplateAsset.mockResolvedValueOnce(motionAsset); mocks.move.create.mockResolvedValueOnce({ id: '123', - entityId: assetStub.livePhotoStillAsset.id, + entityId: stillAsset.id, pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.livePhotoStillAsset.originalPath, + oldPath: stillAsset.originalPath, newPath: newStillPicturePath, }); mocks.move.create.mockResolvedValueOnce({ id: '124', - entityId: assetStub.livePhotoMotionAsset.id, + entityId: motionAsset.id, pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.livePhotoMotionAsset.originalPath, + oldPath: motionAsset.originalPath, newPath: newMotionPicturePath, }); - await expect(sut.handleMigrationSingle({ id: assetStub.livePhotoStillAsset.id })).resolves.toBe( - JobStatus.SUCCESS, - ); + await expect(sut.handleMigrationSingle({ id: stillAsset.id })).resolves.toBe(JobStatus.SUCCESS); - expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.livePhotoStillAsset.id], { exifInfo: true }); - expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.livePhotoMotionAsset.id], { exifInfo: true }); expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(2); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.livePhotoStillAsset.id, - originalPath: newStillPicturePath, - }); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.livePhotoMotionAsset.id, - originalPath: newMotionPicturePath, - }); + expect(mocks.asset.update).toHaveBeenCalledWith({ id: stillAsset.id, originalPath: newStillPicturePath }); + expect(mocks.asset.update).toHaveBeenCalledWith({ id: motionAsset.id, originalPath: newMotionPicturePath }); }); it('should use handlebar if condition for album', async () => { - const asset = assetStub.image; + const asset = assetStub.storageAsset(); const user = userStub.user1; const album = albumStub.oneAsset; const config = structuredClone(defaults); @@ -157,7 +147,7 @@ describe(StorageTemplateService.name, () => { sut.onConfigInit({ newConfig: config }); mocks.user.get.mockResolvedValue(user); - mocks.asset.getByIds.mockResolvedValueOnce([asset]); + mocks.asset.getStorageTemplateAsset.mockResolvedValueOnce(asset); mocks.album.getByAssetId.mockResolvedValueOnce([album]); expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.SUCCESS); @@ -171,14 +161,14 @@ describe(StorageTemplateService.name, () => { }); it('should use handlebar else condition for album', async () => { - const asset = assetStub.image; + const asset = assetStub.storageAsset(); const user = userStub.user1; const config = structuredClone(defaults); config.storageTemplate.template = '{{y}}/{{#if album}}{{album}}{{else}}other//{{MM}}{{/if}}/{{filename}}'; sut.onConfigInit({ newConfig: config }); mocks.user.get.mockResolvedValue(user); - mocks.asset.getByIds.mockResolvedValueOnce([asset]); + mocks.asset.getStorageTemplateAsset.mockResolvedValueOnce(asset); expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.SUCCESS); @@ -193,153 +183,150 @@ describe(StorageTemplateService.name, () => { it('should migrate previously failed move from original path when it still exists', async () => { mocks.user.get.mockResolvedValue(userStub.user1); - const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`; - const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`; - mocks.storage.checkFileExists.mockImplementation((path) => - Promise.resolve(path === assetStub.image.originalPath), - ); + const asset = assetStub.storageAsset(); + const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${asset.originalFileName}`; + const newPath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${asset.originalFileName}`; + + mocks.storage.checkFileExists.mockImplementation((path) => Promise.resolve(path === asset.originalPath)); mocks.move.getByEntity.mockResolvedValue({ id: '123', - entityId: assetStub.image.id, + entityId: asset.id, pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, + oldPath: asset.originalPath, newPath: previousFailedNewPath, }); - mocks.asset.getByIds.mockResolvedValue([assetStub.image]); + mocks.asset.getStorageTemplateAsset.mockResolvedValue(asset); mocks.move.update.mockResolvedValue({ id: '123', - entityId: assetStub.image.id, + entityId: asset.id, pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, + oldPath: asset.originalPath, newPath, }); - await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS); + await expect(sut.handleMigrationSingle({ id: asset.id })).resolves.toBe(JobStatus.SUCCESS); - expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true }); + expect(mocks.asset.getStorageTemplateAsset).toHaveBeenCalledWith(asset.id); expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(3); - expect(mocks.storage.rename).toHaveBeenCalledWith(assetStub.image.originalPath, newPath); + expect(mocks.storage.rename).toHaveBeenCalledWith(asset.originalPath, newPath); expect(mocks.move.update).toHaveBeenCalledWith('123', { id: '123', - oldPath: assetStub.image.originalPath, + oldPath: asset.originalPath, newPath, }); expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.image.id, + id: asset.id, originalPath: newPath, }); }); it('should migrate previously failed move from previous new path when old path no longer exists, should validate file size still matches before moving', async () => { mocks.user.get.mockResolvedValue(userStub.user1); - const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`; - const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`; + + const asset = assetStub.storageAsset({ fileSizeInByte: 5000 }); + const previousFailedNewPath = `upload/library/${asset.ownerId}/2022/June/${asset.originalFileName}`; + const newPath = `upload/library/${asset.ownerId}/2022/2022-06-19/${asset.originalFileName}`; mocks.storage.checkFileExists.mockImplementation((path) => Promise.resolve(path === previousFailedNewPath)); mocks.storage.stat.mockResolvedValue({ size: 5000 } as Stats); - mocks.crypto.hashFile.mockResolvedValue(assetStub.image.checksum); + mocks.crypto.hashFile.mockResolvedValue(asset.checksum); mocks.move.getByEntity.mockResolvedValue({ id: '123', - entityId: assetStub.image.id, + entityId: asset.id, pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, + oldPath: asset.originalPath, newPath: previousFailedNewPath, }); - mocks.asset.getByIds.mockResolvedValue([assetStub.image]); + mocks.asset.getStorageTemplateAsset.mockResolvedValue(asset); mocks.move.update.mockResolvedValue({ id: '123', - entityId: assetStub.image.id, + entityId: asset.id, pathType: AssetPathType.ORIGINAL, oldPath: previousFailedNewPath, newPath, }); - await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS); + await expect(sut.handleMigrationSingle({ id: asset.id })).resolves.toBe(JobStatus.SUCCESS); - expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true }); + expect(mocks.asset.getStorageTemplateAsset).toHaveBeenCalledWith(asset.id); expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(3); expect(mocks.storage.stat).toHaveBeenCalledWith(previousFailedNewPath); expect(mocks.storage.rename).toHaveBeenCalledWith(previousFailedNewPath, newPath); expect(mocks.storage.copyFile).not.toHaveBeenCalled(); - expect(mocks.move.update).toHaveBeenCalledWith('123', { - id: '123', - oldPath: previousFailedNewPath, - newPath, - }); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.image.id, - originalPath: newPath, - }); + expect(mocks.move.update).toHaveBeenCalledWith('123', { id: '123', oldPath: previousFailedNewPath, newPath }); + expect(mocks.asset.update).toHaveBeenCalledWith({ id: asset.id, originalPath: newPath }); }); it('should fail move if copying and hash of asset and the new file do not match', async () => { mocks.user.get.mockResolvedValue(userStub.user1); - const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`; + const newPath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${testAsset.originalFileName}`; mocks.storage.rename.mockRejectedValue({ code: 'EXDEV' }); mocks.storage.stat.mockResolvedValue({ size: 5000 } as Stats); mocks.crypto.hashFile.mockResolvedValue(Buffer.from('different-hash', 'utf8')); - mocks.asset.getByIds.mockResolvedValue([assetStub.image]); + mocks.asset.getStorageTemplateAsset.mockResolvedValue(testAsset); mocks.move.create.mockResolvedValue({ id: '123', - entityId: assetStub.image.id, + entityId: testAsset.id, pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, + oldPath: testAsset.originalPath, newPath, }); - await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS); + await expect(sut.handleMigrationSingle({ id: testAsset.id })).resolves.toBe(JobStatus.SUCCESS); - expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true }); + expect(mocks.asset.getStorageTemplateAsset).toHaveBeenCalledWith(testAsset.id); expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(1); expect(mocks.storage.stat).toHaveBeenCalledWith(newPath); expect(mocks.move.create).toHaveBeenCalledWith({ - entityId: assetStub.image.id, + entityId: testAsset.id, pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, + oldPath: testAsset.originalPath, newPath, }); - expect(mocks.storage.rename).toHaveBeenCalledWith(assetStub.image.originalPath, newPath); - expect(mocks.storage.copyFile).toHaveBeenCalledWith(assetStub.image.originalPath, newPath); + expect(mocks.storage.rename).toHaveBeenCalledWith(testAsset.originalPath, newPath); + expect(mocks.storage.copyFile).toHaveBeenCalledWith(testAsset.originalPath, newPath); expect(mocks.storage.unlink).toHaveBeenCalledWith(newPath); expect(mocks.storage.unlink).toHaveBeenCalledTimes(1); expect(mocks.asset.update).not.toHaveBeenCalled(); }); + const testAsset = assetStub.storageAsset(); + it.each` - failedPathChecksum | failedPathSize | reason - ${assetStub.image.checksum} | ${500} | ${'file size'} - ${Buffer.from('bad checksum', 'utf8')} | ${assetStub.image.exifInfo?.fileSizeInByte} | ${'checksum'} + failedPathChecksum | failedPathSize | reason + ${testAsset.checksum} | ${500} | ${'file size'} + ${Buffer.from('bad checksum', 'utf8')} | ${testAsset.fileSizeInByte} | ${'checksum'} `( 'should fail to migrate previously failed move from previous new path when old path no longer exists if $reason validation fails', async ({ failedPathChecksum, failedPathSize }) => { mocks.user.get.mockResolvedValue(userStub.user1); - const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`; - const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`; + const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${testAsset.originalFileName}`; + const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${testAsset.originalFileName}`; mocks.storage.checkFileExists.mockImplementation((path) => Promise.resolve(previousFailedNewPath === path)); mocks.storage.stat.mockResolvedValue({ size: failedPathSize } as Stats); mocks.crypto.hashFile.mockResolvedValue(failedPathChecksum); mocks.move.getByEntity.mockResolvedValue({ id: '123', - entityId: assetStub.image.id, + entityId: testAsset.id, pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, + oldPath: testAsset.originalPath, newPath: previousFailedNewPath, }); - mocks.asset.getByIds.mockResolvedValue([assetStub.image]); + mocks.asset.getStorageTemplateAsset.mockResolvedValue(testAsset); mocks.move.update.mockResolvedValue({ id: '123', - entityId: assetStub.image.id, + entityId: testAsset.id, pathType: AssetPathType.ORIGINAL, oldPath: previousFailedNewPath, newPath, }); - await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS); + await expect(sut.handleMigrationSingle({ id: testAsset.id })).resolves.toBe(JobStatus.SUCCESS); - expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true }); + expect(mocks.asset.getStorageTemplateAsset).toHaveBeenCalledWith(testAsset.id); expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(3); expect(mocks.storage.stat).toHaveBeenCalledWith(previousFailedNewPath); expect(mocks.storage.rename).not.toHaveBeenCalled(); @@ -352,29 +339,28 @@ describe(StorageTemplateService.name, () => { describe('handle template migration', () => { it('should handle no assets', async () => { - mocks.asset.getAll.mockResolvedValue({ - items: [], - hasNextPage: false, - }); + mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([])); mocks.user.getList.mockResolvedValue([]); await sut.handleMigration(); - expect(mocks.asset.getAll).toHaveBeenCalled(); + expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled(); }); it('should handle an asset with a duplicate destination', async () => { - mocks.asset.getAll.mockResolvedValue({ - items: [assetStub.image], - hasNextPage: false, - }); + const asset = assetStub.storageAsset(); + const oldPath = asset.originalPath; + const newPath = `upload/library/user-id/2022/2022-06-19/${asset.originalFileName}`; + const newPath2 = newPath.replace('.jpg', '+1.jpg'); + + mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([asset])); mocks.user.getList.mockResolvedValue([userStub.user1]); mocks.move.create.mockResolvedValue({ id: '123', - entityId: assetStub.image.id, + entityId: asset.id, pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, - newPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg', + oldPath, + newPath, }); mocks.storage.checkFileExists.mockResolvedValueOnce(true); @@ -382,30 +368,21 @@ describe(StorageTemplateService.name, () => { await sut.handleMigration(); - expect(mocks.asset.getAll).toHaveBeenCalled(); + expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled(); expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(2); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.image.id, - originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg', - }); + expect(mocks.asset.update).toHaveBeenCalledWith({ id: asset.id, originalPath: newPath2 }); expect(mocks.user.getList).toHaveBeenCalled(); }); it('should skip when an asset already matches the template', async () => { - mocks.asset.getAll.mockResolvedValue({ - items: [ - { - ...assetStub.image, - originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', - }, - ], - hasNextPage: false, - }); + const asset = assetStub.storageAsset({ originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg' }); + + mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([asset])); mocks.user.getList.mockResolvedValue([userStub.user1]); await sut.handleMigration(); - expect(mocks.asset.getAll).toHaveBeenCalled(); + expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled(); expect(mocks.storage.rename).not.toHaveBeenCalled(); expect(mocks.storage.copyFile).not.toHaveBeenCalled(); expect(mocks.storage.checkFileExists).not.toHaveBeenCalledTimes(2); @@ -413,20 +390,14 @@ describe(StorageTemplateService.name, () => { }); it('should skip when an asset is probably a duplicate', async () => { - mocks.asset.getAll.mockResolvedValue({ - items: [ - { - ...assetStub.image, - originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg', - }, - ], - hasNextPage: false, - }); + const asset = assetStub.storageAsset({ originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg' }); + + mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([asset])); mocks.user.getList.mockResolvedValue([userStub.user1]); await sut.handleMigration(); - expect(mocks.asset.getAll).toHaveBeenCalled(); + expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled(); expect(mocks.storage.rename).not.toHaveBeenCalled(); expect(mocks.storage.copyFile).not.toHaveBeenCalled(); expect(mocks.storage.checkFileExists).not.toHaveBeenCalledTimes(2); @@ -434,72 +405,63 @@ describe(StorageTemplateService.name, () => { }); it('should move an asset', async () => { - mocks.asset.getAll.mockResolvedValue({ - items: [assetStub.image], - hasNextPage: false, - }); + const asset = assetStub.storageAsset(); + const oldPath = asset.originalPath; + const newPath = `upload/library/user-id/2022/2022-06-19/${asset.originalFileName}`; + mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([asset])); mocks.user.getList.mockResolvedValue([userStub.user1]); mocks.move.create.mockResolvedValue({ id: '123', entityId: assetStub.image.id, pathType: AssetPathType.ORIGINAL, oldPath: assetStub.image.originalPath, - newPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', + newPath, }); await sut.handleMigration(); - expect(mocks.asset.getAll).toHaveBeenCalled(); - expect(mocks.storage.rename).toHaveBeenCalledWith( - '/original/path.jpg', - 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', - ); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.image.id, - originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', - }); + expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled(); + expect(mocks.storage.rename).toHaveBeenCalledWith(oldPath, newPath); + expect(mocks.asset.update).toHaveBeenCalledWith({ id: asset.id, originalPath: newPath }); }); it('should use the user storage label', async () => { - mocks.asset.getAll.mockResolvedValue({ - items: [assetStub.image], - hasNextPage: false, - }); + const asset = assetStub.storageAsset(); + mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([asset])); mocks.user.getList.mockResolvedValue([userStub.storageLabel]); mocks.move.create.mockResolvedValue({ id: '123', - entityId: assetStub.image.id, + entityId: asset.id, pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, - newPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', + oldPath: asset.originalPath, + newPath: `upload/library/user-id/2023/2023-02-23/${asset.originalFileName}`, }); await sut.handleMigration(); - expect(mocks.asset.getAll).toHaveBeenCalled(); + expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled(); expect(mocks.storage.rename).toHaveBeenCalledWith( '/original/path.jpg', - 'upload/library/label-1/2023/2023-02-23/asset-id.jpg', + `upload/library/label-1/2022/2022-06-19/${asset.originalFileName}`, ); expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.image.id, - originalPath: 'upload/library/label-1/2023/2023-02-23/asset-id.jpg', + id: asset.id, + originalPath: `upload/library/label-1/2022/2022-06-19/${asset.originalFileName}`, }); }); it('should copy the file if rename fails due to EXDEV (rename across filesystems)', async () => { - const newPath = 'upload/library/user-id/2023/2023-02-23/asset-id.jpg'; - mocks.asset.getAll.mockResolvedValue({ - items: [assetStub.image], - hasNextPage: false, - }); + const asset = assetStub.storageAsset({ originalPath: '/path/to/original.jpg', fileSizeInByte: 5000 }); + const oldPath = asset.originalPath; + const newPath = `upload/library/user-id/2022/2022-06-19/${asset.originalFileName}`; + mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([asset])); mocks.storage.rename.mockRejectedValue({ code: 'EXDEV' }); mocks.user.getList.mockResolvedValue([userStub.user1]); mocks.move.create.mockResolvedValue({ id: '123', - entityId: assetStub.image.id, + entityId: asset.id, pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, + oldPath, newPath, }); mocks.storage.stat.mockResolvedValueOnce({ @@ -510,40 +472,36 @@ describe(StorageTemplateService.name, () => { size: 5000, } as Stats); mocks.storage.stat.mockResolvedValueOnce({ + size: 5000, atime: new Date(), mtime: new Date(), } as Stats); - mocks.crypto.hashFile.mockResolvedValue(assetStub.image.checksum); + mocks.crypto.hashFile.mockResolvedValue(asset.checksum); await sut.handleMigration(); - expect(mocks.asset.getAll).toHaveBeenCalled(); - expect(mocks.storage.rename).toHaveBeenCalledWith('/original/path.jpg', newPath); - expect(mocks.storage.copyFile).toHaveBeenCalledWith('/original/path.jpg', newPath); + expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled(); + expect(mocks.storage.rename).toHaveBeenCalledWith(oldPath, newPath); + expect(mocks.storage.copyFile).toHaveBeenCalledWith(oldPath, newPath); + expect(mocks.storage.stat).toHaveBeenCalledWith(oldPath); expect(mocks.storage.stat).toHaveBeenCalledWith(newPath); - expect(mocks.storage.stat).toHaveBeenCalledWith(assetStub.image.originalPath); expect(mocks.storage.utimes).toHaveBeenCalledWith(newPath, expect.any(Date), expect.any(Date)); - expect(mocks.storage.unlink).toHaveBeenCalledWith(assetStub.image.originalPath); + expect(mocks.storage.unlink).toHaveBeenCalledWith(oldPath); expect(mocks.storage.unlink).toHaveBeenCalledTimes(1); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.image.id, - originalPath: newPath, - }); + expect(mocks.asset.update).toHaveBeenCalledWith({ id: asset.id, originalPath: newPath }); }); it('should not update the database if the move fails due to incorrect newPath filesize', async () => { - mocks.asset.getAll.mockResolvedValue({ - items: [assetStub.image], - hasNextPage: false, - }); + const asset = assetStub.storageAsset(); + mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([asset])); mocks.storage.rename.mockRejectedValue({ code: 'EXDEV' }); mocks.user.getList.mockResolvedValue([userStub.user1]); mocks.move.create.mockResolvedValue({ id: '123', - entityId: assetStub.image.id, + entityId: asset.id, pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, - newPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', + oldPath: asset.originalPath, + newPath: `upload/library/user-id/2022/2022-06-19/${asset.originalFileName}`, }); mocks.storage.stat.mockResolvedValue({ size: 100, @@ -551,41 +509,41 @@ describe(StorageTemplateService.name, () => { await sut.handleMigration(); - expect(mocks.asset.getAll).toHaveBeenCalled(); + expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled(); expect(mocks.storage.rename).toHaveBeenCalledWith( '/original/path.jpg', - 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', + `upload/library/user-id/2022/2022-06-19/${asset.originalFileName}`, ); expect(mocks.storage.copyFile).toHaveBeenCalledWith( '/original/path.jpg', - 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', + `upload/library/user-id/2022/2022-06-19/${asset.originalFileName}`, + ); + expect(mocks.storage.stat).toHaveBeenCalledWith( + `upload/library/user-id/2022/2022-06-19/${asset.originalFileName}`, ); - expect(mocks.storage.stat).toHaveBeenCalledWith('upload/library/user-id/2023/2023-02-23/asset-id.jpg'); expect(mocks.asset.update).not.toHaveBeenCalled(); }); it('should not update the database if the move fails', async () => { - mocks.asset.getAll.mockResolvedValue({ - items: [assetStub.image], - hasNextPage: false, - }); + const asset = assetStub.storageAsset(); + mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([asset])); mocks.storage.rename.mockRejectedValue(new Error('Read only system')); mocks.storage.copyFile.mockRejectedValue(new Error('Read only system')); mocks.move.create.mockResolvedValue({ id: 'move-123', - entityId: '123', + entityId: asset.id, pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, + oldPath: asset.originalPath, newPath: '', }); mocks.user.getList.mockResolvedValue([userStub.user1]); await sut.handleMigration(); - expect(mocks.asset.getAll).toHaveBeenCalled(); + expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled(); expect(mocks.storage.rename).toHaveBeenCalledWith( '/original/path.jpg', - 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', + `upload/library/user-id/2022/2022-06-19/${asset.originalFileName}`, ); expect(mocks.asset.update).not.toHaveBeenCalled(); }); diff --git a/server/src/services/storage-template.service.ts b/server/src/services/storage-template.service.ts index 6a1548ff20..1a0d4f4644 100644 --- a/server/src/services/storage-template.service.ts +++ b/server/src/services/storage-template.service.ts @@ -3,17 +3,14 @@ import handlebar from 'handlebars'; import { DateTime } from 'luxon'; import path from 'node:path'; import sanitize from 'sanitize-filename'; -import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; import { OnEvent, OnJob } from 'src/decorators'; import { SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config.dto'; -import { AssetEntity } from 'src/entities/asset.entity'; import { AssetPathType, AssetType, DatabaseLock, JobName, JobStatus, QueueName, StorageFolder } from 'src/enum'; import { ArgOf } from 'src/repositories/event.repository'; import { BaseService } from 'src/services/base.service'; -import { JobOf } from 'src/types'; +import { JobOf, StorageAsset } from 'src/types'; import { getLivePhotoMotionFilename } from 'src/utils/file'; -import { usePagination } from 'src/utils/pagination'; const storageTokens = { secondOptions: ['s', 'ss', 'SSS'], @@ -53,7 +50,7 @@ export interface MoveAssetMetadata { } interface RenderMetadata { - asset: AssetEntity; + asset: StorageAsset; filename: string; extension: string; albumName: string | null; @@ -98,7 +95,7 @@ export class StorageTemplateService extends BaseService { originalPath: '/upload/test/IMG_123.jpg', type: AssetType.IMAGE, id: 'd587e44b-f8c0-4832-9ba3-43268bbf5d4e', - } as AssetEntity, + } as StorageAsset, filename: 'IMG_123', extension: 'jpg', albumName: 'album', @@ -121,7 +118,7 @@ export class StorageTemplateService extends BaseService { return JobStatus.SKIPPED; } - const [asset] = await this.assetRepository.getByIds([id], { exifInfo: true }); + const asset = await this.assetRepository.getStorageTemplateAsset(id); if (!asset) { return JobStatus.FAILED; } @@ -133,7 +130,7 @@ export class StorageTemplateService extends BaseService { // move motion part of live photo if (asset.livePhotoVideoId) { - const [livePhotoVideo] = await this.assetRepository.getByIds([asset.livePhotoVideoId], { exifInfo: true }); + const livePhotoVideo = await this.assetRepository.getStorageTemplateAsset(asset.livePhotoVideoId); if (!livePhotoVideo) { return JobStatus.FAILED; } @@ -152,18 +149,17 @@ export class StorageTemplateService extends BaseService { this.logger.log('Storage template migration disabled, skipping'); return JobStatus.SKIPPED; } - const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => - this.assetRepository.getAll(pagination, { withExif: true, withArchived: true }), - ); + + await this.moveRepository.cleanMoveHistory(); + + const assets = this.assetRepository.streamStorageTemplateAssets(); const users = await this.userRepository.getList(); - for await (const assets of assetPagination) { - for (const asset of assets) { - const user = users.find((user) => user.id === asset.ownerId); - const storageLabel = user?.storageLabel || null; - const filename = asset.originalFileName || asset.id; - await this.moveAsset(asset, { storageLabel, filename }); - } + for await (const asset of assets) { + const user = users.find((user) => user.id === asset.ownerId); + const storageLabel = user?.storageLabel || null; + const filename = asset.originalFileName || asset.id; + await this.moveAsset(asset, { storageLabel, filename }); } this.logger.debug('Cleaning up empty directories...'); @@ -175,7 +171,13 @@ export class StorageTemplateService extends BaseService { return JobStatus.SUCCESS; } - async moveAsset(asset: AssetEntity, metadata: MoveAssetMetadata) { + @OnEvent({ name: 'asset.delete' }) + async handleMoveHistoryCleanup({ assetId }: ArgOf<'asset.delete'>) { + this.logger.debug(`Cleaning up move history for asset ${assetId}`); + await this.moveRepository.cleanMoveHistorySingle(assetId); + } + + async moveAsset(asset: StorageAsset, metadata: MoveAssetMetadata) { if (asset.isExternal || StorageCore.isAndroidMotionPath(asset.originalPath)) { // External assets are not affected by storage template // TODO: shouldn't this only apply to external assets? @@ -183,11 +185,11 @@ export class StorageTemplateService extends BaseService { } return this.databaseRepository.withLock(DatabaseLock.StorageTemplateMigration, async () => { - const { id, sidecarPath, originalPath, exifInfo, checksum } = asset; + const { id, sidecarPath, originalPath, checksum, fileSizeInByte } = asset; const oldPath = originalPath; const newPath = await this.getTemplatePath(asset, metadata); - if (!exifInfo || !exifInfo.fileSizeInByte) { + if (!fileSizeInByte) { this.logger.error(`Asset ${id} missing exif info, skipping storage template migration`); return; } @@ -198,7 +200,7 @@ export class StorageTemplateService extends BaseService { pathType: AssetPathType.ORIGINAL, oldPath, newPath, - assetInfo: { sizeInBytes: exifInfo.fileSizeInByte, checksum }, + assetInfo: { sizeInBytes: fileSizeInByte, checksum }, }); if (sidecarPath) { await this.storageCore.moveFile({ @@ -214,15 +216,42 @@ export class StorageTemplateService extends BaseService { }); } - private async getTemplatePath(asset: AssetEntity, metadata: MoveAssetMetadata): Promise { + private async getTemplatePath(asset: StorageAsset, metadata: MoveAssetMetadata): Promise { const { storageLabel, filename } = metadata; try { const source = asset.originalPath; - const extension = path.extname(source).split('.').pop() as string; + let extension = path.extname(source).split('.').pop() as string; const sanitized = sanitize(path.basename(filename, `.${extension}`)); + extension = extension?.toLowerCase(); const rootPath = StorageCore.getLibraryFolder({ id: asset.ownerId, storageLabel }); + switch (extension) { + case 'jpeg': + case 'jpe': { + extension = 'jpg'; + break; + } + case 'tif': { + extension = 'tiff'; + break; + } + case '3gpp': { + extension = '3gp'; + break; + } + case 'mpeg': + case 'mpe': { + extension = 'mpg'; + break; + } + case 'm2ts': + case 'm2t': { + extension = 'mts'; + break; + } + } + let albumName = null; if (this.template.needsAlbum) { const albums = await this.albumRepository.getByAssetId(asset.ownerId, asset.id); @@ -310,7 +339,7 @@ export class StorageTemplateService extends BaseService { }; const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const zone = asset.exifInfo?.timeZone || systemTimeZone; + const zone = asset.timeZone || systemTimeZone; const dt = DateTime.fromJSDate(asset.fileCreatedAt, { zone }); for (const token of Object.values(storageTokens).flat()) { diff --git a/server/src/services/sync.service.spec.ts b/server/src/services/sync.service.spec.ts index d5e53c83a2..27a54b2b58 100644 --- a/server/src/services/sync.service.spec.ts +++ b/server/src/services/sync.service.spec.ts @@ -3,7 +3,7 @@ import { AssetEntity } from 'src/entities/asset.entity'; import { SyncService } from 'src/services/sync.service'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; -import { partnerStub } from 'test/fixtures/partner.stub'; +import { factory } from 'test/small.factory'; import { newTestService, ServiceMocks } from 'test/utils'; const untilDate = new Date(2024); @@ -38,10 +38,15 @@ describe(SyncService.name, () => { describe('getChangesForDeltaSync', () => { it('should return a response requiring a full sync when partners are out of sync', async () => { - mocks.partner.getAll.mockResolvedValue([partnerStub.adminToUser1]); + const partner = factory.partner(); + const auth = factory.auth({ id: partner.sharedWithId }); + + mocks.partner.getAll.mockResolvedValue([partner]); + await expect( - sut.getDeltaSync(authStub.user1, { updatedAfter: new Date(), userIds: [authStub.user1.user.id] }), + sut.getDeltaSync(authStub.user1, { updatedAfter: new Date(), userIds: [auth.user.id] }), ).resolves.toEqual({ needsFullSync: true, upserted: [], deleted: [] }); + expect(mocks.asset.getChangedDeltaSync).toHaveBeenCalledTimes(0); expect(mocks.audit.getAfter).toHaveBeenCalledTimes(0); }); diff --git a/server/src/services/sync.service.ts b/server/src/services/sync.service.ts index 45b1b7ff84..c88348b39e 100644 --- a/server/src/services/sync.service.ts +++ b/server/src/services/sync.service.ts @@ -4,7 +4,7 @@ import { DateTime } from 'luxon'; import { Writable } from 'node:stream'; import { AUDIT_LOG_MAX_DURATION } from 'src/constants'; import { SessionSyncCheckpoints } from 'src/db'; -import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; +import { AssetResponseDto, hexOrBufferToBase64, mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { AssetDeltaSyncDto, @@ -22,10 +22,14 @@ import { setIsEqual } from 'src/utils/set'; import { fromAck, serialize } from 'src/utils/sync'; const FULL_SYNC = { needsFullSync: true, deleted: [], upserted: [] }; -const SYNC_TYPES_ORDER = [ +export const SYNC_TYPES_ORDER = [ // SyncRequestType.UsersV1, SyncRequestType.PartnersV1, + SyncRequestType.AssetsV1, + SyncRequestType.AssetExifsV1, + SyncRequestType.PartnerAssetsV1, + SyncRequestType.PartnerAssetExifsV1, ]; const throwSessionRequired = () => { @@ -49,17 +53,22 @@ export class SyncService extends BaseService { return throwSessionRequired(); } - const checkpoints: Insertable[] = []; + const checkpoints: Record> = {}; for (const ack of dto.acks) { const { type } = fromAck(ack); // TODO proper ack validation via class validator if (!Object.values(SyncEntityType).includes(type)) { throw new BadRequestException(`Invalid ack type: ${type}`); } - checkpoints.push({ sessionId, type, ack }); + + if (checkpoints[type]) { + throw new BadRequestException('Only one ack per type is allowed'); + } + + checkpoints[type] = { sessionId, type, ack }; } - await this.syncRepository.upsertCheckpoints(checkpoints); + await this.syncRepository.upsertCheckpoints(Object.values(checkpoints)); } async deleteAcks(auth: AuthDto, dto: SyncAckDeleteDto) { @@ -115,6 +124,87 @@ export class SyncService extends BaseService { break; } + case SyncRequestType.AssetsV1: { + const deletes = this.syncRepository.getAssetDeletes( + auth.user.id, + checkpointMap[SyncEntityType.AssetDeleteV1], + ); + for await (const { id, ...data } of deletes) { + response.write(serialize({ type: SyncEntityType.AssetDeleteV1, updateId: id, data })); + } + + const upserts = this.syncRepository.getAssetUpserts(auth.user.id, checkpointMap[SyncEntityType.AssetV1]); + for await (const { updateId, checksum, thumbhash, ...data } of upserts) { + response.write( + serialize({ + type: SyncEntityType.AssetV1, + updateId, + data: { + ...data, + checksum: hexOrBufferToBase64(checksum), + thumbhash: thumbhash ? hexOrBufferToBase64(thumbhash) : null, + }, + }), + ); + } + + break; + } + + case SyncRequestType.PartnerAssetsV1: { + const deletes = this.syncRepository.getPartnerAssetDeletes( + auth.user.id, + checkpointMap[SyncEntityType.PartnerAssetDeleteV1], + ); + for await (const { id, ...data } of deletes) { + response.write(serialize({ type: SyncEntityType.PartnerAssetDeleteV1, updateId: id, data })); + } + + const upserts = this.syncRepository.getPartnerAssetsUpserts( + auth.user.id, + checkpointMap[SyncEntityType.PartnerAssetV1], + ); + for await (const { updateId, checksum, thumbhash, ...data } of upserts) { + response.write( + serialize({ + type: SyncEntityType.PartnerAssetV1, + updateId, + data: { + ...data, + checksum: hexOrBufferToBase64(checksum), + thumbhash: thumbhash ? hexOrBufferToBase64(thumbhash) : null, + }, + }), + ); + } + + break; + } + + case SyncRequestType.AssetExifsV1: { + const upserts = this.syncRepository.getAssetExifsUpserts( + auth.user.id, + checkpointMap[SyncEntityType.AssetExifV1], + ); + for await (const { updateId, ...data } of upserts) { + response.write(serialize({ type: SyncEntityType.AssetExifV1, updateId, data })); + } + + break; + } + + case SyncRequestType.PartnerAssetExifsV1: { + const upserts = this.syncRepository.getPartnerAssetExifsUpserts( + auth.user.id, + checkpointMap[SyncEntityType.PartnerAssetExifV1], + ); + for await (const { updateId, ...data } of upserts) { + response.write(serialize({ type: SyncEntityType.PartnerAssetExifV1, updateId, data })); + } + + break; + } + default: { this.logger.warn(`Unsupported sync type: ${type}`); break; diff --git a/server/src/services/tag.service.spec.ts b/server/src/services/tag.service.spec.ts index 22d7747d0d..70507ab433 100644 --- a/server/src/services/tag.service.spec.ts +++ b/server/src/services/tag.service.spec.ts @@ -87,9 +87,12 @@ describe(TagService.name, () => { it('should create a new tag with optional color', async () => { mocks.tag.create.mockResolvedValue(tagStub.colorCreate); + mocks.tag.getByValue.mockResolvedValue(void 0); + await expect(sut.create(authStub.admin, { name: 'tag-1', color: '#000000' })).resolves.toEqual( tagResponseStub.color1, ); + expect(mocks.tag.create).toHaveBeenCalledWith({ userId: authStub.admin.user.id, value: 'tag-1', @@ -168,6 +171,8 @@ describe(TagService.name, () => { it('should remove a tag', async () => { mocks.tag.get.mockResolvedValue(tagStub.tag); + mocks.tag.delete.mockResolvedValue(); + await sut.remove(authStub.admin, 'tag-1'); expect(mocks.tag.delete).toHaveBeenCalledWith('tag-1'); }); @@ -223,6 +228,7 @@ describe(TagService.name, () => { it('should accept accept ids that are new and reject the rest', async () => { mocks.tag.get.mockResolvedValue(tagStub.tag); mocks.tag.getAssetIds.mockResolvedValue(new Set(['asset-1'])); + mocks.tag.addAssetIds.mockResolvedValue(); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-2'])); await expect( @@ -242,6 +248,8 @@ describe(TagService.name, () => { describe('removeAssets', () => { it('should throw an error for an invalid id', async () => { mocks.tag.getAssetIds.mockResolvedValue(new Set()); + mocks.tag.removeAssetIds.mockResolvedValue(); + await expect(sut.removeAssets(authStub.admin, 'tag-1', { ids: ['asset-1'] })).resolves.toEqual([ { id: 'asset-1', success: false, error: 'not_found' }, ]); @@ -250,6 +258,7 @@ describe(TagService.name, () => { it('should accept accept ids that are tagged and reject the rest', async () => { mocks.tag.get.mockResolvedValue(tagStub.tag); mocks.tag.getAssetIds.mockResolvedValue(new Set(['asset-1'])); + mocks.tag.removeAssetIds.mockResolvedValue(); await expect( sut.removeAssets(authStub.admin, 'tag-1', { @@ -267,7 +276,10 @@ describe(TagService.name, () => { describe('handleTagCleanup', () => { it('should delete empty tags', async () => { + mocks.tag.deleteEmptyTags.mockResolvedValue(); + await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.SUCCESS); + expect(mocks.tag.deleteEmptyTags).toHaveBeenCalled(); }); }); diff --git a/server/src/services/timeline.service.spec.ts b/server/src/services/timeline.service.spec.ts index 749633998b..1c2c422433 100644 --- a/server/src/services/timeline.service.spec.ts +++ b/server/src/services/timeline.service.spec.ts @@ -70,6 +70,7 @@ describe(TimelineService.name, () => { it('should include partner shared assets', async () => { mocks.asset.getTimeBucket.mockResolvedValue([assetStub.image]); + mocks.partner.getAll.mockResolvedValue([]); await expect( sut.getTimeBucket(authStub.admin, { diff --git a/server/src/services/timeline.service.ts b/server/src/services/timeline.service.ts index 4c2332afaa..3f806a7e46 100644 --- a/server/src/services/timeline.service.ts +++ b/server/src/services/timeline.service.ts @@ -1,11 +1,12 @@ import { BadRequestException, Injectable } from '@nestjs/common'; -import { AssetResponseDto, SanitizedAssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; +import { AssetResponseDto, SanitizedAssetResponseDto, hexOrBufferToBase64, mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { TimeBucketAssetDto, TimeBucketDto, TimeBucketResponseDto } from 'src/dtos/time-bucket.dto'; +import { LiteTimeBucketAssetDto, LiteTimeBucketAssetResponseDto, LiteTimeBucketDto, LiteTimeBucketResponseDto, TimeBucketAssetDto, TimeBucketDto, TimeBucketResponseDto } from 'src/dtos/time-bucket.dto'; import { Permission } from 'src/enum'; -import { TimeBucketOptions } from 'src/repositories/asset.repository'; +import { TimeBucketOptions, TimeBucketSize } from 'src/repositories/asset.repository'; import { BaseService } from 'src/services/base.service'; import { getMyPartnerIds } from 'src/utils/asset.util'; +import { PaginationResult } from 'src/utils/pagination'; @Injectable() export class TimelineService extends BaseService { @@ -27,6 +28,18 @@ export class TimelineService extends BaseService { : assets.map((asset) => mapAsset(asset, { stripMetadata: true, auth })); } + async getLiteTimeBucket( + auth: AuthDto, + dto: LiteTimeBucketAssetDto, + ) { + await this.timeBucketChecks(auth, dto); + const timeBucketOptions = await this.buildTimeBucketOptions(auth, { ...dto, size: TimeBucketSize.MONTH }); + + const page = dto.page; + const size = 10; + return await this.assetRepository.getLiteTimeBucket({ skip: page, take: size }, dto.timeBucket, timeBucketOptions); + } + private async buildTimeBucketOptions(auth: AuthDto, dto: TimeBucketDto): Promise { const { userId, ...options } = dto; let userIds: string[] | undefined = undefined; @@ -46,7 +59,7 @@ export class TimelineService extends BaseService { return { ...options, userIds }; } - private async timeBucketChecks(auth: AuthDto, dto: TimeBucketDto) { + private async timeBucketChecks(auth: AuthDto, dto: TimeBucketDto | LiteTimeBucketDto) { if (dto.albumId) { await this.requireAccess({ auth, permission: Permission.ALBUM_READ, ids: [dto.albumId] }); } else { diff --git a/server/src/services/trash.service.spec.ts b/server/src/services/trash.service.spec.ts index e7eccd374c..b3bee90815 100644 --- a/server/src/services/trash.service.spec.ts +++ b/server/src/services/trash.service.spec.ts @@ -39,6 +39,7 @@ describe(TrashService.name, () => { it('should restore a batch of assets', async () => { mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1', 'asset2'])); + mocks.trash.restoreAll.mockResolvedValue(0); await sut.restoreAssets(authStub.user1, { ids: ['asset1', 'asset2'] }); diff --git a/server/src/services/version.service.spec.ts b/server/src/services/version.service.spec.ts index 500478e9e7..a83d9f85b6 100644 --- a/server/src/services/version.service.spec.ts +++ b/server/src/services/version.service.spec.ts @@ -4,6 +4,7 @@ import { serverVersion } from 'src/constants'; import { ImmichEnvironment, JobName, JobStatus, SystemMetadataKey } from 'src/enum'; import { VersionService } from 'src/services/version.service'; import { mockEnvData } from 'test/repositories/config.repository.mock'; +import { factory } from 'test/small.factory'; import { newTestService, ServiceMocks } from 'test/utils'; const mockRelease = (version: string) => ({ @@ -30,7 +31,12 @@ describe(VersionService.name, () => { describe('onBootstrap', () => { it('should record a new version', async () => { + mocks.versionHistory.getAll.mockResolvedValue([]); + mocks.versionHistory.getLatest.mockResolvedValue(void 0); + mocks.versionHistory.create.mockResolvedValue(factory.versionHistory()); + await expect(sut.onBootstrap()).resolves.toBeUndefined(); + expect(mocks.versionHistory.create).toHaveBeenCalledWith({ version: expect.any(String) }); }); diff --git a/server/src/types.ts b/server/src/types.ts index 41d98f9a02..1c0a61b259 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -1,4 +1,5 @@ import { + AssetType, DatabaseExtension, ExifOrientation, ImageFormat, @@ -208,17 +209,23 @@ export interface IAssetDeleteJob extends IEntityJob { deleteOnDisk: boolean; } -export interface ILibraryFileJob extends IEntityJob { - ownerId: string; - assetPath: string; +export interface ILibraryFileJob { + libraryId: string; + paths: string[]; + progressCounter?: number; + totalAssets?: number; } -export interface ILibraryAssetJob extends IEntityJob { +export interface ILibraryBulkIdsJob { + libraryId: string; importPaths: string[]; exclusionPatterns: string[]; + assetIds: string[]; + progressCounter: number; + totalAssets: number; } -export interface IBulkEntityJob extends IBaseJob { +export interface IBulkEntityJob { ids: string[]; } @@ -354,10 +361,11 @@ export type JobItem = | { name: JobName.ASSET_DELETION_CHECK; data?: IBaseJob } // Library Management - | { name: JobName.LIBRARY_SYNC_FILE; data: ILibraryFileJob } + | { name: JobName.LIBRARY_SYNC_FILES; data: ILibraryFileJob } | { name: JobName.LIBRARY_QUEUE_SYNC_FILES; data: IEntityJob } | { name: JobName.LIBRARY_QUEUE_SYNC_ASSETS; data: IEntityJob } - | { name: JobName.LIBRARY_SYNC_ASSET; data: ILibraryAssetJob } + | { name: JobName.LIBRARY_SYNC_ASSETS; data: ILibraryBulkIdsJob } + | { name: JobName.LIBRARY_ASSET_REMOVAL; data: ILibraryFileJob } | { name: JobName.LIBRARY_DELETE; data: IEntityJob } | { name: JobName.LIBRARY_QUEUE_SCAN_ALL; data?: IBaseJob } | { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob } @@ -431,3 +439,18 @@ export type SyncAck = { type: SyncEntityType; updateId: string; }; + +export type StorageAsset = { + id: string; + ownerId: string; + livePhotoVideoId: string | null; + type: AssetType; + isExternal: boolean; + checksum: Buffer; + timeZone: string | null; + fileCreatedAt: Date; + originalPath: string; + originalFileName: string; + sidecarPath: string | null; + fileSizeInByte: number | null; +}; diff --git a/server/src/utils/asset.util.ts b/server/src/utils/asset.util.ts index de64720a82..4b954df7f3 100644 --- a/server/src/utils/asset.util.ts +++ b/server/src/utils/asset.util.ts @@ -196,3 +196,17 @@ export const asRequest = (request: AuthRequest, file: Express.Multer.File) => { file: mapToUploadFile(file as ImmichFile), }; }; + + +function isRotated90CW(orientation: number) { + return orientation === 5 || orientation === 6 || orientation === 90; +} + +function isRotated270CW(orientation: number) { + return orientation === 7 || orientation === 8 || orientation === -90; +} + +export function isFlipped(orientation?: string | null) { + const value = Number(orientation); + return value && (isRotated270CW(value) || isRotated90CW(value)); +} \ No newline at end of file diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts index 7483ef6f92..456165063c 100644 --- a/server/src/utils/database.ts +++ b/server/src/utils/database.ts @@ -1,6 +1,4 @@ -import { Expression, RawBuilder, sql, ValueExpression } from 'kysely'; -import { InsertObject } from 'node_modules/kysely/dist/cjs'; -import { DB } from 'src/db'; +import { Expression, sql } from 'kysely'; import { Between, LessThanOrEqual, MoreThanOrEqual } from 'typeorm'; /** @@ -17,33 +15,24 @@ export function OptionalBetween(from?: T, to?: T) { } } -// populated by the database repository at bootstrap -export const UPSERT_COLUMNS = {} as { [T in keyof DB]: { [K in keyof DB[T]]: RawBuilder } }; - -/** Generates the columns for an upsert statement, excluding the conflict keys. - * Assumes that all entries have the same keys. */ -export function mapUpsertColumns( - table: T, - entry: InsertObject, - conflictKeys: readonly (keyof DB[T])[], -) { - const columns = UPSERT_COLUMNS[table] as { [K in keyof DB[T]]: RawBuilder }; - const upsertColumns: Partial>> = {}; - for (const entryColumn in entry) { - if (!conflictKeys.includes(entryColumn as keyof DB[T])) { - upsertColumns[entryColumn as keyof typeof entry] = columns[entryColumn as keyof DB[T]]; - } - } - - return upsertColumns as Expand>>; -} - export const asUuid = (id: string | Expression) => sql`${id}::uuid`; export const anyUuid = (ids: string[]) => sql`any(${`{${ids}}`}::uuid[])`; export const asVector = (embedding: number[]) => sql`${`[${embedding}]`}::vector`; +export const unnest = (array: string[]) => sql>`unnest(array[${sql.join(array)}]::text[])`; + +export const removeUndefinedKeys = (update: T, template: unknown) => { + for (const key in update) { + if ((template as T)[key] === undefined) { + delete update[key]; + } + } + + return update; +}; + /** * Mainly for type debugging to make VS Code display a more useful tooltip. * Source: https://stackoverflow.com/a/69288824 diff --git a/server/src/utils/date.ts b/server/src/utils/date.ts new file mode 100644 index 0000000000..67ce549050 --- /dev/null +++ b/server/src/utils/date.ts @@ -0,0 +1,3 @@ +export const asDateString = (x: Date | string | null): string | null => { + return x instanceof Date ? x.toISOString().split('T')[0] : x; +}; diff --git a/server/src/utils/file.ts b/server/src/utils/file.ts index 915c60de2a..716e0b1957 100644 --- a/server/src/utils/file.ts +++ b/server/src/utils/file.ts @@ -33,27 +33,28 @@ export class ImmichFileResponse { type SendFile = Parameters; type SendFileOptions = SendFile[1]; +const cacheControlHeaders: Record = { + [CacheControl.PRIVATE_WITH_CACHE]: 'private, max-age=86400, no-transform', + [CacheControl.PRIVATE_WITHOUT_CACHE]: 'private, no-cache, no-transform', + [CacheControl.NONE]: null, // falsy value to prevent adding Cache-Control header +}; + export const sendFile = async ( res: Response, next: NextFunction, handler: () => Promise, logger: LoggingRepository, ): Promise => { + // promisified version of 'res.sendFile' for cleaner async handling const _sendFile = (path: string, options: SendFileOptions) => promisify(res.sendFile).bind(res)(path, options); try { const file = await handler(); - switch (file.cacheControl) { - case CacheControl.PRIVATE_WITH_CACHE: { - res.set('Cache-Control', 'private, max-age=86400, no-transform'); - break; - } - - case CacheControl.PRIVATE_WITHOUT_CACHE: { - res.set('Cache-Control', 'private, no-cache, no-transform'); - break; - } + const cacheControlHeader = cacheControlHeaders[file.cacheControl]; + if (cacheControlHeader) { + // set the header to Cache-Control + res.set('Cache-Control', cacheControlHeader); } res.header('Content-Type', file.contentType); @@ -61,6 +62,7 @@ export const sendFile = async ( res.header('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(file.fileName)}`); } + // configure options for serving const options: SendFileOptions = { dotfiles: 'allow' }; if (!isAbsolute(file.path)) { options.root = process.cwd(); diff --git a/server/src/utils/misc.spec.ts b/server/src/utils/misc.spec.ts index 53be77dc21..3c45482938 100644 --- a/server/src/utils/misc.spec.ts +++ b/server/src/utils/misc.spec.ts @@ -1,4 +1,4 @@ -import { getKeysDeep, unsetDeep } from 'src/utils/misc'; +import { getKeysDeep, globToSqlPattern, unsetDeep } from 'src/utils/misc'; import { describe, expect, it } from 'vitest'; describe('getKeysDeep', () => { @@ -51,3 +51,19 @@ describe('unsetDeep', () => { expect(unsetDeep({ foo: 'bar', nested: { enabled: true } }, 'nested.enabled')).toEqual({ foo: 'bar' }); }); }); + +describe('globToSqlPattern', () => { + const testCases = [ + ['**/Raw/**', '%/Raw/%'], + ['**/abc/*.tif', '%/abc/%.tif'], + ['**/*.tif', '%/%.tif'], + ['**/*.jp?', '%/%.jp_'], + ['**/@eaDir/**', '%/@eaDir/%'], + ['**/._*', `%/._%`], + ['/absolute/path/**', `/absolute/path/%`], + ]; + + it.each(testCases)('should convert %s to %s', (input, expected) => { + expect(globToSqlPattern(input)).toEqual(expected); + }); +}); diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index e07d0fe03f..b0c4fd955f 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -10,6 +10,8 @@ import { ReferenceObject, SchemaObject } from '@nestjs/swagger/dist/interfaces/o import _ from 'lodash'; import { writeFileSync } from 'node:fs'; import path from 'node:path'; +import picomatch from 'picomatch'; +import parse from 'picomatch/lib/parse'; import { SystemConfig } from 'src/config'; import { CLIP_MODEL_INFO, serverVersion } from 'src/constants'; import { extraSyncModels } from 'src/dtos/sync.dto'; @@ -268,3 +270,35 @@ export const useSwagger = (app: INestApplication, { write }: { write: boolean }) writeFileSync(outputPath, JSON.stringify(patchOpenAPI(specification), null, 2), { encoding: 'utf8' }); } }; + +const convertTokenToSqlPattern = (token: parse.Token): string => { + switch (token.type) { + case 'slash': { + return '/'; + } + case 'text': { + return token.value; + } + case 'globstar': + case 'star': { + return '%'; + } + case 'underscore': { + return String.raw`\_`; + } + case 'qmark': { + return '_'; + } + case 'dot': { + return '.'; + } + default: { + return ''; + } + } +}; + +export const globToSqlPattern = (glob: string) => { + const tokens = picomatch.parse(glob).tokens; + return tokens.map((token) => convertTokenToSqlPattern(token)).join(''); +}; diff --git a/server/src/utils/pagination.ts b/server/src/utils/pagination.ts index 7cb31d1e04..eb4106c86a 100644 --- a/server/src/utils/pagination.ts +++ b/server/src/utils/pagination.ts @@ -10,6 +10,7 @@ export interface PaginationResult { export type Paginated = Promise>; +/** @deprecated use `this.db. ... .stream()` instead */ export async function* usePagination( pageSize: number, getNextPage: (pagination: PaginationOptions) => PaginationResult | Paginated, diff --git a/server/test/factory.ts b/server/test/factory.ts index 8811b08628..520119fc3e 100644 --- a/server/test/factory.ts +++ b/server/test/factory.ts @@ -1,5 +1,5 @@ import { Insertable, Kysely } from 'kysely'; -import { randomBytes, randomUUID } from 'node:crypto'; +import { randomBytes } from 'node:crypto'; import { Writable } from 'node:stream'; import { Assets, DB, Partners, Sessions, Users } from 'src/db'; import { AuthDto } from 'src/dtos/auth.dto'; @@ -34,8 +34,9 @@ import { TrashRepository } from 'src/repositories/trash.repository'; import { UserRepository } from 'src/repositories/user.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; import { ViewRepository } from 'src/repositories/view-repository'; -import { newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock'; +import { newUuid } from 'test/small.factory'; +import { automock } from 'test/utils'; class CustomWritable extends Writable { private data = ''; @@ -54,13 +55,11 @@ class CustomWritable extends Writable { } } -type Asset = Insertable; +type Asset = Partial>; type User = Partial>; type Session = Omit, 'token'> & { token?: string }; type Partner = Insertable; -export const newUuid = () => randomUUID() as string; - export class TestFactory { private assets: Asset[] = []; private sessions: Session[] = []; @@ -161,10 +160,6 @@ export class TestFactory { } async create() { - for (const asset of this.assets) { - await this.context.createAsset(asset); - } - for (const user of this.users) { await this.context.createUser(user); } @@ -177,6 +172,10 @@ export class TestFactory { await this.context.createSession(session); } + for (const asset of this.assets) { + await this.context.createAsset(asset); + } + return this.context; } } @@ -213,8 +212,8 @@ export class TestContext { versionHistory: VersionHistoryRepository; view: ViewRepository; - private constructor(private db: Kysely) { - const logger = newLoggingRepositoryMock() as unknown as LoggingRepository; + private constructor(public db: Kysely) { + const logger = automock(LoggingRepository, { args: [, { getEnv: () => ({}) }], strict: false }); const config = new ConfigRepository(); this.access = new AccessRepository(this.db); diff --git a/server/test/fixtures/activity.stub.ts b/server/test/fixtures/activity.stub.ts deleted file mode 100644 index a81fd51ca8..0000000000 --- a/server/test/fixtures/activity.stub.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ActivityItem } from 'src/types'; -import { albumStub } from 'test/fixtures/album.stub'; -import { assetStub } from 'test/fixtures/asset.stub'; - -export const activityStub = { - oneComment: Object.freeze({ - id: 'activity-1', - comment: 'comment', - isLiked: false, - userId: 'admin_id', - user: { - id: 'admin_id', - name: 'admin', - email: 'admin@test.com', - profileImagePath: '', - profileChangedAt: new Date('2021-01-01'), - }, - assetId: assetStub.image.id, - albumId: albumStub.oneAsset.id, - createdAt: new Date(), - updatedAt: new Date(), - updateId: 'uuid-v7', - }), - liked: Object.freeze({ - id: 'activity-2', - comment: null, - isLiked: true, - userId: 'admin_id', - user: { - id: 'admin_id', - name: 'admin', - email: 'admin@test.com', - profileImagePath: '', - profileChangedAt: new Date('2021-01-01'), - }, - assetId: assetStub.image.id, - albumId: albumStub.oneAsset.id, - createdAt: new Date(), - updatedAt: new Date(), - updateId: 'uuid-v7', - }), -}; diff --git a/server/test/fixtures/api-key.stub.ts b/server/test/fixtures/api-key.stub.ts deleted file mode 100644 index 905bda34b4..0000000000 --- a/server/test/fixtures/api-key.stub.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { authStub } from 'test/fixtures/auth.stub'; -import { userStub } from 'test/fixtures/user.stub'; - -export const keyStub = { - authKey: Object.freeze({ - id: 'my-random-guid', - key: 'my-api-key (hashed)', - user: userStub.admin, - permissions: [], - } as any), - - admin: Object.freeze({ - id: 'my-random-guid', - name: 'My Key', - key: 'my-api-key (hashed)', - userId: authStub.admin.user.id, - user: userStub.admin, - permissions: [], - } as any), -}; diff --git a/server/test/fixtures/asset.stub.ts b/server/test/fixtures/asset.stub.ts index a0619f1a10..c0902dddb3 100644 --- a/server/test/fixtures/asset.stub.ts +++ b/server/test/fixtures/asset.stub.ts @@ -3,9 +3,9 @@ import { AssetEntity } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; import { StackEntity } from 'src/entities/stack.entity'; import { AssetFileType, AssetStatus, AssetType } from 'src/enum'; +import { StorageAsset } from 'src/types'; import { authStub } from 'test/fixtures/auth.stub'; import { fileStub } from 'test/fixtures/file.stub'; -import { libraryStub } from 'test/fixtures/library.stub'; import { userStub } from 'test/fixtures/user.stub'; const previewFile: AssetFileEntity = { @@ -40,6 +40,21 @@ export const stackStub = (stackId: string, assets: AssetEntity[]): StackEntity = }; export const assetStub = { + storageAsset: (asset: Partial = {}) => ({ + id: 'asset-id', + ownerId: 'user-id', + livePhotoVideoId: null, + type: AssetType.IMAGE, + isExternal: false, + checksum: Buffer.from('file hash'), + timeZone: null, + fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'), + originalPath: '/original/path.jpg', + originalFileName: 'IMG_123.jpg', + sidecarPath: null, + fileSizeInByte: 12_345, + ...asset, + }), noResizePath: Object.freeze({ id: 'asset-id', status: AssetStatus.ACTIVE, @@ -296,6 +311,7 @@ export const assetStub = { isFavorite: false, isArchived: false, duration: null, + libraryId: 'library-id', isVisible: true, isExternal: false, livePhotoVideo: null, @@ -379,7 +395,6 @@ export const assetStub = { livePhotoVideo: null, livePhotoVideoId: null, libraryId: 'library-id', - library: libraryStub.externalLibrary1, tags: [], sharedLinks: [], originalFileName: 'asset-id.jpg', @@ -734,7 +749,6 @@ export const assetStub = { livePhotoVideo: null, livePhotoVideoId: null, libraryId: 'library-id', - library: libraryStub.externalLibrary1, tags: [], sharedLinks: [], originalFileName: 'photo.jpg', diff --git a/server/test/fixtures/library.stub.ts b/server/test/fixtures/library.stub.ts deleted file mode 100644 index bb40035dcc..0000000000 --- a/server/test/fixtures/library.stub.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { LibraryEntity } from 'src/entities/library.entity'; -import { userStub } from 'test/fixtures/user.stub'; - -export const libraryStub = { - externalLibrary1: Object.freeze({ - id: 'library-id', - name: 'test_library', - assets: [], - owner: userStub.admin, - ownerId: 'admin_id', - importPaths: [], - createdAt: new Date('2023-01-01'), - updatedAt: new Date('2023-01-01'), - refreshedAt: null, - exclusionPatterns: [], - }), - externalLibrary2: Object.freeze({ - id: 'library-id2', - name: 'test_library2', - assets: [], - owner: userStub.admin, - ownerId: 'admin_id', - importPaths: [], - createdAt: new Date('2021-01-01'), - updatedAt: new Date('2022-01-01'), - refreshedAt: null, - exclusionPatterns: [], - }), - externalLibraryWithImportPaths1: Object.freeze({ - id: 'library-id-with-paths1', - name: 'library-with-import-paths1', - assets: [], - owner: userStub.admin, - ownerId: 'admin_id', - importPaths: ['/foo', '/bar'], - createdAt: new Date('2023-01-01'), - updatedAt: new Date('2023-01-01'), - refreshedAt: null, - exclusionPatterns: [], - }), - externalLibraryWithImportPaths2: Object.freeze({ - id: 'library-id-with-paths2', - name: 'library-with-import-paths2', - assets: [], - owner: userStub.admin, - ownerId: 'admin_id', - importPaths: ['/xyz', '/asdf'], - createdAt: new Date('2023-01-01'), - updatedAt: new Date('2023-01-01'), - refreshedAt: null, - exclusionPatterns: [], - }), - patternPath: Object.freeze({ - id: 'library-id1337', - name: 'importpath-exclusion-library1', - assets: [], - owner: userStub.admin, - ownerId: 'user-id', - importPaths: ['/xyz', '/asdf'], - createdAt: new Date('2023-01-01'), - updatedAt: new Date('2023-01-01'), - refreshedAt: null, - exclusionPatterns: ['**/dir1/**'], - }), - hasImmichPaths: Object.freeze({ - id: 'library-id1337', - name: 'importpath-exclusion-library1', - assets: [], - owner: userStub.admin, - ownerId: 'user-id', - importPaths: ['upload/thumbs', 'xyz', 'upload/library'], - createdAt: new Date('2023-01-01'), - updatedAt: new Date('2023-01-01'), - refreshedAt: null, - exclusionPatterns: ['**/dir1/**'], - }), -}; diff --git a/server/test/fixtures/memory.stub.ts b/server/test/fixtures/memory.stub.ts deleted file mode 100644 index 5b3d5635c4..0000000000 --- a/server/test/fixtures/memory.stub.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { MemoryType } from 'src/enum'; -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: [], - deletedAt: null, - seenAt: null, - } as unknown as any, - 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], - deletedAt: null, - seenAt: null, - } as unknown as any, -}; diff --git a/server/test/fixtures/partner.stub.ts b/server/test/fixtures/partner.stub.ts deleted file mode 100644 index 4e5643bc1c..0000000000 --- a/server/test/fixtures/partner.stub.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { PartnerEntity } from 'src/entities/partner.entity'; -import { userStub } from 'test/fixtures/user.stub'; - -export const partnerStub = { - adminToUser1: Object.freeze({ - createdAt: new Date('2023-02-23T05:06:29.716Z'), - updatedAt: new Date('2023-02-23T05:06:29.716Z'), - sharedById: userStub.admin.id, - sharedBy: userStub.admin, - sharedWith: userStub.user1, - sharedWithId: userStub.user1.id, - inTimeline: true, - }), - user1ToAdmin1: Object.freeze({ - createdAt: new Date('2023-02-23T05:06:29.716Z'), - updatedAt: new Date('2023-02-23T05:06:29.716Z'), - sharedBy: userStub.user1, - sharedById: userStub.user1.id, - sharedWithId: userStub.admin.id, - sharedWith: userStub.admin, - inTimeline: true, - }), -}; diff --git a/server/test/global-setup.js b/server/test/global-setup.js deleted file mode 100644 index 6e1fbf41d0..0000000000 --- a/server/test/global-setup.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = async () => { - process.env.TZ = 'UTC'; -}; diff --git a/server/test/medium/globalSetup.ts b/server/test/medium/globalSetup.ts index c6a37148c4..3c25142073 100644 --- a/server/test/medium/globalSetup.ts +++ b/server/test/medium/globalSetup.ts @@ -53,7 +53,6 @@ const globalSetup = async () => { // @ts-expect-error const dataSource = new DataSource(config); await dataSource.initialize(); - await dataSource.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'); await dataSource.runMigrations(); await dataSource.destroy(); }; diff --git a/server/test/medium/specs/audit.database.spec.ts b/server/test/medium/specs/audit.database.spec.ts new file mode 100644 index 0000000000..5332193e4c --- /dev/null +++ b/server/test/medium/specs/audit.database.spec.ts @@ -0,0 +1,74 @@ +import { TestContext, TestFactory } from 'test/factory'; +import { getKyselyDB } from 'test/utils'; + +describe('audit', () => { + let context: TestContext; + + beforeAll(async () => { + const db = await getKyselyDB(); + context = await TestContext.from(db).create(); + }); + + describe('partners_audit', () => { + it('should not cascade user deletes to partners_audit', async () => { + const user1 = TestFactory.user(); + const user2 = TestFactory.user(); + + await context + .getFactory() + .withUser(user1) + .withUser(user2) + .withPartner({ sharedById: user1.id, sharedWithId: user2.id }) + .create(); + + await context.user.delete(user1, true); + + await expect( + context.db.selectFrom('partners_audit').select(['id']).where('sharedById', '=', user1.id).execute(), + ).resolves.toHaveLength(0); + }); + }); + + describe('assets_audit', () => { + it('should not cascade user deletes to assets_audit', async () => { + const user = TestFactory.user(); + const asset = TestFactory.asset({ ownerId: user.id }); + + await context.getFactory().withUser(user).withAsset(asset).create(); + + await context.user.delete(user, true); + + await expect( + context.db.selectFrom('assets_audit').select(['id']).where('assetId', '=', asset.id).execute(), + ).resolves.toHaveLength(0); + }); + }); + + describe('exif', () => { + it('should automatically set updatedAt and updateId when the row is updated', async () => { + const user = TestFactory.user(); + const asset = TestFactory.asset({ ownerId: user.id }); + const exif = { assetId: asset.id, make: 'Canon' }; + + await context.getFactory().withUser(user).withAsset(asset).create(); + await context.asset.upsertExif(exif); + + const before = await context.db + .selectFrom('exif') + .select(['updatedAt', 'updateId']) + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(); + + await context.asset.upsertExif({ assetId: asset.id, make: 'Canon 2' }); + + const after = await context.db + .selectFrom('exif') + .select(['updatedAt', 'updateId']) + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(); + + expect(before.updateId).not.toEqual(after.updateId); + expect(before.updatedAt).not.toEqual(after.updatedAt); + }); + }); +}); diff --git a/server/test/medium/specs/metadata.service.spec.ts b/server/test/medium/specs/metadata.service.spec.ts index 22b9174ccd..28f2c9f64f 100644 --- a/server/test/medium/specs/metadata.service.spec.ts +++ b/server/test/medium/specs/metadata.service.spec.ts @@ -3,12 +3,14 @@ import { writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { AssetEntity } from 'src/entities/asset.entity'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { MetadataRepository } from 'src/repositories/metadata.repository'; import { MetadataService } from 'src/services/metadata.service'; -import { newFakeLoggingRepository } from 'test/repositories/logger.repository.mock'; -import { newRandomImage, newTestService, ServiceMocks } from 'test/utils'; +import { automock, newRandomImage, newTestService, ServiceMocks } from 'test/utils'; -const metadataRepository = new MetadataRepository(newFakeLoggingRepository()); +const metadataRepository = new MetadataRepository( + automock(LoggingRepository, { args: [, { getEnv: () => ({}) }], strict: false }), +); const createTestFile = async (exifData: Record) => { const data = newRandomImage(); @@ -36,7 +38,7 @@ describe(MetadataService.name, () => { beforeEach(() => { ({ sut, mocks } = newTestService(MetadataService, { metadata: metadataRepository })); - mocks.storage.stat.mockResolvedValue({ size: 123_456 } as Stats); + mocks.storage.stat.mockResolvedValue({ size: 123_456, ctime: new Date(), mtime: new Date() } as Stats); delete process.env.TZ; }); @@ -51,6 +53,8 @@ describe(MetadataService.name, () => { description: 'should handle no time zone information', exifData: { DateTimeOriginal: '2022:01:01 00:00:00', + FileCreateDate: '2022:01:01 00:00:00', + FileModifyDate: '2022:01:01 00:00:00', }, expected: { localDateTime: '2022-01-01T00:00:00.000Z', @@ -63,6 +67,8 @@ describe(MetadataService.name, () => { serverTimeZone: 'America/Los_Angeles', exifData: { DateTimeOriginal: '2022:01:01 00:00:00', + FileCreateDate: '2022:01:01 00:00:00', + FileModifyDate: '2022:01:01 00:00:00', }, expected: { localDateTime: '2022-01-01T00:00:00.000Z', @@ -75,6 +81,8 @@ describe(MetadataService.name, () => { serverTimeZone: 'Europe/Brussels', exifData: { DateTimeOriginal: '2022:01:01 00:00:00', + FileCreateDate: '2022:01:01 00:00:00', + FileModifyDate: '2022:01:01 00:00:00', }, expected: { localDateTime: '2022-01-01T00:00:00.000Z', @@ -87,6 +95,8 @@ describe(MetadataService.name, () => { serverTimeZone: 'Europe/Brussels', exifData: { DateTimeOriginal: '2022:06:01 00:00:00', + FileCreateDate: '2022:06:01 00:00:00', + FileModifyDate: '2022:06:01 00:00:00', }, expected: { localDateTime: '2022-06-01T00:00:00.000Z', @@ -98,6 +108,8 @@ describe(MetadataService.name, () => { description: 'should handle a +13:00 time zone', exifData: { DateTimeOriginal: '2022:01:01 00:00:00+13:00', + FileCreateDate: '2022:01:01 00:00:00+13:00', + FileModifyDate: '2022:01:01 00:00:00+13:00', }, expected: { localDateTime: '2022-01-01T00:00:00.000Z', diff --git a/server/test/medium/specs/sync.service.spec.ts b/server/test/medium/specs/sync.service.spec.ts index b33b010258..574ddde93c 100644 --- a/server/test/medium/specs/sync.service.spec.ts +++ b/server/test/medium/specs/sync.service.spec.ts @@ -1,6 +1,6 @@ import { AuthDto } from 'src/dtos/auth.dto'; -import { SyncRequestType } from 'src/enum'; -import { SyncService } from 'src/services/sync.service'; +import { SyncEntityType, SyncRequestType } from 'src/enum'; +import { SYNC_TYPES_ORDER, SyncService } from 'src/services/sync.service'; import { TestContext, TestFactory } from 'test/factory'; import { getKyselyDB, newTestService } from 'test/utils'; @@ -33,7 +33,15 @@ const setup = async () => { }; describe(SyncService.name, () => { - describe.concurrent('users', () => { + it('should have all the types in the ordering variable', () => { + for (const key in SyncRequestType) { + expect(SYNC_TYPES_ORDER).includes(key); + } + + expect(SYNC_TYPES_ORDER.length).toBe(Object.keys(SyncRequestType).length); + }); + + describe.concurrent(SyncEntityType.UserV1, () => { it('should detect and sync the first user', async () => { const { context, auth, sut, testSync } = await setup(); @@ -189,7 +197,7 @@ describe(SyncService.name, () => { }); }); - describe.concurrent('partners', () => { + describe.concurrent(SyncEntityType.PartnerV1, () => { it('should detect and sync the first partner', async () => { const { auth, context, sut, testSync } = await setup(); @@ -349,7 +357,7 @@ describe(SyncService.name, () => { ); }); - it('should not sync a partner for an unrelated user', async () => { + it('should not sync a partner or partner delete for an unrelated user', async () => { const { auth, context, testSync } = await setup(); const user2 = await context.createUser(); @@ -357,9 +365,436 @@ describe(SyncService.name, () => { await context.createPartner({ sharedById: user2.id, sharedWithId: user3.id }); - const response = await testSync(auth, [SyncRequestType.PartnersV1]); + expect(await testSync(auth, [SyncRequestType.PartnersV1])).toHaveLength(0); + + await context.partner.remove({ sharedById: user2.id, sharedWithId: user3.id }); + + expect(await testSync(auth, [SyncRequestType.PartnersV1])).toHaveLength(0); + }); + + it('should not sync a partner delete after a user is deleted', async () => { + const { auth, context, testSync } = await setup(); + + const user2 = await context.createUser(); + await context.createPartner({ sharedById: user2.id, sharedWithId: auth.user.id }); + await context.user.delete({ id: user2.id }, true); + + expect(await testSync(auth, [SyncRequestType.PartnersV1])).toHaveLength(0); + }); + }); + + describe.concurrent(SyncEntityType.AssetV1, () => { + it('should detect and sync the first asset', async () => { + const { auth, context, sut, testSync } = await setup(); + + const checksum = '1115vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA='; + const thumbhash = '2225vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA='; + const date = new Date().toISOString(); + + const asset = TestFactory.asset({ + ownerId: auth.user.id, + checksum: Buffer.from(checksum, 'base64'), + thumbhash: Buffer.from(thumbhash, 'base64'), + fileCreatedAt: date, + fileModifiedAt: date, + deletedAt: null, + }); + await context.createAsset(asset); + + const initialSyncResponse = await testSync(auth, [SyncRequestType.AssetsV1]); + + expect(initialSyncResponse).toHaveLength(1); + expect(initialSyncResponse).toEqual( + expect.arrayContaining([ + { + ack: expect.any(String), + data: { + id: asset.id, + ownerId: asset.ownerId, + thumbhash, + checksum, + deletedAt: null, + fileCreatedAt: date, + fileModifiedAt: date, + isFavorite: false, + isVisible: true, + localDateTime: null, + type: asset.type, + }, + type: 'AssetV1', + }, + ]), + ); + + const acks = [initialSyncResponse[0].ack]; + await sut.setAcks(auth, { acks }); + + const ackSyncResponse = await testSync(auth, [SyncRequestType.AssetsV1]); + + expect(ackSyncResponse).toHaveLength(0); + }); + + it('should detect and sync a deleted asset', async () => { + const { auth, context, sut, testSync } = await setup(); + + const asset = TestFactory.asset({ ownerId: auth.user.id }); + await context.createAsset(asset); + await context.asset.remove(asset); + + const response = await testSync(auth, [SyncRequestType.AssetsV1]); + + expect(response).toHaveLength(1); + expect(response).toEqual( + expect.arrayContaining([ + { + ack: expect.any(String), + data: { + assetId: asset.id, + }, + type: 'AssetDeleteV1', + }, + ]), + ); + + const acks = response.map(({ ack }) => ack); + await sut.setAcks(auth, { acks }); + + const ackSyncResponse = await testSync(auth, [SyncRequestType.AssetsV1]); + + expect(ackSyncResponse).toHaveLength(0); + }); + + it('should not sync an asset or asset delete for an unrelated user', async () => { + const { auth, context, testSync } = await setup(); + + const user2 = await context.createUser(); + const session = TestFactory.session({ userId: user2.id }); + const auth2 = TestFactory.auth({ session, user: user2 }); + + const asset = TestFactory.asset({ ownerId: user2.id }); + await context.createAsset(asset); + + expect(await testSync(auth2, [SyncRequestType.AssetsV1])).toHaveLength(1); + expect(await testSync(auth, [SyncRequestType.AssetsV1])).toHaveLength(0); + + await context.asset.remove(asset); + expect(await testSync(auth2, [SyncRequestType.AssetsV1])).toHaveLength(1); + expect(await testSync(auth, [SyncRequestType.AssetsV1])).toHaveLength(0); + }); + }); + + describe.concurrent(SyncRequestType.PartnerAssetsV1, () => { + it('should detect and sync the first partner asset', async () => { + const { auth, context, sut, testSync } = await setup(); + + const checksum = '1115vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA='; + const thumbhash = '2225vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA='; + const date = new Date().toISOString(); + + const user2 = await context.createUser(); + + const asset = TestFactory.asset({ + ownerId: user2.id, + checksum: Buffer.from(checksum, 'base64'), + thumbhash: Buffer.from(thumbhash, 'base64'), + fileCreatedAt: date, + fileModifiedAt: date, + deletedAt: null, + }); + await context.createAsset(asset); + await context.partner.create({ sharedById: user2.id, sharedWithId: auth.user.id }); + + const initialSyncResponse = await testSync(auth, [SyncRequestType.PartnerAssetsV1]); + + expect(initialSyncResponse).toHaveLength(1); + expect(initialSyncResponse).toEqual( + expect.arrayContaining([ + { + ack: expect.any(String), + data: { + id: asset.id, + ownerId: asset.ownerId, + thumbhash, + checksum, + deletedAt: null, + fileCreatedAt: date, + fileModifiedAt: date, + isFavorite: false, + isVisible: true, + localDateTime: null, + type: asset.type, + }, + type: SyncEntityType.PartnerAssetV1, + }, + ]), + ); + + const acks = [initialSyncResponse[0].ack]; + await sut.setAcks(auth, { acks }); + + const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnerAssetsV1]); + + expect(ackSyncResponse).toHaveLength(0); + }); + + it('should detect and sync a deleted partner asset', async () => { + const { auth, context, sut, testSync } = await setup(); + + const user2 = await context.createUser(); + const asset = TestFactory.asset({ ownerId: user2.id }); + await context.createAsset(asset); + await context.partner.create({ sharedById: user2.id, sharedWithId: auth.user.id }); + await context.asset.remove(asset); + + const response = await testSync(auth, [SyncRequestType.PartnerAssetsV1]); + + expect(response).toHaveLength(1); + expect(response).toEqual( + expect.arrayContaining([ + { + ack: expect.any(String), + data: { + assetId: asset.id, + }, + type: SyncEntityType.PartnerAssetDeleteV1, + }, + ]), + ); + + const acks = response.map(({ ack }) => ack); + await sut.setAcks(auth, { acks }); + + const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnerAssetsV1]); + + expect(ackSyncResponse).toHaveLength(0); + }); + + it('should not sync a deleted partner asset due to a user delete', async () => { + const { auth, context, testSync } = await setup(); + + const user2 = await context.createUser(); + await context.partner.create({ sharedById: user2.id, sharedWithId: auth.user.id }); + await context.createAsset({ ownerId: user2.id }); + await context.user.delete({ id: user2.id }, true); + + const response = await testSync(auth, [SyncRequestType.PartnerAssetsV1]); expect(response).toHaveLength(0); }); + + it('should not sync a deleted partner asset due to a partner delete (unshare)', async () => { + const { auth, context, testSync } = await setup(); + + const user2 = await context.createUser(); + await context.createAsset({ ownerId: user2.id }); + const partner = { sharedById: user2.id, sharedWithId: auth.user.id }; + await context.partner.create(partner); + + await expect(testSync(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toHaveLength(1); + + await context.partner.remove(partner); + + await expect(testSync(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toHaveLength(0); + }); + + it('should not sync an asset or asset delete for own user', async () => { + const { auth, context, testSync } = await setup(); + + const user2 = await context.createUser(); + const asset = await context.createAsset({ ownerId: auth.user.id }); + const partner = { sharedById: user2.id, sharedWithId: auth.user.id }; + await context.partner.create(partner); + + await expect(testSync(auth, [SyncRequestType.AssetsV1])).resolves.toHaveLength(1); + await expect(testSync(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toHaveLength(0); + + await context.asset.remove(asset); + + await expect(testSync(auth, [SyncRequestType.AssetsV1])).resolves.toHaveLength(1); + await expect(testSync(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toHaveLength(0); + }); + + it('should not sync an asset or asset delete for unrelated user', async () => { + const { auth, context, testSync } = await setup(); + + const user2 = await context.createUser(); + const session = TestFactory.session({ userId: user2.id }); + const auth2 = TestFactory.auth({ session, user: user2 }); + const asset = await context.createAsset({ ownerId: user2.id }); + + await expect(testSync(auth2, [SyncRequestType.AssetsV1])).resolves.toHaveLength(1); + await expect(testSync(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toHaveLength(0); + + await context.asset.remove(asset); + + await expect(testSync(auth2, [SyncRequestType.AssetsV1])).resolves.toHaveLength(1); + await expect(testSync(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toHaveLength(0); + }); + }); + + describe.concurrent(SyncRequestType.AssetExifsV1, () => { + it('should detect and sync the first asset exif', async () => { + const { auth, context, sut, testSync } = await setup(); + + const asset = TestFactory.asset({ ownerId: auth.user.id }); + const exif = { assetId: asset.id, make: 'Canon' }; + + await context.createAsset(asset); + await context.asset.upsertExif(exif); + + const initialSyncResponse = await testSync(auth, [SyncRequestType.AssetExifsV1]); + + expect(initialSyncResponse).toHaveLength(1); + expect(initialSyncResponse).toEqual( + expect.arrayContaining([ + { + ack: expect.any(String), + data: { + assetId: asset.id, + city: null, + country: null, + dateTimeOriginal: null, + description: '', + exifImageHeight: null, + exifImageWidth: null, + exposureTime: null, + fNumber: null, + fileSizeInByte: null, + focalLength: null, + fps: null, + iso: null, + latitude: null, + lensModel: null, + longitude: null, + make: 'Canon', + model: null, + modifyDate: null, + orientation: null, + profileDescription: null, + projectionType: null, + rating: null, + state: null, + timeZone: null, + }, + type: SyncEntityType.AssetExifV1, + }, + ]), + ); + + const acks = [initialSyncResponse[0].ack]; + await sut.setAcks(auth, { acks }); + + const ackSyncResponse = await testSync(auth, [SyncRequestType.AssetExifsV1]); + + expect(ackSyncResponse).toHaveLength(0); + }); + + it('should only sync asset exif for own user', async () => { + const { auth, context, testSync } = await setup(); + + const user2 = await context.createUser(); + const session = TestFactory.session({ userId: user2.id }); + const auth2 = TestFactory.auth({ session, user: user2 }); + + await context.partner.create({ sharedById: user2.id, sharedWithId: auth.user.id }); + const asset = TestFactory.asset({ ownerId: user2.id }); + const exif = { assetId: asset.id, make: 'Canon' }; + + await context.createAsset(asset); + await context.asset.upsertExif(exif); + + await expect(testSync(auth2, [SyncRequestType.AssetExifsV1])).resolves.toHaveLength(1); + await expect(testSync(auth, [SyncRequestType.AssetExifsV1])).resolves.toHaveLength(0); + }); + }); + + describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => { + it('should detect and sync the first partner asset exif', async () => { + const { auth, context, sut, testSync } = await setup(); + + const user2 = await context.createUser(); + await context.partner.create({ sharedById: user2.id, sharedWithId: auth.user.id }); + const asset = TestFactory.asset({ ownerId: user2.id }); + await context.createAsset(asset); + const exif = { assetId: asset.id, make: 'Canon' }; + await context.asset.upsertExif(exif); + + const initialSyncResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]); + + expect(initialSyncResponse).toHaveLength(1); + expect(initialSyncResponse).toEqual( + expect.arrayContaining([ + { + ack: expect.any(String), + data: { + assetId: asset.id, + city: null, + country: null, + dateTimeOriginal: null, + description: '', + exifImageHeight: null, + exifImageWidth: null, + exposureTime: null, + fNumber: null, + fileSizeInByte: null, + focalLength: null, + fps: null, + iso: null, + latitude: null, + lensModel: null, + longitude: null, + make: 'Canon', + model: null, + modifyDate: null, + orientation: null, + profileDescription: null, + projectionType: null, + rating: null, + state: null, + timeZone: null, + }, + type: SyncEntityType.PartnerAssetExifV1, + }, + ]), + ); + + const acks = [initialSyncResponse[0].ack]; + await sut.setAcks(auth, { acks }); + + const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]); + + expect(ackSyncResponse).toHaveLength(0); + }); + + it('should not sync partner asset exif for own user', async () => { + const { auth, context, testSync } = await setup(); + + const user2 = await context.createUser(); + await context.partner.create({ sharedById: user2.id, sharedWithId: auth.user.id }); + const asset = TestFactory.asset({ ownerId: auth.user.id }); + const exif = { assetId: asset.id, make: 'Canon' }; + await context.createAsset(asset); + await context.asset.upsertExif(exif); + + await expect(testSync(auth, [SyncRequestType.AssetExifsV1])).resolves.toHaveLength(1); + await expect(testSync(auth, [SyncRequestType.PartnerAssetExifsV1])).resolves.toHaveLength(0); + }); + + it('should not sync partner asset exif for unrelated user', async () => { + const { auth, context, testSync } = await setup(); + + const user2 = await context.createUser(); + const user3 = await context.createUser(); + const session = TestFactory.session({ userId: user3.id }); + const authUser3 = TestFactory.auth({ session, user: user3 }); + await context.partner.create({ sharedById: user2.id, sharedWithId: auth.user.id }); + const asset = TestFactory.asset({ ownerId: user3.id }); + const exif = { assetId: asset.id, make: 'Canon' }; + await context.createAsset(asset); + await context.asset.upsertExif(exif); + + await expect(testSync(authUser3, [SyncRequestType.AssetExifsV1])).resolves.toHaveLength(1); + await expect(testSync(auth, [SyncRequestType.PartnerAssetExifsV1])).resolves.toHaveLength(0); + }); }); }); diff --git a/server/test/repositories/activity.repository.mock.ts b/server/test/repositories/activity.repository.mock.ts deleted file mode 100644 index 81208b7232..0000000000 --- a/server/test/repositories/activity.repository.mock.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ActivityRepository } from 'src/repositories/activity.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newActivityRepositoryMock = (): Mocked> => { - return { - search: vitest.fn(), - create: vitest.fn(), - delete: vitest.fn(), - getStatistics: vitest.fn(), - }; -}; diff --git a/server/test/repositories/album-user.repository.mock.ts b/server/test/repositories/album-user.repository.mock.ts deleted file mode 100644 index e3225661a4..0000000000 --- a/server/test/repositories/album-user.repository.mock.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AlbumUserRepository } from 'src/repositories/album-user.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked } from 'vitest'; - -export const newAlbumUserRepositoryMock = (): Mocked> => { - return { - create: vitest.fn(), - delete: vitest.fn(), - update: vitest.fn(), - }; -}; diff --git a/server/test/repositories/album.repository.mock.ts b/server/test/repositories/album.repository.mock.ts deleted file mode 100644 index 7a1ae68a52..0000000000 --- a/server/test/repositories/album.repository.mock.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AlbumRepository } from 'src/repositories/album.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newAlbumRepositoryMock = (): Mocked> => { - return { - getById: vitest.fn(), - getByAssetId: vitest.fn(), - getMetadataForIds: vitest.fn(), - getOwned: vitest.fn(), - getShared: vitest.fn(), - getNotShared: vitest.fn(), - restoreAll: vitest.fn(), - softDeleteAll: vitest.fn(), - deleteAll: vitest.fn(), - addAssetIds: vitest.fn(), - removeAsset: vitest.fn(), - removeAssetIds: vitest.fn(), - getAssetIds: vitest.fn(), - create: vitest.fn(), - update: vitest.fn(), - delete: vitest.fn(), - updateThumbnails: vitest.fn(), - }; -}; diff --git a/server/test/repositories/api-key.repository.mock.ts b/server/test/repositories/api-key.repository.mock.ts deleted file mode 100644 index e8ae0bf8e2..0000000000 --- a/server/test/repositories/api-key.repository.mock.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ApiKeyRepository } from 'src/repositories/api-key.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newKeyRepositoryMock = (): Mocked> => { - return { - create: vitest.fn(), - update: vitest.fn(), - delete: vitest.fn(), - getKey: vitest.fn(), - getById: vitest.fn(), - getByUserId: vitest.fn(), - }; -}; diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index a2baed7cce..19464f7ff2 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -5,6 +5,7 @@ import { Mocked, vitest } from 'vitest'; export const newAssetRepositoryMock = (): Mocked> => { return { create: vitest.fn(), + createAll: vitest.fn(), upsertExif: vitest.fn(), upsertJobStatus: vitest.fn(), getByDayOfYear: vitest.fn(), @@ -23,6 +24,7 @@ export const newAssetRepositoryMock = (): Mocked> => { - return { - getAfter: vitest.fn(), - removeBefore: vitest.fn(), - }; -}; diff --git a/server/test/repositories/cron.repository.mock.ts b/server/test/repositories/cron.repository.mock.ts deleted file mode 100644 index 5b74bd3cf5..0000000000 --- a/server/test/repositories/cron.repository.mock.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { CronRepository } from 'src/repositories/cron.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newCronRepositoryMock = (): Mocked> => { - return { - create: vitest.fn(), - update: vitest.fn(), - }; -}; diff --git a/server/test/repositories/database.repository.mock.ts b/server/test/repositories/database.repository.mock.ts index fe954c725b..eeedf682de 100644 --- a/server/test/repositories/database.repository.mock.ts +++ b/server/test/repositories/database.repository.mock.ts @@ -4,9 +4,7 @@ import { Mocked, vitest } from 'vitest'; export const newDatabaseRepositoryMock = (): Mocked> => { return { - init: vitest.fn(), shutdown: vitest.fn(), - reconnect: vitest.fn(), getExtensionVersion: vitest.fn(), getExtensionVersionRange: vitest.fn(), getPostgresVersion: vitest.fn().mockResolvedValue('14.10 (Debian 14.10-1.pgdg120+1)'), diff --git a/server/test/repositories/event.repository.mock.ts b/server/test/repositories/event.repository.mock.ts deleted file mode 100644 index a253e93671..0000000000 --- a/server/test/repositories/event.repository.mock.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { EventRepository } from 'src/repositories/event.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newEventRepositoryMock = (): Mocked> => { - return { - setup: vitest.fn(), - emit: vitest.fn() as any, - clientSend: vitest.fn() as any, - clientBroadcast: vitest.fn() as any, - serverSend: vitest.fn(), - afterInit: vitest.fn(), - handleConnection: vitest.fn(), - handleDisconnect: vitest.fn(), - setAuthFn: vitest.fn(), - }; -}; diff --git a/server/test/repositories/library.repository.mock.ts b/server/test/repositories/library.repository.mock.ts deleted file mode 100644 index 5db9e18d33..0000000000 --- a/server/test/repositories/library.repository.mock.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { LibraryRepository } from 'src/repositories/library.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newLibraryRepositoryMock = (): Mocked> => { - return { - get: vitest.fn(), - create: vitest.fn(), - delete: vitest.fn(), - softDelete: vitest.fn(), - update: vitest.fn(), - getStatistics: vitest.fn(), - getAllDeleted: vitest.fn(), - getAll: vitest.fn(), - }; -}; diff --git a/server/test/repositories/logger.repository.mock.ts b/server/test/repositories/logger.repository.mock.ts deleted file mode 100644 index 7257d375f1..0000000000 --- a/server/test/repositories/logger.repository.mock.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newLoggingRepositoryMock = (): Mocked> => { - return { - setLogLevel: vitest.fn(), - setContext: vitest.fn(), - setAppName: vitest.fn(), - isLevelEnabled: vitest.fn(), - verbose: vitest.fn(), - verboseFn: vitest.fn(), - debug: vitest.fn(), - debugFn: vitest.fn(), - log: vitest.fn(), - warn: vitest.fn(), - error: vitest.fn(), - fatal: vitest.fn(), - }; -}; - -export const newFakeLoggingRepository = () => - newLoggingRepositoryMock() as RepositoryInterface as LoggingRepository; diff --git a/server/test/repositories/machine-learning.repository.mock.ts b/server/test/repositories/machine-learning.repository.mock.ts deleted file mode 100644 index 229e8f92ec..0000000000 --- a/server/test/repositories/machine-learning.repository.mock.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newMachineLearningRepositoryMock = (): Mocked> => { - return { - encodeImage: vitest.fn(), - encodeText: vitest.fn(), - detectFaces: vitest.fn(), - }; -}; diff --git a/server/test/repositories/map.repository.mock.ts b/server/test/repositories/map.repository.mock.ts deleted file mode 100644 index 9e7df32252..0000000000 --- a/server/test/repositories/map.repository.mock.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { MapRepository } from 'src/repositories/map.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked } from 'vitest'; - -export const newMapRepositoryMock = (): Mocked> => { - return { - init: vitest.fn(), - reverseGeocode: vitest.fn(), - getMapMarkers: vitest.fn(), - }; -}; diff --git a/server/test/repositories/memory.repository.mock.ts b/server/test/repositories/memory.repository.mock.ts deleted file mode 100644 index c3a6d774f0..0000000000 --- a/server/test/repositories/memory.repository.mock.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { MemoryRepository } from 'src/repositories/memory.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newMemoryRepositoryMock = (): Mocked> => { - return { - search: vitest.fn().mockResolvedValue([]), - get: vitest.fn(), - create: vitest.fn(), - update: vitest.fn(), - delete: vitest.fn(), - getAssetIds: vitest.fn().mockResolvedValue(new Set()), - addAssetIds: vitest.fn(), - removeAssetIds: vitest.fn(), - cleanup: vitest.fn(), - }; -}; diff --git a/server/test/repositories/move.repository.mock.ts b/server/test/repositories/move.repository.mock.ts deleted file mode 100644 index cf304b591e..0000000000 --- a/server/test/repositories/move.repository.mock.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { MoveRepository } from 'src/repositories/move.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newMoveRepositoryMock = (): Mocked> => { - return { - create: vitest.fn(), - getByEntity: vitest.fn(), - update: vitest.fn(), - delete: vitest.fn(), - }; -}; diff --git a/server/test/repositories/notification.repository.mock.ts b/server/test/repositories/notification.repository.mock.ts deleted file mode 100644 index 3aa7f63cf2..0000000000 --- a/server/test/repositories/notification.repository.mock.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { NotificationRepository } from 'src/repositories/notification.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked } from 'vitest'; - -export const newNotificationRepositoryMock = (): Mocked> => { - return { - renderEmail: vitest.fn(), - sendEmail: vitest.fn().mockResolvedValue({ messageId: 'message-1' }), - verifySmtp: vitest.fn(), - }; -}; diff --git a/server/test/repositories/oauth.repository.mock.ts b/server/test/repositories/oauth.repository.mock.ts deleted file mode 100644 index 64777fa671..0000000000 --- a/server/test/repositories/oauth.repository.mock.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { OAuthRepository } from 'src/repositories/oauth.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked } from 'vitest'; - -export const newOAuthRepositoryMock = (): Mocked> => { - return { - init: vitest.fn(), - authorize: vitest.fn(), - getLogoutEndpoint: vitest.fn(), - getProfile: vitest.fn(), - }; -}; diff --git a/server/test/repositories/partner.repository.mock.ts b/server/test/repositories/partner.repository.mock.ts deleted file mode 100644 index 6f9d4a36be..0000000000 --- a/server/test/repositories/partner.repository.mock.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PartnerRepository } from 'src/repositories/partner.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newPartnerRepositoryMock = (): Mocked> => { - return { - create: vitest.fn(), - remove: vitest.fn(), - getAll: vitest.fn().mockResolvedValue([]), - get: vitest.fn(), - update: vitest.fn(), - }; -}; diff --git a/server/test/repositories/person.repository.mock.ts b/server/test/repositories/person.repository.mock.ts deleted file mode 100644 index c8a4253edc..0000000000 --- a/server/test/repositories/person.repository.mock.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { PersonRepository } from 'src/repositories/person.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newPersonRepositoryMock = (): Mocked> => { - return { - getById: vitest.fn(), - getAll: vitest.fn(), - getAllForUser: vitest.fn(), - getAllWithoutFaces: vitest.fn(), - - getByName: vitest.fn(), - getDistinctNames: vitest.fn(), - - create: vitest.fn(), - createAll: vitest.fn(), - update: vitest.fn(), - updateAll: vitest.fn(), - delete: vitest.fn(), - deleteFaces: vitest.fn(), - - getStatistics: vitest.fn(), - getAllFaces: vitest.fn(), - getFacesByIds: vitest.fn(), - getRandomFace: vitest.fn(), - - reassignFaces: vitest.fn(), - unassignFaces: vitest.fn(), - refreshFaces: vitest.fn(), - getFaces: vitest.fn(), - reassignFace: vitest.fn(), - getFaceById: vitest.fn(), - getFaceByIdWithAssets: vitest.fn(), - getNumberOfPeople: vitest.fn(), - getLatestFaceDate: vitest.fn(), - - createAssetFace: vitest.fn(), - deleteAssetFace: vitest.fn(), - softDeleteAssetFaces: vitest.fn(), - }; -}; diff --git a/server/test/repositories/process.repository.mock.ts b/server/test/repositories/process.repository.mock.ts deleted file mode 100644 index f78975310b..0000000000 --- a/server/test/repositories/process.repository.mock.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ProcessRepository } from 'src/repositories/process.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newProcessRepositoryMock = (): Mocked> => { - return { - spawn: vitest.fn(), - }; -}; diff --git a/server/test/repositories/search.repository.mock.ts b/server/test/repositories/search.repository.mock.ts deleted file mode 100644 index 520bf23b3e..0000000000 --- a/server/test/repositories/search.repository.mock.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { SearchRepository } from 'src/repositories/search.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newSearchRepositoryMock = (): Mocked> => { - return { - searchMetadata: vitest.fn(), - searchSmart: vitest.fn(), - searchDuplicates: vitest.fn(), - searchFaces: vitest.fn(), - searchRandom: vitest.fn(), - upsert: vitest.fn(), - searchPlaces: vitest.fn(), - getAssetsByCity: vitest.fn(), - deleteAllSearchEmbeddings: vitest.fn(), - getDimensionSize: vitest.fn(), - setDimensionSize: vitest.fn(), - getCameraMakes: vitest.fn(), - getCameraModels: vitest.fn(), - getCities: vitest.fn(), - getCountries: vitest.fn(), - getStates: vitest.fn(), - }; -}; diff --git a/server/test/repositories/server-info.repository.mock.ts b/server/test/repositories/server-info.repository.mock.ts deleted file mode 100644 index 49f955b283..0000000000 --- a/server/test/repositories/server-info.repository.mock.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ServerInfoRepository } from 'src/repositories/server-info.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newServerInfoRepositoryMock = (): Mocked> => { - return { - getGitHubRelease: vitest.fn(), - getBuildVersions: vitest.fn(), - }; -}; diff --git a/server/test/repositories/session.repository.mock.ts b/server/test/repositories/session.repository.mock.ts deleted file mode 100644 index b519b07e36..0000000000 --- a/server/test/repositories/session.repository.mock.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { SessionRepository } from 'src/repositories/session.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newSessionRepositoryMock = (): Mocked> => { - return { - search: vitest.fn(), - create: vitest.fn() as any, - update: vitest.fn() as any, - delete: vitest.fn(), - getByToken: vitest.fn(), - getByUserId: vitest.fn(), - }; -}; diff --git a/server/test/repositories/shared-link.repository.mock.ts b/server/test/repositories/shared-link.repository.mock.ts deleted file mode 100644 index 66044b9eed..0000000000 --- a/server/test/repositories/shared-link.repository.mock.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newSharedLinkRepositoryMock = (): Mocked> => { - return { - getAll: vitest.fn(), - get: vitest.fn(), - getByKey: vitest.fn(), - create: vitest.fn(), - remove: vitest.fn(), - update: vitest.fn(), - }; -}; diff --git a/server/test/repositories/stack.repository.mock.ts b/server/test/repositories/stack.repository.mock.ts deleted file mode 100644 index 74fef6f4b1..0000000000 --- a/server/test/repositories/stack.repository.mock.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { StackRepository } from 'src/repositories/stack.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newStackRepositoryMock = (): Mocked> => { - return { - search: vitest.fn(), - create: vitest.fn(), - update: vitest.fn(), - delete: vitest.fn(), - getById: vitest.fn(), - deleteAll: vitest.fn(), - }; -}; diff --git a/server/test/repositories/sync.repository.mock.ts b/server/test/repositories/sync.repository.mock.ts deleted file mode 100644 index 6d94f6e039..0000000000 --- a/server/test/repositories/sync.repository.mock.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { SyncRepository } from 'src/repositories/sync.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newSyncRepositoryMock = (): Mocked> => { - return { - getCheckpoints: vitest.fn(), - upsertCheckpoints: vitest.fn(), - deleteCheckpoints: vitest.fn(), - getUserUpserts: vitest.fn(), - getUserDeletes: vitest.fn(), - getPartnerUpserts: vitest.fn(), - getPartnerDeletes: vitest.fn(), - }; -}; diff --git a/server/test/repositories/tag.repository.mock.ts b/server/test/repositories/tag.repository.mock.ts deleted file mode 100644 index b6313ca798..0000000000 --- a/server/test/repositories/tag.repository.mock.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { TagRepository } from 'src/repositories/tag.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newTagRepositoryMock = (): Mocked> => { - return { - getAll: vitest.fn(), - getByValue: vitest.fn(), - upsertValue: vitest.fn(), - replaceAssetTags: vitest.fn(), - - get: vitest.fn(), - create: vitest.fn(), - update: vitest.fn(), - delete: vitest.fn(), - - getAssetIds: vitest.fn(), - addAssetIds: vitest.fn(), - removeAssetIds: vitest.fn(), - upsertAssetIds: vitest.fn(), - deleteEmptyTags: vitest.fn(), - }; -}; diff --git a/server/test/repositories/trash.repository.mock.ts b/server/test/repositories/trash.repository.mock.ts deleted file mode 100644 index b42867213a..0000000000 --- a/server/test/repositories/trash.repository.mock.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { TrashRepository } from 'src/repositories/trash.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newTrashRepositoryMock = (): Mocked> => { - return { - empty: vitest.fn(), - restore: vitest.fn(), - restoreAll: vitest.fn(), - getDeletedIds: vitest.fn(), - }; -}; diff --git a/server/test/repositories/user.repository.mock.ts b/server/test/repositories/user.repository.mock.ts deleted file mode 100644 index 2dc6b9eec2..0000000000 --- a/server/test/repositories/user.repository.mock.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { UserRepository } from 'src/repositories/user.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newUserRepositoryMock = (): Mocked> => { - return { - get: vitest.fn(), - getMetadata: vitest.fn().mockResolvedValue([]), - getAdmin: vitest.fn(), - getByEmail: vitest.fn(), - getByStorageLabel: vitest.fn(), - getByOAuthId: vitest.fn(), - getUserStats: vitest.fn(), - getList: vitest.fn(), - create: vitest.fn(), - update: vitest.fn(), - delete: vitest.fn(), - restore: vitest.fn(), - getDeletedUsers: vitest.fn(), - hasAdmin: vitest.fn(), - updateUsage: vitest.fn(), - syncUsage: vitest.fn(), - upsertMetadata: vitest.fn(), - deleteMetadata: vitest.fn(), - }; -}; diff --git a/server/test/repositories/version-history.repository.mock.ts b/server/test/repositories/version-history.repository.mock.ts deleted file mode 100644 index 98a9166487..0000000000 --- a/server/test/repositories/version-history.repository.mock.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newVersionHistoryRepositoryMock = (): Mocked> => { - return { - getAll: vitest.fn().mockResolvedValue([]), - getLatest: vitest.fn(), - create: vitest.fn(), - }; -}; diff --git a/server/test/repositories/view.repository.mock.ts b/server/test/repositories/view.repository.mock.ts deleted file mode 100644 index 057d7ee28a..0000000000 --- a/server/test/repositories/view.repository.mock.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ViewRepository } from 'src/repositories/view-repository'; -import { RepositoryInterface } from 'src/types'; -import { Mocked, vitest } from 'vitest'; - -export const newViewRepositoryMock = (): Mocked> => { - return { - getAssetsByOriginalPath: vitest.fn(), - getUniqueOriginalPaths: vitest.fn(), - }; -}; diff --git a/server/test/small.factory.ts b/server/test/small.factory.ts new file mode 100644 index 0000000000..1faedf311a --- /dev/null +++ b/server/test/small.factory.ts @@ -0,0 +1,202 @@ +import { randomUUID } from 'node:crypto'; +import { ApiKey, Asset, AuthApiKey, AuthUser, Library, Partner, User } from 'src/database'; +import { AuthDto } from 'src/dtos/auth.dto'; +import { OnThisDayData } from 'src/entities/memory.entity'; +import { AssetStatus, AssetType, MemoryType, Permission } from 'src/enum'; +import { ActivityItem, MemoryItem } from 'src/types'; + +export const newUuid = () => randomUUID() as string; +export const newUuids = () => + Array.from({ length: 100 }) + .fill(0) + .map(() => newUuid()); +export const newDate = () => new Date(); +export const newUpdateId = () => 'uuid-v7'; +export const newSha1 = () => Buffer.from('this is a fake hash'); + +const authFactory = ({ apiKey, ...user }: Partial & { apiKey?: Partial } = {}) => { + const auth: AuthDto = { + user: authUserFactory(user), + }; + + if (apiKey) { + auth.apiKey = authApiKeyFactory(apiKey); + } + + return auth; +}; + +const authApiKeyFactory = (apiKey: Partial = {}) => ({ + id: newUuid(), + permissions: [Permission.ALL], + ...apiKey, +}); + +const authUserFactory = (authUser: Partial = {}) => ({ + id: newUuid(), + isAdmin: false, + name: 'Test User', + email: 'test@immich.cloud', + quotaUsageInBytes: 0, + quotaSizeInBytes: null, + ...authUser, +}); + +const partnerFactory = (partner: Partial = {}) => { + const sharedBy = userFactory(partner.sharedBy || {}); + const sharedWith = userFactory(partner.sharedWith || {}); + + return { + sharedById: sharedBy.id, + sharedBy, + sharedWithId: sharedWith.id, + sharedWith, + createdAt: newDate(), + updatedAt: newDate(), + updateId: newUpdateId(), + inTimeline: true, + ...partner, + }; +}; + +const sessionFactory = () => ({ + id: newUuid(), + createdAt: newDate(), + updatedAt: newDate(), + updateId: newUpdateId(), + deviceOS: 'android', + deviceType: 'mobile', + token: 'abc123', + userId: newUuid(), +}); + +const stackFactory = () => ({ + id: newUuid(), + ownerId: newUuid(), + primaryAssetId: newUuid(), +}); + +const userFactory = (user: Partial = {}) => ({ + id: newUuid(), + name: 'Test User', + email: 'test@immich.cloud', + profileImagePath: '', + profileChangedAt: newDate(), + ...user, +}); + +const assetFactory = (asset: Partial = {}) => ({ + id: newUuid(), + createdAt: newDate(), + updatedAt: newDate(), + deletedAt: null, + updateId: newUpdateId(), + status: AssetStatus.ACTIVE, + checksum: newSha1(), + deviceAssetId: '', + deviceId: '', + duplicateId: null, + duration: null, + encodedVideoPath: null, + fileCreatedAt: newDate(), + fileModifiedAt: newDate(), + isArchived: false, + isExternal: false, + isFavorite: false, + isOffline: false, + isVisible: true, + libraryId: null, + livePhotoVideoId: null, + localDateTime: newDate(), + originalFileName: 'IMG_123.jpg', + originalPath: `upload/12/34/IMG_123.jpg`, + ownerId: newUuid(), + sidecarPath: null, + stackId: null, + thumbhash: null, + type: AssetType.IMAGE, + ...asset, +}); + +const activityFactory = (activity: Partial = {}) => { + const userId = activity.userId || newUuid(); + return { + id: newUuid(), + comment: null, + isLiked: false, + userId, + user: userFactory({ id: userId }), + assetId: newUuid(), + albumId: newUuid(), + createdAt: newDate(), + updatedAt: newDate(), + updateId: newUpdateId(), + ...activity, + }; +}; + +const apiKeyFactory = (apiKey: Partial = {}) => ({ + id: newUuid(), + userId: newUuid(), + createdAt: newDate(), + updatedAt: newDate(), + updateId: newUpdateId(), + name: 'Api Key', + permissions: [Permission.ALL], + ...apiKey, +}); + +const libraryFactory = (library: Partial = {}) => ({ + id: newUuid(), + createdAt: newDate(), + updatedAt: newDate(), + updateId: newUpdateId(), + deletedAt: null, + refreshedAt: null, + name: 'Library', + assets: [], + ownerId: newUuid(), + importPaths: [], + exclusionPatterns: [], + ...library, +}); + +const memoryFactory = (memory: Partial = {}) => ({ + id: newUuid(), + createdAt: newDate(), + updatedAt: newDate(), + updateId: newUpdateId(), + deletedAt: null, + ownerId: newUuid(), + type: MemoryType.ON_THIS_DAY, + data: { year: 2024 } as OnThisDayData, + isSaved: false, + memoryAt: newDate(), + seenAt: null, + showAt: newDate(), + hideAt: newDate(), + assets: [], + ...memory, +}); + +const versionHistoryFactory = () => ({ + id: newUuid(), + createdAt: newDate(), + version: '1.123.45', +}); + +export const factory = { + activity: activityFactory, + apiKey: apiKeyFactory, + asset: assetFactory, + auth: authFactory, + authApiKey: authApiKeyFactory, + authUser: authUserFactory, + library: libraryFactory, + memory: memoryFactory, + partner: partnerFactory, + session: sessionFactory, + stack: stackFactory, + user: userFactory, + versionHistory: versionHistoryFactory, +}; diff --git a/server/test/utils.ts b/server/test/utils.ts index b15888e4a3..988d4cd97e 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -1,3 +1,4 @@ +import { ClassConstructor } from 'class-transformer'; import { Kysely, sql } from 'kysely'; import { PostgresJSDialect } from 'kysely-postgres-js'; import { ChildProcessWithoutNullStreams } from 'node:child_process'; @@ -17,6 +18,7 @@ import { ConfigRepository } from 'src/repositories/config.repository'; import { CronRepository } from 'src/repositories/cron.repository'; import { CryptoRepository } from 'src/repositories/crypto.repository'; import { DatabaseRepository } from 'src/repositories/database.repository'; +import { DownloadRepository } from 'src/repositories/download.repository'; import { EventRepository } from 'src/repositories/event.repository'; import { JobRepository } from 'src/repositories/job.repository'; import { LibraryRepository } from 'src/repositories/library.repository'; @@ -49,47 +51,59 @@ import { ViewRepository } from 'src/repositories/view-repository'; import { BaseService } from 'src/services/base.service'; import { RepositoryInterface } from 'src/types'; import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; -import { newActivityRepositoryMock } from 'test/repositories/activity.repository.mock'; -import { newAlbumUserRepositoryMock } from 'test/repositories/album-user.repository.mock'; -import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock'; -import { newKeyRepositoryMock } from 'test/repositories/api-key.repository.mock'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; -import { newAuditRepositoryMock } from 'test/repositories/audit.repository.mock'; import { newConfigRepositoryMock } from 'test/repositories/config.repository.mock'; -import { newCronRepositoryMock } from 'test/repositories/cron.repository.mock'; import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; -import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; -import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock'; -import { newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock'; -import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock'; -import { newMapRepositoryMock } from 'test/repositories/map.repository.mock'; import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock'; -import { newMemoryRepositoryMock } from 'test/repositories/memory.repository.mock'; import { newMetadataRepositoryMock } from 'test/repositories/metadata.repository.mock'; -import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock'; -import { newNotificationRepositoryMock } from 'test/repositories/notification.repository.mock'; -import { newOAuthRepositoryMock } from 'test/repositories/oauth.repository.mock'; -import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; -import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; -import { newProcessRepositoryMock } from 'test/repositories/process.repository.mock'; -import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock'; -import { newServerInfoRepositoryMock } from 'test/repositories/server-info.repository.mock'; -import { newSessionRepositoryMock } from 'test/repositories/session.repository.mock'; -import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock'; -import { newStackRepositoryMock } from 'test/repositories/stack.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; -import { newSyncRepositoryMock } from 'test/repositories/sync.repository.mock'; import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock'; -import { newTagRepositoryMock } from 'test/repositories/tag.repository.mock'; import { ITelemetryRepositoryMock, newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock'; -import { newTrashRepositoryMock } from 'test/repositories/trash.repository.mock'; -import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; -import { newVersionHistoryRepositoryMock } from 'test/repositories/version-history.repository.mock'; -import { newViewRepositoryMock } from 'test/repositories/view.repository.mock'; import { Readable } from 'typeorm/platform/PlatformTools'; -import { Mocked, vitest } from 'vitest'; +import { assert, Mocked, vitest } from 'vitest'; + +const mockFn = (label: string, { strict }: { strict: boolean }) => { + const message = `Called a mock function without a mock implementation (${label})`; + return vitest.fn().mockImplementation(() => { + if (strict) { + assert.fail(message); + } else { + // console.warn(message); + } + }); +}; + +export const automock = ( + Dependency: ClassConstructor, + options?: { + args?: ConstructorParameters>; + strict?: boolean; + }, +): Mocked => { + const mock: Record = {}; + const strict = options?.strict ?? true; + const args = options?.args ?? []; + + const instance = new Dependency(...args); + for (const property of Object.getOwnPropertyNames(Dependency.prototype)) { + if (property === 'constructor') { + continue; + } + + const label = `${Dependency.name}.${property}`; + // console.log(`Automocking ${label}`); + + const target = instance[property as keyof T]; + if (typeof target === 'function') { + mock[property] = mockFn(label, { strict }); + continue; + } + } + + return mock as Mocked; +}; export type ServiceOverrides = { access: AccessRepository; @@ -103,6 +117,7 @@ export type ServiceOverrides = { cron: CronRepository; crypto: CryptoRepository; database: DatabaseRepository; + downloadRepository: DownloadRepository; event: EventRepository; job: JobRepository; library: LibraryRepository; @@ -150,47 +165,52 @@ export const newTestService = ( Service: Constructor, overrides: Partial = {}, ) => { + const loggerMock = { setContext: () => {} }; + const configMock = { getEnv: () => ({}) }; + const mocks: ServiceMocks = { access: newAccessRepositoryMock(), - logger: newLoggingRepositoryMock(), - cron: newCronRepositoryMock(), + logger: automock(LoggingRepository, { args: [, configMock], strict: false }), + cron: automock(CronRepository, { args: [, loggerMock] }), crypto: newCryptoRepositoryMock(), - activity: newActivityRepositoryMock(), - audit: newAuditRepositoryMock(), - album: newAlbumRepositoryMock(), - albumUser: newAlbumUserRepositoryMock(), + activity: automock(ActivityRepository), + audit: automock(AuditRepository), + album: automock(AlbumRepository, { strict: false }), + albumUser: automock(AlbumUserRepository), asset: newAssetRepositoryMock(), config: newConfigRepositoryMock(), database: newDatabaseRepositoryMock(), - event: newEventRepositoryMock(), + downloadRepository: automock(DownloadRepository, { strict: false }), + event: automock(EventRepository, { args: [, , loggerMock], strict: false }), job: newJobRepositoryMock(), - apiKey: newKeyRepositoryMock(), - library: newLibraryRepositoryMock(), - machineLearning: newMachineLearningRepositoryMock(), - map: newMapRepositoryMock(), + apiKey: automock(ApiKeyRepository), + library: automock(LibraryRepository, { strict: false }), + machineLearning: automock(MachineLearningRepository, { args: [loggerMock], strict: false }), + map: automock(MapRepository, { args: [undefined, undefined, { setContext: () => {} }] }), media: newMediaRepositoryMock(), - memory: newMemoryRepositoryMock(), + memory: automock(MemoryRepository), metadata: newMetadataRepositoryMock(), - move: newMoveRepositoryMock(), - notification: newNotificationRepositoryMock(), - oauth: newOAuthRepositoryMock(), - partner: newPartnerRepositoryMock(), - person: newPersonRepositoryMock(), - process: newProcessRepositoryMock(), - search: newSearchRepositoryMock(), - serverInfo: newServerInfoRepositoryMock(), - session: newSessionRepositoryMock(), - sharedLink: newSharedLinkRepositoryMock(), - stack: newStackRepositoryMock(), + move: automock(MoveRepository, { strict: false }), + notification: automock(NotificationRepository, { args: [loggerMock] }), + oauth: automock(OAuthRepository, { args: [loggerMock] }), + partner: automock(PartnerRepository, { strict: false }), + person: automock(PersonRepository, { strict: false }), + process: automock(ProcessRepository, { args: [loggerMock] }), + search: automock(SearchRepository, { args: [loggerMock], strict: false }), + serverInfo: automock(ServerInfoRepository, { args: [, loggerMock], strict: false }), + session: automock(SessionRepository), + sharedLink: automock(SharedLinkRepository), + stack: automock(StackRepository), storage: newStorageRepositoryMock(), - sync: newSyncRepositoryMock(), + sync: automock(SyncRepository), systemMetadata: newSystemMetadataRepositoryMock(), - tag: newTagRepositoryMock(), + // systemMetadata: automock(SystemMetadataRepository, { strict: false }), + tag: automock(TagRepository, { args: [, loggerMock], strict: false }), telemetry: newTelemetryRepositoryMock(), - trash: newTrashRepositoryMock(), - user: newUserRepositoryMock(), - versionHistory: newVersionHistoryRepositoryMock(), - view: newViewRepositoryMock(), + trash: automock(TrashRepository), + user: automock(UserRepository, { strict: false }), + versionHistory: automock(VersionHistoryRepository), + view: automock(ViewRepository), }; const sut = new Service( @@ -206,6 +226,7 @@ export const newTestService = ( overrides.cron || (mocks.cron as As), overrides.crypto || (mocks.crypto as As), overrides.database || (mocks.database as As), + overrides.downloadRepository || (mocks.downloadRepository as As), overrides.event || (mocks.event as As), overrides.job || (mocks.job as As), overrides.library || (mocks.library as As), diff --git a/server/test/vitest.config.mjs b/server/test/vitest.config.mjs index 92fc027d40..071e4886f2 100644 --- a/server/test/vitest.config.mjs +++ b/server/test/vitest.config.mjs @@ -2,6 +2,9 @@ import swc from 'unplugin-swc'; import tsconfigPaths from 'vite-tsconfig-paths'; import { defineConfig } from 'vitest/config'; +// Set the timezone to UTC to avoid timezone issues during testing +process.env.TZ = 'UTC'; + export default defineConfig({ test: { root: './', diff --git a/web/package-lock.json b/web/package-lock.json index 623a09523a..a2da0e1ae9 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,17 +1,17 @@ { "name": "immich-web", - "version": "1.128.0", + "version": "1.129.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "immich-web", - "version": "1.128.0", + "version": "1.129.0", "license": "GNU Affero General Public License version 3", "dependencies": { "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/sdk": "file:../open-api/typescript-sdk", - "@immich/ui": "^0.16.0", + "@immich/ui": "^0.17.3", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", "@photo-sphere-viewer/core": "^5.11.5", @@ -27,6 +27,7 @@ "justified-layout": "^4.1.0", "lodash-es": "^4.17.21", "luxon": "^3.4.4", + "pmtiles": "^4.3.0", "qrcode": "^1.5.4", "socket.io-client": "~4.8.0", "svelte-gestures": "^5.1.3", @@ -69,8 +70,8 @@ "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-svelte": "^3.3.3", "rollup-plugin-visualizer": "^5.14.0", - "svelte": "^5.17.4", - "svelte-check": "^4.1.4", + "svelte": "^5.22.6", + "svelte-check": "^4.1.5", "tailwindcss": "^3.4.17", "tslib": "^2.6.2", "typescript": "^5.7.3", @@ -80,13 +81,13 @@ }, "../open-api/typescript-sdk": { "name": "@immich/sdk", - "version": "1.128.0", + "version": "1.129.0", "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.13.5", + "@types/node": "^22.13.9", "typescript": "^5.3.3" } }, @@ -793,9 +794,9 @@ } }, "node_modules/@faker-js/faker": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.5.0.tgz", - "integrity": "sha512-3qbjLv+fzuuCg3umxc9/7YjrEXNaKwHgmig949nfyaTx8eL4FAsvFbu+1JcFUj1YAXofhaDn6JdEUBTYuk0Ssw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.6.0.tgz", + "integrity": "sha512-3vm4by+B5lvsFPSyep3ELWmZfE3kicDtmemVpuwl1yH7tqtnHdsA6hG8fbXedMVdkzgtvzWoRgjSB4Q+FHnZiw==", "dev": true, "funding": [ { @@ -1321,9 +1322,9 @@ "link": true }, "node_modules/@immich/ui": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.16.0.tgz", - "integrity": "sha512-itSOu0r7drZ46qJ+9eNNyq5YZVRAeUKwjRdzBUd6uz4Csy3oEDoepEtWF1EHDaph2XUcKJrNvQPUvETTWeQC6g==", + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.17.3.tgz", + "integrity": "sha512-oZ8lJCa/bC+HQYxi4Qi7syW4jWpyyGM4Mue55cvXCECj+292Mu4aOC6oDt7617vMPF7kPblE2A7CpGqJxy5T8g==", "license": "GNU Affero General Public License version 3", "dependencies": { "@mdi/js": "^7.4.47", @@ -2063,6 +2064,15 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", + "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, "node_modules/@sveltejs/adapter-static": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz", @@ -2092,9 +2102,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.17.3.tgz", - "integrity": "sha512-GcNaPDr0ti4O/TonPewkML2DG7UVXkSxPN3nPMlpmx0Rs4b2kVP4gymz98WEHlfzPXdd4uOOT1Js26DtieTNBQ==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.19.0.tgz", + "integrity": "sha512-UTx28Ad4sYsLU//gqkEo5aFOPFBRT2uXCmXTsURqhurDCvzkVwXruJgBcHDaMiK6RKKpYRteDUaXYqZyGPgCXQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2481,9 +2491,10 @@ "dev": true }, "node_modules/@types/leaflet": { - "version": "1.9.8", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.8.tgz", - "integrity": "sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg==", + "version": "1.9.16", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.16.tgz", + "integrity": "sha512-wzZoyySUxkgMZ0ihJ7IaUIblG8Rdc8AbbZKLneyn+QjYsj5q1QU7TEKYqwTr10BGSzY5LI7tJk9Ifo+mEjdFRw==", + "license": "MIT", "dependencies": { "@types/geojson": "*" } @@ -2561,17 +2572,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz", - "integrity": "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz", + "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/type-utils": "8.25.0", - "@typescript-eslint/utils": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/type-utils": "8.26.0", + "@typescript-eslint/utils": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2587,20 +2598,20 @@ "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.25.0.tgz", - "integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz", + "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4" }, "engines": { @@ -2612,18 +2623,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz", - "integrity": "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", + "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0" + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2634,14 +2645,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz", - "integrity": "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz", + "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/utils": "8.25.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/utils": "8.26.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -2654,13 +2665,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.25.0.tgz", - "integrity": "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", + "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", "dev": true, "license": "MIT", "engines": { @@ -2672,14 +2683,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz", - "integrity": "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", + "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2695,7 +2706,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -2725,16 +2736,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.25.0.tgz", - "integrity": "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz", + "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0" + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2745,17 +2756,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz", - "integrity": "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", + "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/types": "8.26.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2780,9 +2791,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.7.tgz", - "integrity": "sha512-Av8WgBJLTrfLOer0uy3CxjlVuWK4CzcLBndW1Nm2vI+3hZ2ozHututkfc7Blu1u6waeQ7J8gzPK/AsBRnWA5mQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.8.tgz", + "integrity": "sha512-y7SAKsQirsEJ2F8bulBck4DoluhI2EEgTimHd6EEUgJBGKy9tC25cpywh1MH4FvDGoG2Unt7+asVd1kj4qOSAw==", "dev": true, "license": "MIT", "dependencies": { @@ -2803,8 +2814,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.0.7", - "vitest": "3.0.7" + "@vitest/browser": "3.0.8", + "vitest": "3.0.8" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2831,14 +2842,14 @@ } }, "node_modules/@vitest/expect": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.7.tgz", - "integrity": "sha512-QP25f+YJhzPfHrHfYHtvRn+uvkCFCqFtW9CktfBxmB+25QqWsx7VB2As6f4GmwllHLDhXNHvqedwhvMmSnNmjw==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.8.tgz", + "integrity": "sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.7", - "@vitest/utils": "3.0.7", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -2847,13 +2858,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.7.tgz", - "integrity": "sha512-qui+3BLz9Eonx4EAuR/i+QlCX6AUZ35taDQgwGkK/Tw6/WgwodSrjN1X2xf69IA/643ZX5zNKIn2svvtZDrs4w==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.8.tgz", + "integrity": "sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.7", + "@vitest/spy": "3.0.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -2874,9 +2885,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.7.tgz", - "integrity": "sha512-CiRY0BViD/V8uwuEzz9Yapyao+M9M008/9oMOSQydwbwb+CMokEq3XVaF3XK/VWaOK0Jm9z7ENhybg70Gtxsmg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", + "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", "dev": true, "license": "MIT", "dependencies": { @@ -2887,13 +2898,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.7.tgz", - "integrity": "sha512-WeEl38Z0S2ZcuRTeyYqaZtm4e26tq6ZFqh5y8YD9YxfWuu0OFiGFUbnxNynwLjNRHPsXyee2M9tV7YxOTPZl2g==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.8.tgz", + "integrity": "sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.0.7", + "@vitest/utils": "3.0.8", "pathe": "^2.0.3" }, "funding": { @@ -2901,13 +2912,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.7.tgz", - "integrity": "sha512-eqTUryJWQN0Rtf5yqCGTQWsCFOQe4eNz5Twsu21xYEcnFJtMU5XvmG0vgebhdLlrHQTSq5p8vWHJIeJQV8ovsA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.8.tgz", + "integrity": "sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.7", + "@vitest/pretty-format": "3.0.8", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -2916,9 +2927,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.7.tgz", - "integrity": "sha512-4T4WcsibB0B6hrKdAZTM37ekuyFZt2cGbEGd2+L0P8ov15J1/HUsUaqkXEQPNAWr4BtPPe1gI+FYfMHhEKfR8w==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.8.tgz", + "integrity": "sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2929,13 +2940,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.7.tgz", - "integrity": "sha512-xePVpCRfooFX3rANQjwoditoXgWb1MaFbzmGuPP59MK6i13mrnDw/yEIyJudLeW6/38mCNcwCiJIGmpDPibAIg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.8.tgz", + "integrity": "sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.7", + "@vitest/pretty-format": "3.0.8", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -2944,9 +2955,9 @@ } }, "node_modules/@zoom-image/core": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@zoom-image/core/-/core-0.39.0.tgz", - "integrity": "sha512-JD6UghIOvfdRdI5FCFQRtvaJGht2gIpkzFp+5NrcwKXbHQwSfl00VQ9JQ0TYbaeHa6tc+dxgepYgJukCtrPVgg==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@zoom-image/core/-/core-0.40.0.tgz", + "integrity": "sha512-NsXhw7QCXu7390rs7AlcB3d8Vx6HnEB9gFHpF9yf5IfhB8wrv37IgIL1FC4QW+1id5FeGv2o62XyPL6vMz3EfA==", "license": "MIT", "dependencies": { "@namnode/store": "^0.1.0" @@ -2957,12 +2968,12 @@ } }, "node_modules/@zoom-image/svelte": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@zoom-image/svelte/-/svelte-0.3.0.tgz", - "integrity": "sha512-0dfAPgpGRm+j6d3fn044swV7r821l2ZFJZmR0WqUATUUaPZ3GbDkDyrVuZGmP7s4QAk/Nvs1F3+cBhcMWt9Zfw==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@zoom-image/svelte/-/svelte-0.3.1.tgz", + "integrity": "sha512-ZJj5/Q11jzcpjItqPJpfvU/XpcWkOehvTcIBZzenGfUWprJ6o8qSesuUAum1WEpv2vbTwUlybe5h3Rim3/vIGA==", "license": "MIT", "dependencies": { - "@zoom-image/core": "0.39.0" + "@zoom-image/core": "0.40.0" }, "funding": { "type": "github", @@ -3019,15 +3030,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-typescript": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", - "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", - "license": "MIT", - "peerDependencies": { - "acorn": ">=8.9.0" - } - }, "node_modules/acorn-walk": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", @@ -4267,22 +4269,22 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.2.tgz", - "integrity": "sha512-1105/17ZIMjmCOJOPNfVdbXafLCLj3hPmkmB7dLgt7XsQ/zkxSuDerE/xgO3RxoHysR1N1whmquY0lSn2O0VLg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz", + "integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==", "dev": true, "license": "MIT", "bin": { - "eslint-config-prettier": "build/bin/cli.js" + "eslint-config-prettier": "bin/cli.js" }, "peerDependencies": { "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-svelte": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.0.2.tgz", - "integrity": "sha512-+0QglmWNryvXXxRQKzLF3i+AreTsueCw7PBb0nGVBq+F9HoYqAjQeJ/9N6vFAtjMjK3wgsETrLVyBKPdeufN6Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.0.3.tgz", + "integrity": "sha512-R7HSKkLN33P6WwYhVbO+5xPT0YIpO+YAZfWxow7I1IvjVgZOxuI7zReqxFL3B7F028u16Megx+hn8SEXDNcDvw==", "dev": true, "license": "MIT", "dependencies": { @@ -4295,10 +4297,10 @@ "postcss-load-config": "^3.1.4", "postcss-safe-parser": "^7.0.0", "semver": "^7.6.3", - "svelte-eslint-parser": "^1.0.0" + "svelte-eslint-parser": "^1.0.1" }, "engines": { - "node": "^18.20.4 || ^20.18.0 || >=22.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://github.com/sponsors/ota-meshi" @@ -6884,12 +6886,12 @@ } }, "node_modules/pmtiles": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pmtiles/-/pmtiles-3.0.3.tgz", - "integrity": "sha512-tj4l3HHJd6/qf9VefzlPK2eYEQgbf+4uXPzNlrj3k7hHvLtibYSxfp51TF6ALt4YezM8MCdiOminnHvdAyqyGg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/pmtiles/-/pmtiles-4.3.0.tgz", + "integrity": "sha512-wnzQeSiYT/MyO63o7AVxwt7+uKqU0QUy2lHrivM7GvecNy0m1A4voVyGey7bujnEW5Hn+ZzLdvHPoFaqrOzbPA==", + "license": "BSD-3-Clause", "dependencies": { - "@types/leaflet": "^1.9.8", - "fflate": "^0.8.0" + "fflate": "^0.8.2" } }, "node_modules/pngjs": { @@ -7114,9 +7116,9 @@ } }, "node_modules/prettier": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", - "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", "bin": { @@ -8337,16 +8339,16 @@ } }, "node_modules/svelte": { - "version": "5.20.5", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.20.5.tgz", - "integrity": "sha512-dpu2lTPVsAAgZFKpF7A9741sBCdXGogfxFU4aQeVgun7GVNCSVheTzj0FsT7g9OsLhBaMX4lKLwVIvmzQGytmQ==", + "version": "5.22.6", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.22.6.tgz", + "integrity": "sha512-dxHyh3USJyayafSt5I5QD7KuoCM5ZGdIOtLQiKHEro7tymdh0jMcNkiSBVHW+LOA2jEqZEHhyfwN6/pCjx0Fug==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", - "acorn-typescript": "^1.4.13", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", @@ -8362,9 +8364,9 @@ } }, "node_modules/svelte-check": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.4.tgz", - "integrity": "sha512-v0j7yLbT29MezzaQJPEDwksybTE2Ups9rUxEXy92T06TiA0cbqcO8wAOwNUVkFW6B0hsYHA+oAX3BS8b/2oHtw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.5.tgz", + "integrity": "sha512-Gb0T2IqBNe1tLB9EB1Qh+LOe+JB8wt2/rNBDGvkxQVvk8vNeAoG+vZgFB/3P5+zC7RWlyBlzm9dVjZFph/maIg==", "dev": true, "license": "MIT", "dependencies": { @@ -8386,9 +8388,9 @@ } }, "node_modules/svelte-check/node_modules/chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { @@ -8402,9 +8404,9 @@ } }, "node_modules/svelte-check/node_modules/fdir": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", - "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -8432,13 +8434,13 @@ } }, "node_modules/svelte-check/node_modules/readdirp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", - "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14.16.0" + "node": ">= 14.18.0" }, "funding": { "type": "individual", @@ -8446,9 +8448,9 @@ } }, "node_modules/svelte-eslint-parser": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.0.0.tgz", - "integrity": "sha512-diZzpeeFhAxormeIhmRS4vXx98GG6T7Dq5y1a6qffqs/5MBrBqqDg8bj88iEohp6bvhU4MIABJmOTa0gXWcbSQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.0.1.tgz", + "integrity": "sha512-JjdEMXOJqy+dxeaElxbN+meTOtVpHfLnq9VGpiTAOLgM0uHO+ogmUsA3IFgx0x3Wl15pqTZWycCikcD7cAQN/g==", "dev": true, "license": "MIT", "dependencies": { @@ -8460,7 +8462,7 @@ "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^18.20.4 || ^20.18.0 || >=22.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://github.com/sponsors/ota-meshi" @@ -8985,6 +8987,16 @@ } } }, + "node_modules/svelte-maplibre/node_modules/pmtiles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/pmtiles/-/pmtiles-3.2.1.tgz", + "integrity": "sha512-3R4fBwwoli5mw7a6t1IGwOtfmcSAODq6Okz0zkXhS1zi9sz1ssjjIfslwPvcWw5TNhdjNBUg9fgfPLeqZlH6ng==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/leaflet": "^1.9.8", + "fflate": "^0.8.0" + } + }, "node_modules/svelte-parse-markup": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/svelte-parse-markup/-/svelte-parse-markup-0.1.5.tgz", @@ -9426,9 +9438,9 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -9652,9 +9664,9 @@ } }, "node_modules/vite-node": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.7.tgz", - "integrity": "sha512-2fX0QwX4GkkkpULXdT1Pf4q0tC1i1lFOyseKoonavXUNlQ77KpW2XqBGGNIm/J4Ows4KxgGJzDguYVPKwG/n5A==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.8.tgz", + "integrity": "sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg==", "dev": true, "license": "MIT", "dependencies": { @@ -9712,19 +9724,19 @@ } }, "node_modules/vitest": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.7.tgz", - "integrity": "sha512-IP7gPK3LS3Fvn44x30X1dM9vtawm0aesAa2yBIZ9vQf+qB69NXC5776+Qmcr7ohUXIQuLhk7xQR0aSUIDPqavg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.8.tgz", + "integrity": "sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.0.7", - "@vitest/mocker": "3.0.7", - "@vitest/pretty-format": "^3.0.7", - "@vitest/runner": "3.0.7", - "@vitest/snapshot": "3.0.7", - "@vitest/spy": "3.0.7", - "@vitest/utils": "3.0.7", + "@vitest/expect": "3.0.8", + "@vitest/mocker": "3.0.8", + "@vitest/pretty-format": "^3.0.8", + "@vitest/runner": "3.0.8", + "@vitest/snapshot": "3.0.8", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.1.0", @@ -9736,7 +9748,7 @@ "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.0.7", + "vite-node": "3.0.8", "why-is-node-running": "^2.3.0" }, "bin": { @@ -9752,8 +9764,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.0.7", - "@vitest/ui": "3.0.7", + "@vitest/browser": "3.0.8", + "@vitest/ui": "3.0.8", "happy-dom": "*", "jsdom": "*" }, diff --git a/web/package.json b/web/package.json index cb17b546a9..77da2d24cb 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "immich-web", - "version": "1.128.0", + "version": "1.129.0", "license": "GNU Affero General Public License version 3", "scripts": { "dev": "vite dev --host 0.0.0.0 --port 3000", @@ -8,7 +8,7 @@ "build:stats": "BUILD_STATS=true vite build", "package": "svelte-kit package", "preview": "vite preview", - "check:svelte": "svelte-check --no-tsconfig --fail-on-warnings --compiler-warnings 'reactive_declaration_non_reactive_property:ignore'", + "check:svelte": "svelte-check --no-tsconfig --fail-on-warnings --compiler-warnings 'reactive_declaration_non_reactive_property:ignore' --ignore src/lib/components/photos-page/asset-grid.svelte", "check:typescript": "tsc --noEmit", "check:watch": "npm run check:svelte -- --watch", "check:code": "npm run format && npm run lint && npm run check:svelte && npm run check:typescript", @@ -56,8 +56,8 @@ "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-svelte": "^3.3.3", "rollup-plugin-visualizer": "^5.14.0", - "svelte": "^5.17.4", - "svelte-check": "^4.1.4", + "svelte": "^5.22.6", + "svelte-check": "^4.1.5", "tailwindcss": "^3.4.17", "tslib": "^2.6.2", "typescript": "^5.7.3", @@ -68,7 +68,7 @@ "dependencies": { "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/sdk": "file:../open-api/typescript-sdk", - "@immich/ui": "^0.16.0", + "@immich/ui": "^0.17.3", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", "@photo-sphere-viewer/core": "^5.11.5", @@ -84,6 +84,7 @@ "justified-layout": "^4.1.0", "lodash-es": "^4.17.21", "luxon": "^3.4.4", + "pmtiles": "^4.3.0", "qrcode": "^1.5.4", "socket.io-client": "~4.8.0", "svelte-gestures": "^5.1.3", diff --git a/web/src/app.css b/web/src/app.css index 9bc1695a8f..a256cc9d80 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -135,32 +135,13 @@ input:focus-visible { } /* width */ - .immich-scrollbar::-webkit-scrollbar { - width: 8px; - } - - /* Track */ - .immich-scrollbar::-webkit-scrollbar-track { - background: #f1f1f1; - border-radius: 16px; - } - - /* Handle */ - .immich-scrollbar::-webkit-scrollbar-thumb { - background: rgba(85, 86, 87, 0.408); - border-radius: 16px; - } - - /* Handle on hover */ - .immich-scrollbar::-webkit-scrollbar-thumb:hover { - background: #4250afad; - border-radius: 16px; + .immich-scrollbar { + scrollbar-width: thin; } /* Hidden scrollbar */ /* width */ - .scrollbar-hidden::-webkit-scrollbar { - display: none; + .scrollbar-hidden { scrollbar-width: none; } diff --git a/web/src/lib/actions/intersection-observer.ts b/web/src/lib/actions/intersection-observer.ts index 3a10074051..74643aa95d 100644 --- a/web/src/lib/actions/intersection-observer.ts +++ b/web/src/lib/actions/intersection-observer.ts @@ -13,6 +13,7 @@ type OnIntersectCallback = (entryOrElement: IntersectionObserverEntry | HTMLElem type OnSeparateCallback = (element: HTMLElement) => unknown; type IntersectionObserverActionProperties = { key?: string; + disabled?: boolean; /** Function to execute when the element leaves the viewport */ onSeparate?: OnSeparateCallback; /** Function to execute when the element enters the viewport */ @@ -83,8 +84,15 @@ const observe = (key: HTMLElement | string, target: HTMLElement, properties: Int }; function configure(key: HTMLElement | string, element: HTMLElement, properties: IntersectionObserverActionProperties) { - elementToConfig.set(key, properties); - observe(key, element, properties); + if (properties.disabled) { + const config = elementToConfig.get(key); + const { observer } = config || {}; + observer?.unobserve(element); + elementToConfig.delete(key); + } else { + elementToConfig.set(key, properties); + observe(key, element, properties); + } } function _intersectionObserver( diff --git a/web/src/lib/actions/resize-observer.ts b/web/src/lib/actions/resize-observer.ts index 9f3adc44b0..4fa35c7d93 100644 --- a/web/src/lib/actions/resize-observer.ts +++ b/web/src/lib/actions/resize-observer.ts @@ -1,4 +1,4 @@ -type OnResizeCallback = (resizeEvent: { target: HTMLElement; width: number; height: number }) => void; +export type OnResizeCallback = (resizeEvent: { target: HTMLElement; width: number; height: number }) => void; let observer: ResizeObserver; let callbacks: WeakMap; diff --git a/web/src/lib/assets/immich-logo-inline-dark.svg b/web/src/lib/assets/immich-logo-inline-dark.svg deleted file mode 100644 index 024337c2e3..0000000000 --- a/web/src/lib/assets/immich-logo-inline-dark.svg +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/lib/assets/immich-logo-inline-light.svg b/web/src/lib/assets/immich-logo-inline-light.svg deleted file mode 100644 index 216466f58d..0000000000 --- a/web/src/lib/assets/immich-logo-inline-light.svg +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/lib/assets/immich-logo-stacked-dark.svg b/web/src/lib/assets/immich-logo-stacked-dark.svg deleted file mode 100644 index 7f8381869a..0000000000 --- a/web/src/lib/assets/immich-logo-stacked-dark.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/lib/assets/immich-logo-stacked-light.svg b/web/src/lib/assets/immich-logo-stacked-light.svg deleted file mode 100644 index 8c4505d97e..0000000000 --- a/web/src/lib/assets/immich-logo-stacked-light.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/lib/assets/immich-logo.json b/web/src/lib/assets/immich-logo.json deleted file mode 100644 index 9422c6398d..0000000000 --- a/web/src/lib/assets/immich-logo.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "content": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAFACAYAAAAszc0KAAAAxnpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjabVBbEoMwCPzPKXoEXonkOLHqTG/Q4xeEzKjTdbKLICuh7N/PUV4OQilSF229NTBIl07DAoXAOBlBTj5hJcrsLV9khmTKppwNLRRnPhum4rCoXoz0nYX1XugSSvowinmAfSKPtzTqacQUBUyDEdeC1nW5XmHd4Q6NU5zWw+bxHWXz810W295W7T9MtDMyGDO3GID9SOFhARsjd/vQn5GMrDmJLeTfnibKD6lPWpnDBF3gAAABhGlDQ1BJQ0MgcHJvZmlsZQAAeJx9kT1Iw0AcxV9bpSJVQYuIOGSoTnZRUcdahSJUCLVCqw4ml35Bk4YkxcVRcC04+LFYdXBx1tXBVRAEP0CcHZwUXaTE/yWFFjEeHPfj3b3H3TvAXy8z1eyIAapmGalEXMhkV4XgKwLoxyBm0CsxU58TxSQ8x9c9fHy9i/Is73N/jh4lZzLAJxDHmG5YxBvE05uWznmfOMyKkkJ8Tjxu0AWJH7kuu/zGueCwn2eGjXRqnjhMLBTaWG5jVjRU4iniiKJqlO/PuKxw3uKslquseU/+wlBOW1nmOs0RJLCIJYgQIKOKEsqwEKVVI8VEivbjHv5hxy+SSyZXCYwcC6hAheT4wf/gd7dmfnLCTQrFgc4X2/4YBYK7QKNm29/Htt04AQLPwJXW8lfqwOwn6bWWFjkC+raBi+uWJu8BlzvA0JMuGZIjBWj683ng/Yy+KQsM3ALda25vzX2cPgBp6ip5AxwcAmMFyl73eHdXe2//nmn29wO1pXLBEGyzRgAADXppVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDQuNC4wLUV4aXYyIj4KIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgIHhtbG5zOkdJTVA9Imh0dHA6Ly93d3cuZ2ltcC5vcmcveG1wLyIKICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICB4bXBNTTpEb2N1bWVudElEPSJnaW1wOmRvY2lkOmdpbXA6MDY0YmE0N2YtMDU3MC00OTBmLWJkOGUtY2UzNDFhYjk0ZGY1IgogICB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjdmYzk2NDZlLTE5NWQtNGE1Mi1iYTE3LWM0NDM4NmQyMWU1ZiIKICAgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjM0MGQxNWRmLTgzZDMtNDQ1Yi1hMzkwLTBjNTQ1NGNiMWI3ZCIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iTWFjIE9TIgogICBHSU1QOlRpbWVTdGFtcD0iMTcxMDgwMjgyMTQ5NjYwMyIKICAgR0lNUDpWZXJzaW9uPSIyLjEwLjM2IgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjQ6MDM6MTlUMDA6MDA6MTgrMDE6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDI0OjAzOjE5VDAwOjAwOjE4KzAxOjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MTEwZjhhOTQtNTQ4MS00MTkxLTkxYjktZjQ3NzA3ZDUyYTVlIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKE1hYyBPUykiCiAgICAgIHN0RXZ0OndoZW49IjIwMjQtMDMtMTlUMDA6MDA6MjErMDE6MDAiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSJ3Ij8+fd4ZrQAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAAd0SU1FB+gDEhcAFf/Hh5UAACAASURBVHja7d15nBT1nTfwT3fPTPfcMMoMwqgzcnvwgERAnnXETTYZnyCR4OYBEhLdx30E8yhiEkUTA2zceCUq+CyQXfcRY4Q8CTgq7oKRuGj2EUhUDrkPRTkEBplh7p6rnj9qqqeq+ldXd3V3dffn/XrNS+mjuqq6uuvT3/odPkmSJBARERFR1vBzFxARERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASERERMQASEREREQMgERERETEAEhEREREDIBERERGlixzuAnKTz+dzdXmSJHGnEhERMQBSuoe6RL02wyIREREDIKVJyEvGNjAcEhERMQAy7HGbGQqJiIgBkBj4uF8YComIiAGQGPgcSWR4StY2qV+HYZCIiDI2K0g8yzHwpTjcpct+4EeFiIgYACnjwk6mHgrcV0RERAyADDIMMNyXRETEAMjdkNlBhW8x9zUREREDYBaEEb6t3P9EREQMgBkeOvg28r0hIiJiAMyCcMG3ju8XERERA2AWBAm+XQyDREREDIBZEBr4FjHQExERMQAy+Al1bXkXrQ8vRu/xEwnbBv+llchfeA+Cf/tNvqEMgkRExABIqQh9ao3XTkFvfX3itykYxMAje/jmeuz9JyIisotzAafJiT8Z1T27gt9i9c/p+2r3eFAexyBIREQJzSmsAKY2/Nnd/U6re2XHDzte7/anl6P9medcXy4l7ocBERERA2AGBj8vVf6c8l9aidy/moKu/3zPcv2zrW1hIo4VIiIiBsAMOZk3TrkpKjzpq3DJavuX8P2YhW0LGQSJiIgBMINP4HZ3c/j3r6D9mefcq/j5fIDdt9jngyRJ8CXjtSyWkY29je0cS/y4EhERA2AGBT/AXhs8K2XHD8ddEcxfeA/Cv/mta1XF3Kk1KHp+BXzBoOZ2q/XM1t7GDIJERMQAmEHBz7XqnhuVtiS9pg+A1bOkvsfphebORsHP/4HHGYMgERG5zM9d4O5JWZIk4Um5/enlaL3/QVvhL3dqDQYe2YOy44cjf/5Bg9QvYnud1cvKX3hP7Buve01luVbLtLOmor1advxw1oY/s+Mo1qBIRESkOX+wAuhO8FNO2mqu9eBNRcXPzjoJgqGdil/Ur5BLKxH6u++hbek/Gm5zts9AwsvCRETEAJgmJ15RD1435Ez8EqSuTvTs2O3qcvMX3oOOFf8MKRx2ZXkD936AjudXo+NfX4TU1BR1f+7UGuR+9SuQ6usNH6N5L7J8BhKGQCIiYgD0wAnXate1/9Ov0P74L9xdJxhU2DxWJfQPGgQpHLYMdZr9CZj2QM7mNoFuHpdEREQMgDGcYN0+yYouFVuFIc+FvniHkLHAGUhSe4wSEVFmYScQhydWu43znRC1E7QVpLx0gu8Lf26vkQQg+K2ZPDBjOA7ZQYSIiAzPEawAOgt/iaC+VBxV+YujyudTLU/S3Wb3uTHuyEg1MHdqDQLDr0D31u3o3rvf0Xob/mrJ8g4hsQQ9fsyJiIgB0MMnUPXgyEYDKAPA+UtHaP6tfmy8A0GXHT+M8OtvoO2eH0Dq7Y15Ofl3/08gGLTVwUOzr3UhWHQ5PNs7hDAEEhERA2ACT5zJ2j2idoADj+wRhj9RALQTpDIJO4R4+3gmIiJvy+HJ0hsny9b7F0VV7YzCX/vTy+1tn/r/y8pQ8u+voPP/rndckYNuOaV/egv+khI0DL/atSFjnGCHEPNj1uy49vVdniciIgZAhr8UBj9FVPiDvSqf7e31+3Fh8tS4l1P6p7fQ9eZmtD35dPLDn8+H3Osn8VNrIwSaHeMMgURElJWXgL0W/uBC2NNXxaSmJjRcNcHRMvyDBiGv9ivIveXraP7Wd8S/GK4dh+4Pd8a238vKIJ0/H997x7Z/aX+sExFR6mXdMDBeOCF2bXkXjVNuwvlLR0T+HL9xQy4Rb0NTE9qfXo7G62+yHfpCc2ej+He/wYAP34N/zBi0/mCR4eNjDX8AIDU22nmDzJeRgkvO6czsmOYwMUREWVwgyKYKoFeqIU576Rr1BlYHx1h63KqXKzU1ofX+Reh8863Y9m1f20BfMIiWO+9G15Z349pH+kqjLxiMhL/8hfcg//57+elNw2OfiIgYAD1x8kvFCdBJxS84fRoKnn7c1lAwTnr9qpfb/eFOtPyvhTHPW+wvLUHJe/8Bf0mJvB7hsKMQGBh+BXqOfGy5vuHX3+gPvIt+iPzv38VPcBp/DoiIiAEwq056ot6z6gqXLxhEaOE9woAjGi5Gt8HCgaOVNoLh37+C9meeEz8/jkGn/ZdWIvjtWfJrvPxb0zBpNpBz+9PL0f7Mc9bvK9sCMgQSEREDYCwnvFRtunrmD0Ae0w7BIDqeX20ZcBqn3BRTpa7s+GHb4SoedquQZgGua+t2tH5/oeVlcg4HwxBIREQMgGkR/sx0vvkWWu682zTg6MOjEf+llTFf0rXDf2kleo6fEIc91VRwvkAAUk+P4wDXW1+P1u8vRNfW7QyAbujeBHQ9AeR8D8i93fTzwQBIRJT5MrYXcDo2es/72t9YPib/+3eh7PhhlB0/jIFH9iB3ao04QDkIf4Gqyx0Hq15V+JOidzB8kDtrDDi4y3AdTQ/MQYNQ/LvfoPDpJwwfY3dAbALQcQfQvQXomG/5OWDvYCIiBkCGvxRqnHITOl5aa7ydwSCKnl9he3miLQ+MvQY9xz7VBK/GKTc52dlypU8U0J55Dh3Pr0bR8ysQ/NZM4faFf/+K6eJF7QTVy/dECOxaDbRWA82++P5aq4HOVe69Tmu1/BwAkE4rSdDW54EhkIgos2XcJeB0D3+iDiJWw56on5O/8B50rPhn4Xh5onZ36iFpcqfWoPvDnY6niVMvV98DWP+a+p7LdjpzqJ+j7ixjd/8kVHgJ0LnU3WXmLQaCS1x6nRCQ96D2ucVSRv5gIiIi+zKqApgJJ7LQwnuibrOqdAXnztY81nAfqIKTMhi1uqNF15Z3Y5ojWAqHI8trGH61ZvgXKRw2rfRJ4bBlFTDe/ZMwnavcD3+AvEx9NS/m1+nQPjd3lqPPBiuBRESZKWMqgJlWxRCNpWdU6RI9NndqjXAcvpJNryPnqjGOB6PWKzt+WFN5FFXmNO9PX6VPNAaiVRVQ/RylfaJoOUmvBLZc0n9pNacWyK8DENLebh1pgeJ2Oai1z5A7a9h9Xt48ubrnGxx9d7Pg86Bexyz4DBERUYYHwEw9cTkNgQ3Dr478e+CRPWj9/sKYZ/awEwDblv6jZugaK2Y9k806ntgNgECSB4huTnJ1zAdxQ04A8FfJ1b28xXLAc7pu/iog90H4gvMZAomIskDaXwLO5KqF0slD3YvW6HKnLxjUzBbS8U+/QtHzK5C/6IdAIOD6urU9+hjyF/1Q2MPXqIeyWc/keC7hql+nI8HjHGqFrB9SLIn/7Dw3p1auDirPKZLkCl5gXPRje48B4ceB1vFAeL7zTek9BoTnQ+pYHNNnjYiI0ixjpHsF0NWxzM5cAHYeA9pUlzILgsDoIcDlg1K2jUZTq+ln1Gj5+7vRuam/4udkajg3KVU6p1PCGVXvrCqAA4/s0VQ/kzY+YPtN8tAqhgcnAF+VtjIXee7N1pd7/VXy8/rG7dPoflVu29ez0/3tylsMX2hpRv6oIiKivlMMw5/Khx9rwx8g/3vXp6ndTkElEJAram0P/bQ/E+zZF5U/nAY39V+i1ttIrNU70TzJCdV7AGi7Xhv+cmoRVdWToK3M9WyTbw8vsdfWr/eYZtw+jZxbgYId/dVB3wDr5RV9blCRbO9b/z6dSw0rgawCEhExAGZW+OvqATq6xPf19KZ+e/vClH48PXUv2uB3ZouzwrXjEPz7vzNdfnD6NPfykapziZOxCs06kXiC1CiHN3WYU8Jffl1flc8iNLZ/z1mP3pzJ9tZJarReVssl/eMDhucDPUqADcnrHxUCH2MIJCJiAPR++IvZJ2eBN3WX0mZM9N52B4Mo/OXjKDt+WFP1ar3/QTROuQn+8kFRlTv/pZXIu+2bKPzpQ6ZVvfDrb2iGa3EyNIvehetvQtvSf4wEQX37xLTUuapvoOal6B9MOQTkPQbkb5T/P7hIW1nL3wj4LtYup/vXzl63+x3zwZ5bBjofIqb3mLw9bTfJobDzWcMQmLTPIBERMQDGKqbq3/6Tcru/LtV8tRWlnt9W/Xh46svB6rDVe/wE2h7+qa1OFsoy2p9ejtb7H4z9fQiH0fH8ak0QFI3flzbCS+SKmbrKFpgMFO6QQ5+RjjsA6Zyz1wroK34JbnMnnQbCC+VtVEJg5FJ2h2mnECIiYgBMGlcv/e49ARw4GX37mQtA3Z89vR/yv3+X3MNXF7zOXzpCeCm1/Znn0P5Pv7IV3tpd6kmrDoLSuXMY8OF76Rn+1JUwfxUQegEo2Ar4R4uf071JrhbaHgsQ/Q02lUvLie5roV++Mvh06xjAP1hzO9sDEhExAGZG+GtuB97ZBxw6ldZvXv7374q6HKyXuiFSxEHQir5SmdK5fvXhL6cWKNwv7pWr1nGHfJnV8tM32jiQCQ/1UNQ0bo6GlbFcPuT11q87QyAREQNg2jpzAXhzl1zZ2/wRcL6lb2Dd9B/Wwuzyqn5atlSy8/rqSmXHS2s11UijjiqNU25yf2VF4c9oFg2l4qe0y7NT+cupkTuGmO6wqGQZPcCz8pqRdoluvFGC29gekIgoo6TNOIBxV/827tD28tXPqiDq9PH6+/09gEcPBcYMTYs3VT1Fm9uMpphz9b3umxpOPV1d7tQaFD2/IlLtNJoJBHBhHEAn4Q+wOfVbSO4lrLQZ1E8jF1oLdMx2MBVcij6HJYjvc0hERJ6Qk/Hh79N64MCp6CFe1E/NDYifox7+5cBJcXtBwBODRWuixsJ70P74LxKy7J69+/VvDiRJEl5RLHp+BTqeeQ7d+udYUMKrejgZJfx1bXkXrQ8bd0wIzZ0d24Z1b5I7eugvfxqFP6PHiwQmy+0GNZd8VYGxZ6fcmzcNSE3iEOjrOw6IiCg9pEUFMOYAuN8ktOkpFUAnz9Gc5P3A9C95cv91792PptrpSX/dsuOHHc8Gon6uaAYQdVVQ9JyYiap4ZpU/W1U/RLfZU7g1j7B6+frKpRNKdbJ9hmUV0rAK2LEYCC7htyoREQNggsOfaOq26AcCdtoo+Xzxtwf0WCVQre3hn6LjpbVpc2AaBUCjS7+hubNR8PN/iDEhb5KnZlPLnSVX7ZTw56TiFw9l/j7/aOs2gspUc4GJQM92oPdT7X2J+mTn18GXO0O8+k2Qe0qLpr8jIiLPSMtOIJHMKpq6TRTsKkrl6pzZ4M5Owt/oofLy9OMFemDaOMNs+vN/sOwxnGzFL/2r4TRxXVu3R91m1CO47Pjh2MMfIPfa1ayYJLfJU4cXuz174/7FAzk42ZnZQ5lqrut32vCXU6sNf0Y9h/VTwBnShbienebVd9H0d0RE5CmebgNo2bvQaOo2veYO4MQX7lXm1O0B9RVGD0wbZ3oqv/t/ujbOn5GLThyxfMwXlcPR+vBilG56DQ1XTYh+y771Hc2/W3+wCOHfrY/enljb/EVCVKP2Um7ePOvHJFqsl3EBIDBJvmzdnK9af926S6cB32BEBn7umA90rTb7oAnWT1zZ85X0VQGB/unvALkqmHMrkPdg32sTEVFKM5ZXLwHbavenHqxZqe6duQD85Yh2Zo942b2MrPDwpeD2p5e7FgDtBD2nvqgcbn1sBIMo2fQ6AsOviO1FzC7nKtWyrtVy0LFb9fNXJadC6CX+KviKxNssNQ8wr2L6q+RKp9WYikRExAAYFQD1w7RUlwN/2NV3m9KYyvUVs3e52KOdQsw6UaQi8MUSCPMX/RD5378r9oUadeDIqZXn742nM0W2fYEYdQjp/Vy+bG7aoSQkX4YmIiIGQNvhDwA++gw4ojqR5wSAbn3lLwFBsCAIDCwATjZYP656EDByiGf2rboThZ1hWlIR+qzCYNzj/Ol74KqrUQx/7oVA9ee1+1V5v/bsNN73RETEAGgZ/gC50rf9sHzZV6SiFJg0Ajj0eWxDu5jRV/jM5g7+66uB0gLPBUCzYVq8FPxsHQuxBkC3hlHJNMWSo6FqRCHQ8H1qzoe2XSErgUREWR8AHY/519Mr97z9tN4oLYjb77kx7It6Wb298n+tlpvi9oGioVWkcBhtDy9G+HfrPR384g6CorZ/SgDsXCXfR6rjGuI5iiV7AdDwPQo/DnQ+FB04iYgoadJiGBjTE31bGGhuNw9nRsHQvRXsfx2r5XpwqBhfMIiip59wJfydH3aV8ab/chkkSYr8NYy/Pu4fC47moTUaykU6DYQXOvjUVAGBcd548wLj5AGcfRf13RAyHtrF1pAv6v1icVuxBKU3cKTnrx3BRQx8RESpPvd7qQLouPp35DSw74R26BWjCpxRJbC8BDjblJgNqi4HPjkrvq8gCIy8RH5MEokqgI5CVDr+SFBEtf0bDaAj+3rvxipnKtC9RRsAVdU8x/MEG12KJyKihPOn7ZrvPyl3AtGPu2d0shGFnBkTgf86OiGdhQEA46rk11D+Aqrd3RaWZzHZfzKlu9Fu+Gv75TJv/5JxGmKLJXmYErfDX2SQZd2fabCqBYobnFfokq1bMKizqponNYGIiNKE5yuAwtX75KwcnkxZ9P6tvAgozpc7h+hfI+B3b0BndaXv0Clg7wnzxya4faBSAbRzubfl/gdR9PQTnjlYG66fioFbt1g+znbFqdnHbwAn8ubJbSXV+1C3b+31CDZpi0lEREnhmQqgowqOqEdvwA9cVQmEcpUlGj+/MARcWw0cOGFcMdRX7GKlrvSNHCIvVzSNnPLYBLcP9AWDttv6Ffx8KZq+83eGJ3TlT3R7b0eH5rnnh11luCwz6ucM3LrFtI1hTMdSuvBCPtIP4RKrZE2rR0RExudKr1QAbVf/APGQK9dcBhw9I5gbOMZxAAuC8lPbbcw1HMsuDOXKs5WIKo0JbB/oNBz1dnSg5c67UfKb/6O5veH6qchf9AMEp0/TLFP9nknhMPyhkGZZbQ8vdlRVjOU5hscPK37J+SyLhoRpqTIOfXnzgOBK7jgiomwLgI47f4gCYCjX/tzAbhk9FDj8eXyXi0cPBcYM1c5qor8vReHPKgQCQOd725A3ZbLhe9Yw/GqUHd0beWzu9ZMi9zVeOwUDd2yNes9FgTLWddesT9T4czHKqZXn0FXmw3VruZ4TAgr+A+j6FdD1W9vbaHgZWNRGkJd+iYhSwrOdQBzl0kEl9sOfm5cHD5zsD23K+jpd/oGTctvA0UPE9+085kqwjeeyqD8UQtHzK4T3tf5gkelz8772N5rHSmG5otq9d39U+Ev4j4vcWe6HP0CeySIjdchD3oReAIo+t12ls90ZJG8ev4GJiFIk5RVAx9W/C23A23v6/x3wA8MGyyFKr6JUPEvIjIny8DEHT2mDYTJ2RUEQGFYBnL1gPIOJ2XNj6CRiFf4arp+K/IX3IPStmaaPOz/sqkg1zzAy/G49gn/7zci/e+vrcWFqLcr2fRi57Yuq0bjo2AHD993NCmBkOWaXIEUhTzSHrSj8iYg6OQiTdRXgvx7orkPKK4j+Kuft8oolw211PCQMEREl92vfiytlOuvHBx/rtsAnDn8AUFpo/CL6mUPUr5nIpmJtYXn4mtJC551MYugkYic4Ddy6BW0P/dTycepqnuEq6pbjHzQIBUt+rLnNKPwl9IdGkYNwk19ncnvI+vlGnRzy5mmf33sM6F6rCn+h1H3oYu2UYbCthlXA8BJ+6xKRrXOX48H+Kf0DoKFDn8sVQLWuHpPHnzI5cfVdVjWa7UACkJeTwG05FdvzenqBN3fJw8lYtD00++C0/e9Vmn+rK3sN109F+6/XROefJQ/j/JXXRv4t6pFbdnSv5oPr8/kMK4sN109N7hdKidGnoEp3g1EQs1H5a62WZxbRB7/8jX1VRbNKn0faEebNk8Ou1Wwnzb7obbX6EdW5lCGQiOIuXFC2BcBjZ43vqyiVh1cxus/wSDO4fcxQ4MvXuNdmcPTQ6OFfenrlKuDwwcDN482HiNGkt7AcIP9jL3C+JabVKfhf8wxD3MCtW5D/3TlRgz8HysujqnmSJKFrz77Y9klH2NbDeuvrE3dMFUtAwVbnYU+4PYJqWLEkt52LaeiTEJD3mLyMoga5raFvQGI/Y8r65twKFOzQDmZt1dYxpxYobu9bX8k4hDMEEhFldwB0NPQLYN4ZYtII40uqk0Zo/x3Vi1jwmgdOAht39F0adqHd0oGTck9ffQWzp1ee0m7jDrmy99k5eX3ttPNrbgfe2SesJtr9BRVaeI9xSPzBgsj/N1w/Nap9n1I1zLlqDFruf9DxLtF3Aml96JGoxzROuQmB8v7hcC587RbTZUqdL8jt/YyOOVEVsFM1xIxS9RKFnWaf+Z+oGmZ2n5nAZHkdun8lP79loBycpMbEfSDz5snLDy+RK5n67etcav583SVy089y51J5ma3VQNdqfhMTESU7g6WyE4jjACga/kUxY6LxY2ZMtPfcnl5g+2H7nTOMOpnYUVrQl2jaou8TDf8iGibG5DmJGPLl/LCrMPDIHmEnDSkcFj5PeU4kSKqGhTF6383WPbzpDwjWflUc/joWwxdaams7I23UiiWg5ZL+gJZfJ1e/1AEuqUJyFU06DbRUQ3hZ2F8lh8PwQ86DpZp6CBapEeh8FuhaZh0yc2qBnm3RjxMM6SL8jDcZbDMRkeB7g53HEiMn47cwlCtX1mxVXfxy9W3Xp9GdREQmjQD+30Hgi2bn6yUKfooDJ7WznRQEgaKQvecUBOGrHRfz7vKHQnIILB8UNfhy2dG9UZfEG6fchOD0ryO08B4UPb8i6nnq5zRcPxU5147DF52duOj44f5l9LUFDP/+FbQ/85zhujV95++iwp/UtREIz4ev6Jjt8BedSFQhSh3+UqLDOnT2HpMvKcf1DRtHuBX1kHYypEvu7bqqXweIiCjJQTtVFUDHw78AziuAFaVyGzmzjiLq56r19MpVNzOjh8rtEpMxAHXA39/G8dN6uSexYLt835zk+kvbGf6lcepXUfjLJ5BzrRw+jSp9Rtp+uUxzydl2dmseDF+xuAomtcnDtvh8+eL7mxA9J7C+ipWxgzyb/QqokiuMubf336aukupZDOZsWOk32+9ElL3BhBXA5HzVe2llXH+TJ42wDn+DDLqGnvgClm3/DpxM3uwjPb1yuK37M/DhJ9bb5SKzdoKKAVv+gNwJ4yM9f52EPwCm4U/UNlBp62cU/gDYH7bFTKYM8myn2KcM+hxc2d9Gz6oNIwdzJiJKS5l9CfjQ59aPMepFe6weiR0Q0AaHU80lovoH9PcYTpXCx36mDX8di+HLs3MJ1Dz8+UqU4R9DiFT5enZqhz8JLpL/THUA7TPEl0YT9skVDErdsxNoux7CiqXkoJ2dWbUPYLWOiCgD+DNqa/RVMXU7OiOicHWhLebhVVRn3Pi3Rz3VXKxr0d6ZlF1v1TvXCVHFL7I94ZXCtn5SR4whtbVaO7RKZyztCENyGFNfMk2k3FmC6mYHEL4jOvz5VPdb9WK202PZpYofx/kiIn43pHhfp6INoOPevwqzNoCTRgAffiy+NGrVW/eqSmDkEPkxO4/J4+xFnUVtrF9BEGjt6O8ocfN4YPNu9y/XGkwJJ9yvr/0FGD0EvlFDXX8f7bQNjHU5+unp1J09ogJhcL44LNqcRi6qV6q/Csh90H7Y6VotB8dYZ9Owou+ZLBJeKPfiVQJp4Q7APzr+NowuVfssewOzqhi1r9juiRj++DlIpMypAO46Zhy09OMA6h3oG0dPGP7QF/5s/CppC2t7yYZygWsugyvVQP3r6KaEMwo5vm9c1799LgvOne34OaLqnmg5+unpfLk3i6dzCy90f8N6j8nz29oZrDi8JMZBnm3KqTUPf70H5Mu+kfAHud2if3T//8cqWe37cmr5TSz4HLMSQtmO4Y8B0B6jzhjV5dZz7iqXWQfkmx2KztanuEBe7uWDVOHRxYO5IGi/beA3rkvILi9c8hOEN/3B1uVfJfjp2/OZLUczPd3k8ej4taDDTt7t5ifT1urYN1DfEUL017k0Mcdz3jy5Kpa/Mfo+Zcq5Zh/QOkYek08dptRtFoOLtLN5OPkLrnS+3l2rxYNIC799quROJ6JtJCKixP7gzJhLwCLq4V2snjtjojwbh5u9eovzgWur5dk6ROvkdOBpo+WXFWVUtcDo0rKdS85SUwi+EueXPCWpPb6OHKJOGXYvvzq9/CnspBGSq32WHVYSKLzEMBCLZmDhr3vz70fuH8rWY5/fEcnh5y5Q0Ye/eEOVMlWbPsTW/dn5tG8my7ds47bjk7R6G4yGnQl+65vWT7a45Gk8TVxfR45YLkcKO2XA3uVXJ5dZleqaPvwFJstt/twIfz075cvqoipeqqqhWUaSJJ74iCjxgTvZFcC4Gnk6rQCqp0cTPTfg77+MOqgEqG+Kb+MKQ8CXr5bHEDQYqNl0HdWspn5T79MEDf+S9idSB9XA/mPQeWGBnAAAIABJREFUwZAuoqpfohhV19zoPNGzE+h+Eeh+1Z22jAb7hQ28ichJPuD3Q2Jl9jiAB04CAZ/cw1d44lMFLKfhz+8DenUHZ2uH9ewhonW0M1xNrCFo0065F3BVefYd3XmLATxk77FOp0XLnSW3X0tk+Ot+Va7GGYUyq+qhMr9v94uJ66SSiv1CRBkf/ogB0JjV0C6RgHXKOADGozfGXybxtDU0qhaafahqx8lDwWTjF0roob5fkXL7Pl+BcVXPVyIYDsYsWAaXJHblpdNA+2xEtSG0U3VUgl/XMvn/He20wUDurUDOfwcCU/kNSUQpCX+s/iVhv6ftJeDpX7JfbZsx0fnlY6eK8+U2ecl+A2O4/JuNVUFJagc65sOXv9r4MbG0ABDNmxuL7k3y0DNmlbrcWUDwGaBzlbtVvSSFPn7JE5Gd7wZ+LyRH+lYAA3H0Xxk91J3LrhWlcicO0bqcawL+dMDeMsZVAZs/Mm7zZ7faqXx4XvtLJCCLhoDJxqqgz5evCYPqf4vpp04zaBvYewzomB9/AOy4w3gGjvw6OZh1PisP++KkqudWQCUiosw6L6ZtBbAgaDBoc5JcXCK/fqLXobpcHkx616fAp/XR+5MdQFwjNWXQxngw+LECSER2vhv4vcAAaB4AUykRYwbqlx/H/qQYA6AkxT91WpaGOwZAImIATLM6gSdOuumm7s+JC38FwcQtW73f02xswKSJZ+q0ZAS/0AtA4SdpeUlX9FnnDxgiohQF72RWAOOuAIjGxnPYPi4hpk0AjpyWB3ZOxCXhgiAweohwwGieQFP0Y6RnJ9A2PrYXSub4gR7/pc9f+0TECmCKagpptbbVgl6r8cyk4YaAH3hzp9ypJJ7wZ7YNbWG5DSB5R2Ccs1k8FEazhhARESUzeKdVBRAATjXI8+cqjNrLbT8EnGpMj3ehulzuCXyqQQ6SF9rMH18QBCovkodyyQnwKHYJf3WmyXcAEWX09wK/E5Ij/YaBsQpHivOt9pd53XDgs/rkXkoWBdchA+U/NdFl77YwcOgU8HkDj2AiogwKQQw/xAAo8slZ6/H7Pq2XZ/+w25GiohSoLAMuGWA41EpC6Hs0G7XzGz0E2HtCvAyDgaelY2eRlVO/ERGlcfhT/p8hkJJy3KXVJWDR0CvqStp+i3l19bN1mA3kvD+xc/QKBfzyAM5WPq0HPvoM6OqR9yvHAnQFv3TT4DuAKAs+F9n2meAl4NRIrwqgqKrnZGxAfcXsuuHi8Gen0pgIPb2ujXWYDtO9bTzZgSf2tuG/Xx7E/FGFDH9ElLU/PnkJmBgAzQT8xtOlqdkdGuadfcC11UBZkfb2o2fiW0+lsviHXeYVS4WonV+8PN5z+PO2HlTXnUPHtyuw7eUz/CQSUczMhsNKl0DF4Bff+8z96Fx6DQMzeoj1YyovksOXkWLVHLDN7XII1Lf7u7g49nVUXj/gjw5/1eWxb1csLrs4IYvdeLIDVa/I+6zqlXq8cKTN9nNfONKGqlfqcUlBAB3frgCAyH+JiNwOBT6fj+OlZkn44/vtcJ+m3TAwQHTFTNR2TlRVU6pvujZ0COUCN493f4PVl3MdTO+WyA+HGwb//ixO/21/mA29fMZWiFu8sxlLxxV78oPAX41p9h1A5PB7j8dZ+n4nxHJ+4/ttzZ+Wa62vmIkun+ofo66+XT4I+MrY/n93dCVl+rVMoQ5/gHUFryHci/v+Yhz+xr3xBcMfkUVFg1WN+H70ch+mBzfCH99ve3LScq1HDpH/zNrOKY8xEsrVtincuEP+r8m0a9lu48kOzN/ejGPftL9vVh5sRXXdOTTO6g+No187hwPf6L88vXhsIWZw9xLFHXISsfx4fiAlqnen0Xqrl2/0mFg6W2RyL1U3jrFE7Q8773OyPiuZyJ/Wa69U+apj7Okqep5bnSeSXFFMxhfSHe81OQp/i3c2Y/6oQk34q93cgFCg/4M6eeMXmHEZp0Uj8uqJP9ZKiug5blRl7D7f6jvR7rokajus1isZgcat10jE+jpdHq/kxLCP07INoJuMpl+LtxK485g8nAwAlBYAf3112lcL7DCqEla9Uo/FYwtxsKkHj18rXwoOvXwGO75ehjEDcmN+vXWfbsbyQ2vx7t/8a0zPv+K1aZaPqSyowL0jZ2PmZV/mN0Ymfgek0f5KdAXNrROs1fJj3Q4nFSE3npuo7Uj2+52s80Us6yvaZrffZ37HiCW1AujJsu2QgXI4mzFROyZgPJXA/Sf7wx8AjB6acQeO0QdKVCWs3dyA/dMvwrGW/vAHyJd+4wl/zx54Gbdd/pWYw59dJ9rO4JHdK/htQRkbwt1ctlcvxTkNsHarg+l46TGTL5fy8rBHA6DniTqXvLnL2fRw+kGkK0qj5/dNwRfcYx+1RIZuMWI0pIt62BcjdZ/Jj9F3EJn1biPqppZi9dF2TSeQ2s0NeOiaItvb9s6Z91Hz1v/Q3Hbf6G/Htb/sVP8U4d5O3Lj5Tqw5tpGfkxRXDMj9E78kSVF/TsOO0/ZabrU9c3IMxVMpzMZjwOlfotYvEe8zMQBqjRwSfyVQH/7MxiRMooeuKbJsv3fsm4Mwf3tz1O122v7N/tMF4WN+WzMAjZ0SFr7fogl/dVNLHa3/AzuWGVb6bt+6GB09Yc1tkzbNjXlf3TtqNo5O34Cj0zcg6M+L3K5UApcdXMPPCqsOZCPMpdOJ2E4oTfdgwc8gaY4HKclHdFpcnz90Cth7Ivp2s3aBn9YDB07JgVEx/UviqeYSZPXRdizd3eqoo0Ys5m1rwspJxaZfJvO2NWHV5BIAwH1/acaz18nVv3FvfIGttQORn2N/v7xz5n3cWCGeI3nalnux7oanEAoEI499ZPdKy8vCVtW/e0fNxoJRc7Dq8Do8tf9F4WMqCyow+/JazBtxG79JMuWz7/F9Fu/+cro8q/fMSWBy4/13Y384aXNm9DqJOpYT3QbQizOmJKu9KL9rouVwFwgYDTOjVAP1AXD/SfHcwUkMf0t2tWDp7lbTxzz2UQsWXV0ofq7NAZqVD9GSXS3C++s+68CMy0KR8KdM+aZYPLbQUfgD5OqfkaPNJyLhT3ns9tqXLJd5dPoGzb/DvZ2Y9+ef492zHwAAlh9cCwBYMGoO5o24Lep+QK4IPrX/RdSUX4srS6/g54Yyjlk4chom7AQtLwWQbAgMXt9GN9fP5/MxBOr3CSuAJowqgXZUlwPjqhK6eq8eD2Ph+8041tITfed3B9v6QK061Ib5o/pD4ax3G/HbmgGGr1n1Sr1hhbF2cwM2fUXb3lFf/ds57SLb22e3mufUFa9NE/bsFYU8pRKo3P/I7pVY/9nmqGVWFlTgruEzMafqZn6rZMJn36P7LNkVQLPnxFJNimd73Dp+3Br7MBEVpkRWrbz4+Uv0JXd+5zAAusNs0GmF0uYvCZW/0+29qK47h44e7b6rHRJE3dRShAL22uVcsq6/44bSNk9doYtn+jal+qfMFKJUB+2atGmurWqe0/CnCPrzsG/aes39ViFQse/Cx7jlnQVRyxc9lvhlnEkBUHmem2Ey1qCQiAAY6zp5OQB6scqZ6EHI+b1jjZ1A7Bo9xPz+youSEv5WH21Hdd05XLKuPir8zaoKRcKf3Q+Eutdu3dRSbDnTqenxG8/cvU/sbYPftxuX1d0OAI7CX81b/8P18KcX7u3EsNdvwbDXb8GNm+/E+s/+iKA/D6smPoya8gmRxy0/uDaq48eVpVcIq32ixxKls0QOq8HpuigVxy/1ff6kFOwdJvLYGLXzq5s6ALdeGoz5V5b6PVBXBK0ea7ZspfpXUXA/Pp3xsqe+CK58YybCvZ1R96srgnYrgUaPXTPl55h08TU8aPmZd3W/paICaOe7JNGVPDf3g1vVsHSuAHrhs5fMDinsECLGCqDHbToVRnXdOfheOiMMf7VDgobhz8mBPvj3Z1Fdd85W+Ju3rQmNnebL/cGH76G84EHPhT8lyImEezvx6N7nUR9uMKwErjq8ThgcV018GJNVge+BncuEIZMo3hOZ1ypmHJ/NnfcmkVPLEQmPDVYAvSkypIugg4e6nV+yvwQkScKrx8OWl3OrX/02Prk1tvDX0RPGvD//HKuvX+rKOht1+lAYVQSVjh1vnd6uqe4ZLas+3ICvvj0fTV2tmsdOG3oD7h01WzOmYDYGF37m3Tt5p2JojES2w0tlBTAV09Il+n1O9HITHUxZAUwOVgA9aMmuFnnwZUH4s9vOL5EfqPnbm7SBdHOD5t+LdzZHhb+f7Pon28sPBYJYNfFh18IfYD6dm1FF8ETbGTy653msmviwcEBovUHBgfjJVX8ftYxVh9fhli0LsKPhIA9uStnnlvjekLsFkbTfD6wAesOmU2HM324wpAuAeSMLsHJSccoPfH3bvxeOtOGO4QWRf6882KoZViaVzAZ7FlXx3vp8G5YfWot9Fz6OemxJbmHU7fqxBBWP7n0eLxx9TXjfk+PuE1Yhs/ELlyfQ5Fcw3KwAJrMK58UKoJvLy6a2f3bORckaADvbv4NSEgB5Qoh2ybp6nG7XDjMTy6VeM5FOJAZjBEZed3MDtp3rQuOscssAqKYe9qX61Rn45NY6T4Y/hWgYGIX+srDyWPXtdoZ8Wf/ZH/Ho3n+JXBYeFByIbV/7NX9t88uXAZABkAEwid8LzBwMgJ5iVvWbVRXCC1NKXAt/qw61aef5tQiBZr/MjD646kGfR294DgduuSdy37MHXsZ9o7/tmfCnMGqjZzYFnP75SiVx/Wd/xPJDa3Gi7UxM623WTjHTAiDDHwNgqgKgfnkMgNnzw5BtARkAPUNU9QMAaW5F0l7LaRA0CoD6QZ9//2kT/u3ULzUdORIdAp898DJ+dXh9pEIX9OfZ7ok7rKgST4y/D+MHjtLcbtRBRC3oz8NdI2ZGpo+Lh1lVkgGQATAbA6DXjx8GwPT5XmAA9HAAzLY3xPdSdKUonrZ+op7DVUUBzKoK4fE94nmCq4oChlO7OQmA+infdny9LDI+XjJCoFL1cxL6RO4Y9g3cNXwmBgXlKe3sVgKt32wANg/tTKsEMgAyADIAMgB64T1l7vBIAMz2E4N+UOd4q35Gg0SbhjnVa8bTOUQ05ZsyNmEyQuDtWxdrhmkRqSmfYPkYRdCfhznVN0eC4LKDa2xX92rKJ0T1GlbvB2Ud9JU+ozaHDH/EAOjtqg0DYPqEMAZABsCU2XQqjIXvt+DAhW7N7bOqQlh7Q6mjZZmNE2iH/jWf3d+Ghe83x9w2UKFU/0Thx61x/fTHSri3E1e+MdP0sYOCA1EfbnDhRSFX86KPZlwzYDj+7189bjjen3491ZU+UaXRqJcxAyADYCoCoP75DIDpFwC9sA9TvU78TurHcQCTZOf5bszYciEq/NUOCeKFKSWOlmU2TmDtkCAG5Jm/rY+NL9KEvwMXuvHQjhb5H78+Lf/Fup3TLoqMiK/8hQJBV8PfFa9N03T0sDPAsj78xTIwc035BOy7ZT2eHHcfSnILo5LhR42HhTOFqNfTaDzBeSNuw9HpGzT3c05h8hpJkiJ/RG7/mM+012IAdPhGZOIAjR09Eu7Y2oSOHu32Oh3UubFTwsL3jS/1Kj2HGzt7DZcxriwXi67WhhfRusUbBBN1vKiDn1FFb9V1P8awokrTZS0/uFYQ4oyNLLk8cml35mVfxh/+eqVm6jf1ch/ZvcJw3fSDTod7OzWPnVN9s2ZZZoHS6zjYKvG4yJ5gxUCWhp9FKcV7P5PLsaJLvqGADzu+XobRpTmax756PIyF7zc7uqSrHydw1aE2PLSj1TQA1k0doJk7OGp4GDNxXh6Ohbpzx8/G3o1Hdq/Q9PK9d9RszBtxG4a9fkvkOZUFFfibSyYbDsasUNrZrf/sj1h2aA1Otp01fby+c4a+XZ9+2cq6iajb/Kkfa9VWMN1P9Pyyd2dfpuoScKLCHy8BJ3YbvbQPvbAu7A3MAJhQ2+q7cNNbDVGVtcfGF0VV4E6398qdKHrsb7c+/NnpBFI7JIiNXx4Q1+smOhCq33vRjBqTL74G2859FBXiRJ0olJBmZtLFV2P7uT2qAxKmvXX1gcyq/aHRYNH6bVMvV7/MdG0LyADIAMgAyADoxe8EBkCZJ9sApvslgsZOCbP/80JUsJo3siAq/AHAE3vbHIewb1yaFwl/D+2wDn8vTCnRhL9YX1ej7zLxvG1NMbULun3r4kh7PuXvxs13Ys2xjQCAn1x1Z9Tl3OLcQqy67seaAKYELbVwb6fmcqqR7ef2aDp1VOZX4HtVt+DK0iuEjw/3duLGzXfiqf0vItzbGdWOcNV1P9Y8d/nBtRj2+i0Y9votwm3Tb4c6vGZSYCEyCn78UZCdP2LIA++J5IFPX6ZVCmZsuYBXj3cAML7kq2Y4SLOyL+ZWIH/N2aiwdt+YQmw714lt9V3WX7S6YWbirv4h+nKyU2aXUNWVs30XPsYt7yyI3Hd0+gbDKdnUl4L3TVtvuHy9gkAI/zp5MSZedLXmdrOBoJXBo2/70w8162a2XWbrq670Gd2ezl/2PNGnvmLhtQqgndfPhgqgfjnJmgs3mfvVSz2SWQH0eABM1zdFfyn24qAf58K98ewd2B5B2KaqogCqigLYcloONuPKcrHzfJejZYwry40a7iXWEPjI7pVY/9lmR0FJfwn1R2O+F9UWUAljVkPEqCnLUYguQ5vJ8QXQ3dsjv20+H2B6DGvfW3Ubw3QOgBxriwEwWwJguvzQcVJ9S9ZcvNneDtELPHEJOFN2vj785Qd8MYY/9cHp/r451tITCX8AsHisuDfs4Hzjw8PoOU4F/Xl4ctwCHJ2+AfumrUdN+YTIfcsPro0MhaIfHuVHY74b9Vij5Tu5pPrU/hc1w6/oX8dKt9TT//ZZHtfa+9XDwmQahj/K9FCeKZ8//TBeoj9+B2TIMSx55F1J16rBplNhzN8e3Xt3WHEAR5t7TE78Tj9EsTzH2ujSHOyffpFwWjojblX/RESXT+8Y9g2Eezoj7efkYPY93DFselRnCX1v4HtHzsbmM9vxh8+3Ol6XyoIK3DV8JmZe9mVHVUSLA91GMNTKhAogv/y9U7FI5KXGRL1+qtc5k85ZbgbXdP1cswLosQCYricOUfu94cU5ONLcLXx87ZAgdjR04Ux7b9LXVek0om73Fwr48MmMi3HJunrby4m37V8sIbCyoAJNXS1o6pIrrKLev/eOmo1fHV4f1aGiOLcA58KNUaHKqq2e4t5RszUVxssLh+DT1lNxhTaztoXpHAB5+ZcnLL4n6b0d2RAE+XliAHRnnXWVs6sH5GBPozj8DQz60NQpoSeFm3TrpaFIBxXFfWMK8ex+bS/ieSMLsOpQW9TzE1n904fABR88hbc+32b4mMoCuWPLibb+96AktzASEu2EKqdtBFUHq6P2fTYXKi9T9zF4ctx9kbEH0/FkwrDCE1Y2vC/p/J64VRVMl33Az5PMny0HZiIs2dWi+ffisYXC8CfNrYA0twIXbIS/UMAHaW4F2ueUY3hxwPSxtUOCaJ9T7midt52Lrjrpg540twI3VuQKn+9W2z8rQX8eVl33Y4Np1xAJfifazmja6DV1tTqa5s1pG0HVN4bVA2L5+kQwkBdV8UuXdoEc5oGyTaZMjafeDrM/yrAfMJLH3tV0qSDoO3yYVf6M2+/F18O3PORHVy/QYDTzR4yLH5zvFw5Lox9IOlnqww2474OnNANAqw0KDoyaei3oz+u/xCrYD+qQterwOjy1/8UEHdDO34PKggqcaD2jOWTS4TIwq3/J27/cr+TFH3qsADIAJuTg8tJq6sOfUZs/oyCVjtbeUIpZVaGUvb6d9no15RNsjfkHRM/QEe7txM3/cY9l275YbLhxmeHA0upOK2a8HgDZ9o+I0jXE8hKwR3j9jVh1qE0T/q4qFYe/WVUhnG436gUcz6Wy5F9mmzeyIKXhD+i7JDzxYdMhWVZNfNh0GaNKqyL/v/zgWqw6vK7/uYfX4dPWUwnZu7e8swCjNtyKhR/8Iqu+XBj+iIjfTR4OwpIH94QXqwmi4V6S1aNXmluh6WxSVRTAFUUBvH06uj1ffsCHdpd6mdiZxSTZzCqBlQUVmg4h+vvuGj4Tb53eHnluji8Hg/Mv6rvc6uxa7bShNXhy/AJN+8H6cAO++vZ8y04oyrrMqbpZUwEUVzDljiGVhf2DRPPzSkREGRkAvXhSEQ330j6nHPlrzib+14pgKrgYhpNz7LHxRcK5i70QAq16CIsE/XnY8d/WYvy/z460D4ynFaYyFdz4gaMit63/7I94YOeztp6vH15m37T1uPLfZhqukDL0TTp8Vhn+iIgYANM+AG46FcbNf9SOIze6NAcdPVLUANBumzeyACsnFePxPa14aEdL3MsryfWhqcvePtTPH+w1b32+DcsPrcW+Cx+nfF2Uqt60oTWY+Z8/xMfNJ6wO8L6gJ3gvTMb89lpbQFb/iIgYADP25KKv/klzK4QVQSdhSt+RRG3x2EIs+S9F4n1iMWNH7ZAgNp0KC+/L9ftQFvTZvmTt9QAIwPZgzlYG5pXitsv+Gv9ypE4TtvTLD/rz8LOxd+PRvf8ivNQbDOQi3NOVsO31UgBk+CMiYgBM6kkm6ROYO5gmzWAJcHqh0ejyq++l0zAqD82qCuGFKSWYseWCYQi0a1ZVCGtvKE2Lg9iNELjoytvx+L7VkX9PG1qDZRN+FFm+erDoHF9AnvM3BRgAiYjIDf50XOlkDzirTKEWW/CDrfDXPqcctUP6p1cTVQfliqN4XWqHBPHClBKEAj7UTS3VLMspZVnpwk4PYXGQ6t+XT+1/KfL/NeUT8OT4BZrlqzt7pCr8pcNnkOGPiChNvselNPjGTvXJxrz9nZ3qnkmjLtvL0D9O+5z2OeWaoNrRI8XcQUW/rHQR7u3EI7tXYv1nm20FwOnv3oe9jUej7ts3bT22n/sIj+xeadirOFW8UgFkxw8iovTm5y6wtujqwsh0bsrf4Hy/KtzZCW5mt9k9caofp23Hpw9sj+9pjXG9kJbhD5ArdU+OW4Cj0zfYmt7tN1MexcC8UuFyHtixzHPhz+s/yIiIKH3kpMNKSpIkPOn4fL6kVR1ePR7GwvebTXr9WlX5zMKcrdOu7jna11LaKVYVBXD9xTlYeyy2NoCpHvDZLbcMrcG64+JK4OSLxwIASnKK8PZXVuHaf5/dv2d9wI2b74yaWk5w8CV+HJ6o9b7Gs+GP1T8iIgbAjAuBp9t7MftPFzTj8IkDmjis2elNqx/nD9D2BpZ7HVtv57GWHgdD02iXl25t/4zsu/AxNpx81/D+HecPRP6/JKdIuxckRFX+9OP19R2QCPpzMaSgHJ+0nHS8jkF/Hu4aMTN6uSbNAXacP+jJ8EdEROmHl4ANNHZKWLKrBdV153DJunqT8CeZ3jZvZIGt11s8NrrH79LdrZFLucmYU7huamnaXv5VhHs78eDOZZGBno0eI0g3wseW5BYKQhowfuAobJi6HP82dTn+qnx8TOspWm6kkizZXG+P/DgjIqL0khadQOxUIdzYjNVH27F0d6vtCpp+ijb17SKbToXxxN42fO+KEG4fli/ePsHyqooCCR9w2my908myg2siwSroz8OGqcvw1bfnRz2usqAC04begDdO/inlbf1qyidg+7mPbAW8yoLUTAnHS79ERJkl7SqARieceC9PLdnVgjveazINWvaGVjFejzvea8KW052Yv73Z0bolI/xlirXHNkX+/95RszGsqFLYIeRE2xmsOrwuOvwluQBaUz4BqyY+jHtHzbb1+BNtZ/DI7hUMf0REFJecTNqYWNsDGs3KUVUUwOKxhZpqXX+FTkJ13TnRadHwdZTLuB098nMfvKrA9iViskfdeWPeiNsAAHOqb8YLR1+z+QsDuDhvAM51NiZ0PUOBPPzDNXcDAL769t2OqpDJvBTMdn9ERAyAnmHUISQW+vBXOyRosy2cz7Ayt2RXi3Aqt1DAF2lLeKylB/O3N+N0e6/htG/S3Aph5xD1uh5p7sGR5m4eySZ+NOa7ONp8IjJTiA8+SCZBXR/+jMbeG/b6LcLH2JmZRJKAE+1nDNoBRjs6fYPm9bzwGSQiovSVtp1A4r0UvOlUGNV152IMf+JAqFB33lAz6ujhe+kMquvOCZ9TOyTXdBvcCn+ZXIlUZgqJHDsmHS305lTdLLzdLNy9ceJPONpy3LBjiRIS7Ya/VOGlXyIiBsCMC4H69n52w5/x/VJUsNNTBpTWT/sGyBVB0Wwj287FFvCcdOiQ5lZg5aTijD7Qg/48+PSDb/uAJ8fdh5LcQuFzjk7fgJ+NvVt43wM7lglvX3ZwDR7Y+SxOtp1N+jiBDH9ERGRXTqZumFV7QPWwKrOqQpF5dK1MGZSLt09bt8Hq6JHge+mMsB2hMl/v/O3NWH203XgbXrJqF2Y8+LT1c7PHu2c/wCO7Vwov+y4/tBYLRs3Bz/b8S9R9Ti65eunybKLCHxERMQB6hll7QLudQtbeUGr79fZeUFfktIP21g4JYtMp7QwcSls//bAvoYAPL0wpwQtTSrDzfDfG/9sXTk7RCAWAAXn+pIwPmO4e2LHMcGaPE21n8OS+FxH05yW0c0XQn4d7R82OdEwxMvnN7xquq3odlx1cgwWj5iQ1/LH6R0SUOTJiIGizE5Pb1YwzmsClfd26qaXCoWKUXr9G1b5xZTkO2uDJobOjR4oKf5MH5fKIFtAHqnEDR2n+He7ttD0MSyzkQaOXWYY/0bqqzanub4+4/OBarDq8juGPiIiyNwAmMwSaXSZ+fE+rYQhUKoFGVk4qtmy3VzskiFCg/985/v51keZWYGttGaS5FRic77dcTqZSj/m37OCaqPuPTt+A9Tf8Iqpn77wRt2HDjdp2ffeOmo2j0zc4Coc15ROwb9p6HJ2+IfK37oZfYFhRZdzb9qMx30VN+QRNCGT4IyKimL73pQz7drd7IlO3kXP62rLoAAAItElEQVTSYeLxPa14aEcLJl2ch+3nkjs1V/uccjy0oxXP7td2MJk3sgArJxXbmMnEh3FlOdhaOxD5a87GtP1e9+je5+2P+eeyaUNr8OT4BcKBp60o7RRF4wHOqbo50hkl3NuJK9+YqQm0ifzcMPwREWWmjOsE4uYYgSKLri7EoqsLhUEy0UIBHx4bX4gDF7ojbQ1DAR9WTio2HMxat3eweGxh2s/3a0Y/5p+basonaJbrVvgCxO0UlVlC1IEylnDJ8EdERHr+TNyoRE0X5wVKD2JFR4+Ehe/bCX/ypd9bLw1m9AGtjPk387Kv2Eg+9pc7bWiNZixBt7x79gPcuPlOYds/ffhLBIY/IqLslLHDwBhVApWewepZOYxm7nD8mrpLqafbezHm9S/Q2OlOT12jaqP+krCddcv0EPjkuAV4ctwCAMCVb8zU9PAN+vOwb9r6qBk7gv48/Gzs3Zh52Zc1w7oE/XkYU1Ltahgzu+Srft1UhD8iIsp8/kzeOLNKYMe3+wPR0t2tWLKrxfXXH5zvxzNfKuJRlmL6ThxKGFSqhUrQCvd24pHdK6LCVyJm7XhgxzJH8/+6HfzY6YOIKLtlXCcQ0cnO1K9Pa/4pGrjZdPk2OpOoH1NVFMCAPD92nu9K2DYr23C6vRcP7WjBvJEF+MaleZi/vVnYQSRbqoOiwZorCypwTekIbPz8PyO3HZ2+AasOr8NT+180XZ5VG0A7VT4j04bWYNmEH1luh9N2iFafB4Y/IqLskJPpG2jZKeS7gzUh0Gjg5nioLzcfa+lBKNCrCV3WA0FrB5w2o5/STumwcsm6+qwfNFo02POJtjOo74hufzdvxG2YN+K2qMvHalaDMZsNQG2mpnwCnhy/IOk/hhj+iIiyR8ZXAO2e/PSVQBFRddBOBVAZOkZzstU9dv72Zqw61Jb0/aIMIZMN7FT1Us3OUDKxVAAZ/oiIKCsDoJ0TYejlM5FKneFjAj60zyl3FAAV+WvORpa/eGyho44n6ufGK5s6hJgxq+4l0tHpGzRTvomGezHjNACyvR8REen5s22DzU546o4hho/pkeB76Uzkzwn1dG9OO54sHlvoyvbbn3Iu8yVy+jcjc6rk6dzUl4YTOdwLwx8REQnPD1KWngWcnhjtVOCsqnodPRJmbLkQGcTZznOM6Nv0saqXHOrq272jZpu2ATSy7OAaTa9iJx05nDyX4Y+IiBgAHZ4g9SdJUTs+I1VFATx4VYGw2iYKgcpz7hqRr5llRG3TqbBhL95xZbnY8fUyHs1JDoCA3Iv4R2O+i2lDa0yfZ9Qj2Ky3r96aYxsjw9SYPZft/YiIiAHQxRAoYlYZNKruGYVAANjx9Yswriy6c7ZZL966qQMyfoYPrwbASDib8nNMuvgaw+ep2/wpnLb9s9NukOGPiIjs8Gf7DrA6IVqdUJW2eZMH5Ubdt3R3K6rrzmHh+y2a8KZM5yYaambhB82ax246FUZ13TnD8JcN07ulgwd2LjPtUKIPf8rUcnbDX1NXq2W7QYY/IiKyK+srgHZPnnZPoGbVPUC+1HvrpSE8eFUBBufL+dt6HEAYhj/1mH+UeOoK4Lav/RpffXs+mrr6p+KrLKjAXcNnYk7VzYaXfZ0O3rzm2EY8tf9Fzeuol+HWsUtERNnDz13Qf4KMtxoImFf3AHkg6Gf3ayuD48pyYuqdy/CXWoOCA/GTq/5ec9uJtjN4ZPcKLDu4xpXp3pYdXINHdq/QhL+a8gm2j0k7xzUREWUfVgBjDHp2d9urx8NYurvV9anfsmkAZy8RjcH36N7n8cLR12w9f07VzfjZ2LsN7zebPq6yoAL3jpyNmZd9mVU/IiJiAPR6CHQ7EHLIF28FQEW4txO3bFmAoy0nhM8N+vOwb9p60+WLOosA2k4fDH9ERBSvHO4C8xOo2clWuc/JyfbWS+VOG7EGQQ7k7F3rP/ujYfhTAqJRL2IzyvRwoUDQ9nFLRETEABhnELSquPh8PscnXiUIUuZQD9CsrtjFOuUcO3oQEVGisBOIzRBop4OInZM0ZZ71n/0RN26+03CYllimnFOmjLN7XDH8ERGRE2wD6HSH2Qx53K2ZSd8GUD81m/o+HmdERORVvATskJ22gcr9PDlnths33ynsratU7xIZ/nhsERERA2CKgqCdEMiTdeZShz+n07rFGvx4PBEREQOgB0KgnRM3g2BmU3rpxhr+GPyIiIgBkEGQPCzoz4vq0btswo8Y/IiIKK2wF3ACgqCdEz97DacnfY/eWNr7OXnvGf6IiCgR2As4UTvWYbjj28BjgscDERExADIIEt9/IiIiBsBsCgIMAwx+REREDIAMgsT3loiIiAEw28ICAwPfRyIiIgZAhgjuPL5fREREDIDZGCwYLvjeEBERMQAycHAH8j0gIiJiAMzGEMIwwn1NRETEAMiAwqDCfUlERMQAyACTHSGG+4uIiIgBkOEmA8MO9wMREREDIANhkiTrEEr2djH0ERERAyAxDGYBfhyIiCgb5HAXZF+gYShk2CMiIgZAYvDJ+FDIsEdERMQASA4CUrqEQ4Y8IiIiBkBKYrBiD1wiIiIGQGJIJCIiIo/ycxcQERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgEREREQMgERERETEAEhEREREDIBERERExABIRERFRuvn/XcNQ24xp80UAAAAASUVORK5CYII=" -} diff --git a/web/src/lib/assets/immich-logo.svg b/web/src/lib/assets/immich-logo.svg deleted file mode 100644 index 376fa6f3e8..0000000000 --- a/web/src/lib/assets/immich-logo.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - diff --git a/web/src/lib/assets/svg-paths.ts b/web/src/lib/assets/svg-paths.ts index 9c37849fcc..ded3db0fc8 100644 --- a/web/src/lib/assets/svg-paths.ts +++ b/web/src/lib/assets/svg-paths.ts @@ -6,4 +6,5 @@ export const moonViewBox = '0 0 20 20'; export const sunViewBox = '0 0 20 20'; export const discordPath = - 'M 9.1367188 3.8691406 C 9.1217187 3.8691406 9.1067969 3.8700938 9.0917969 3.8710938 C 8.9647969 3.8810937 5.9534375 4.1403594 4.0234375 5.6933594 C 3.0154375 6.6253594 1 12.073203 1 16.783203 C 1 16.866203 1.0215 16.946531 1.0625 17.019531 C 2.4535 19.462531 6.2473281 20.102859 7.1113281 20.130859 L 7.1269531 20.130859 C 7.2799531 20.130859 7.4236719 20.057594 7.5136719 19.933594 L 8.3886719 18.732422 C 6.0296719 18.122422 4.8248594 17.086391 4.7558594 17.025391 C 4.5578594 16.850391 4.5378906 16.549563 4.7128906 16.351562 C 4.8068906 16.244563 4.9383125 16.189453 5.0703125 16.189453 C 5.1823125 16.189453 5.2957188 16.228594 5.3867188 16.308594 C 5.4157187 16.334594 7.6340469 18.216797 11.998047 18.216797 C 16.370047 18.216797 18.589328 16.325641 18.611328 16.306641 C 18.702328 16.227641 18.815734 16.189453 18.927734 16.189453 C 19.059734 16.189453 19.190156 16.243562 19.285156 16.351562 C 19.459156 16.549563 19.441141 16.851391 19.244141 17.025391 C 19.174141 17.087391 17.968375 18.120469 15.609375 18.730469 L 16.484375 19.933594 C 16.574375 20.057594 16.718094 20.130859 16.871094 20.130859 L 16.886719 20.130859 C 17.751719 20.103859 21.5465 19.463531 22.9375 17.019531 C 22.9785 16.947531 23 16.866203 23 16.783203 C 23 12.073203 20.984172 6.624875 19.951172 5.671875 C 18.047172 4.140875 15.036203 3.8820937 14.908203 3.8710938 C 14.895203 3.8700938 14.880188 3.8691406 14.867188 3.8691406 C 14.681188 3.8691406 14.510594 3.9793906 14.433594 4.1503906 C 14.427594 4.1623906 14.362062 4.3138281 14.289062 4.5488281 C 15.548063 4.7608281 17.094141 5.1895937 18.494141 6.0585938 C 18.718141 6.1975938 18.787437 6.4917969 18.648438 6.7167969 C 18.558438 6.8627969 18.402188 6.9433594 18.242188 6.9433594 C 18.156188 6.9433594 18.069234 6.9200937 17.990234 6.8710938 C 15.584234 5.3800938 12.578 5.3046875 12 5.3046875 C 11.422 5.3046875 8.4157187 5.3810469 6.0117188 6.8730469 C 5.9327188 6.9210469 5.8457656 6.9433594 5.7597656 6.9433594 C 5.5997656 6.9433594 5.4425625 6.86475 5.3515625 6.71875 C 5.2115625 6.49375 5.2818594 6.1985938 5.5058594 6.0585938 C 6.9058594 5.1905937 8.4528906 4.7627812 9.7128906 4.5507812 C 9.6388906 4.3147813 9.5714062 4.1643437 9.5664062 4.1523438 C 9.4894063 3.9813438 9.3217188 3.8691406 9.1367188 3.8691406 z M 12 7.3046875 C 12.296 7.3046875 14.950594 7.3403125 16.933594 8.5703125 C 17.326594 8.8143125 17.777234 8.9453125 18.240234 8.9453125 C 18.633234 8.9453125 19.010656 8.8555 19.347656 8.6875 C 19.964656 10.2405 20.690828 12.686219 20.923828 15.199219 C 20.883828 15.143219 20.840922 15.089109 20.794922 15.037109 C 20.324922 14.498109 19.644687 14.191406 18.929688 14.191406 C 18.332687 14.191406 17.754078 14.405437 17.330078 14.773438 C 17.257078 14.832437 15.505 16.21875 12 16.21875 C 8.496 16.21875 6.7450313 14.834687 6.7070312 14.804688 C 6.2540312 14.407687 5.6742656 14.189453 5.0722656 14.189453 C 4.3612656 14.189453 3.6838438 14.494391 3.2148438 15.025391 C 3.1658438 15.080391 3.1201719 15.138266 3.0761719 15.197266 C 3.3091719 12.686266 4.0344375 10.235594 4.6484375 8.6835938 C 4.9864375 8.8525938 5.3657656 8.9433594 5.7597656 8.9433594 C 6.2217656 8.9433594 6.6724531 8.8143125 7.0644531 8.5703125 C 9.0494531 7.3393125 11.704 7.3046875 12 7.3046875 z M 8.890625 10.044922 C 7.966625 10.044922 7.2167969 10.901031 7.2167969 11.957031 C 7.2167969 13.013031 7.965625 13.869141 8.890625 13.869141 C 9.815625 13.869141 10.564453 13.013031 10.564453 11.957031 C 10.564453 10.900031 9.815625 10.044922 8.890625 10.044922 z M 15.109375 10.044922 C 14.185375 10.044922 13.435547 10.901031 13.435547 11.957031 C 13.435547 13.013031 14.184375 13.869141 15.109375 13.869141 C 16.034375 13.869141 16.783203 13.013031 16.783203 11.957031 C 16.783203 10.900031 16.033375 10.044922 15.109375 10.044922 z'; + 'M81.15,0c-1.2376,2.1973-2.3489,4.4704-3.3591,6.794-9.5975-1.4396-19.3718-1.4396-28.9945,0-.985-2.3236-2.1216-4.5967-3.3591-6.794-9.0166,1.5407-17.8059,4.2431-26.1405,8.0568C2.779,32.5304-1.6914,56.3725.5312,79.8863c9.6732,7.1476,20.5083,12.603,32.0505,16.0884,2.6014-3.4854,4.8998-7.1981,6.8698-11.0623-3.738-1.3891-7.3497-3.1318-10.8098-5.1523.9092-.6567,1.7932-1.3386,2.6519-1.9953,20.281,9.547,43.7696,9.547,64.0758,0,.8587.7072,1.7427,1.3891,2.6519,1.9953-3.4601,2.0457-7.0718,3.7632-10.835,5.1776,1.97,3.8642,4.2683,7.5769,6.8698,11.0623,11.5419-3.4854,22.3769-8.9156,32.0509-16.0631,2.626-27.2771-4.496-50.9172-18.817-71.8548C98.9811,4.2684,90.1918,1.5659,81.1752.0505l-.0252-.0505ZM42.2802,65.4144c-6.2383,0-11.4159-5.6575-11.4159-12.6535s4.9755-12.6788,11.3907-12.6788,11.5169,5.708,11.4159,12.6788c-.101,6.9708-5.026,12.6535-11.3907,12.6535ZM84.3576,65.4144c-6.2637,0-11.3907-5.6575-11.3907-12.6535s4.9755-12.6788,11.3907-12.6788,11.4917,5.708,11.3906,12.6788c-.101,6.9708-5.026,12.6535-11.3906,12.6535Z'; +export const discordViewBox = '0 0 126.644 96'; diff --git a/web/src/lib/components/admin-page/restore-dialogue.svelte b/web/src/lib/components/admin-page/restore-dialogue.svelte index a72ada2ca5..1386ae9fc4 100644 --- a/web/src/lib/components/admin-page/restore-dialogue.svelte +++ b/web/src/lib/components/admin-page/restore-dialogue.svelte @@ -32,7 +32,7 @@ diff --git a/web/src/lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte b/web/src/lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte index 2a95e7a9f5..59bb798a0b 100644 --- a/web/src/lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte +++ b/web/src/lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte @@ -1,20 +1,20 @@ void; onNext: () => Promise; onPrevious: () => Promise; - onRandom: () => Promise; + onRandom: () => Promise; copyImage?: () => Promise; } diff --git a/web/src/lib/components/asset-viewer/editor/crop-tool/crop-preset.svelte b/web/src/lib/components/asset-viewer/editor/crop-tool/crop-preset.svelte index eb788b2d16..a27dc8336f 100644 --- a/web/src/lib/components/asset-viewer/editor/crop-tool/crop-preset.svelte +++ b/web/src/lib/components/asset-viewer/editor/crop-tool/crop-preset.svelte @@ -1,7 +1,7 @@

  • - diff --git a/web/src/lib/components/asset-viewer/editor/editor-panel.svelte b/web/src/lib/components/asset-viewer/editor/editor-panel.svelte index 133d9c9021..39ba7ac200 100644 --- a/web/src/lib/components/asset-viewer/editor/editor-panel.svelte +++ b/web/src/lib/components/asset-viewer/editor/editor-panel.svelte @@ -69,7 +69,7 @@ prompt={$t('editor_close_without_save_prompt')} cancelText={$t('no')} cancelColor="secondary" - confirmColor="red" + confirmColor="danger" confirmText={$t('close')} onCancel={() => { $showCancelConfirmDialog = false; diff --git a/web/src/lib/components/asset-viewer/photo-viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte index 24701685ef..70467ccb82 100644 --- a/web/src/lib/components/asset-viewer/photo-viewer.svelte +++ b/web/src/lib/components/asset-viewer/photo-viewer.svelte @@ -19,7 +19,7 @@ import { NotificationType, notificationController } from '../shared-components/notification/notification'; import { handleError } from '$lib/utils/handle-error'; import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte'; - import { photoViewerImgElement } from '$lib/stores/assets.store'; + import { photoViewerImgElement } from '$lib/stores/assets-store.svelte'; import { isFaceEditMode } from '$lib/stores/face-edit.svelte'; interface Props { diff --git a/web/src/lib/components/assets/thumbnail/__test__/thumbnail.spec.ts b/web/src/lib/components/assets/thumbnail/__test__/thumbnail.spec.ts new file mode 100644 index 0000000000..62f0802a22 --- /dev/null +++ b/web/src/lib/components/assets/thumbnail/__test__/thumbnail.spec.ts @@ -0,0 +1,63 @@ +import { getIntersectionObserverMock } from '$lib/__mocks__/intersection-observer.mock'; +import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte'; +import { assetFactory } from '@test-data/factories/asset-factory'; +import { fireEvent, render, screen } from '@testing-library/svelte'; + +describe('Thumbnail component', () => { + beforeAll(() => { + vi.stubGlobal('IntersectionObserver', getIntersectionObserverMock()); + }); + + it('should only contain a single tabbable element (the container)', () => { + const asset = assetFactory.build({ originalPath: 'image.jpg', originalMimeType: 'image/jpeg' }); + render(Thumbnail, { + asset, + focussed: false, + overrideDisplayForTest: true, + selected: true, + }); + + const container = screen.getByTestId('container-with-tabindex'); + expect(container.getAttribute('tabindex')).toBe('0'); + + // This isn't capturing all tabbable elements, but should be the most likely ones. Mainly guarding against + // inserting extra tabbable elments in future in + let allTabbableElements = screen.queryAllByRole('link'); + allTabbableElements = allTabbableElements.concat(screen.queryAllByRole('checkbox')); + expect(allTabbableElements.length).toBeGreaterThan(0); + for (const tabbableElement of allTabbableElements) { + const testIdValue = tabbableElement.dataset.testid; + if (testIdValue === null || testIdValue !== 'container-with-tabindex') { + expect(tabbableElement.getAttribute('tabindex')).toBe('-1'); + } + } + }); + + it('handleFocus should be called on focus of container', async () => { + const asset = assetFactory.build({ originalPath: 'image.jpg', originalMimeType: 'image/jpeg' }); + const handleFocusSpy = vi.fn(); + render(Thumbnail, { + asset, + overrideDisplayForTest: true, + handleFocus: handleFocusSpy, + }); + + const container = screen.getByTestId('container-with-tabindex'); + await fireEvent(container, new FocusEvent('focus')); + + expect(handleFocusSpy).toBeCalled(); + }); + + it('element will be focussed if not already', () => { + const asset = assetFactory.build({ originalPath: 'image.jpg', originalMimeType: 'image/jpeg' }); + const handleFocusSpy = vi.fn(); + render(Thumbnail, { + asset, + overrideDisplayForTest: true, + focussed: true, + handleFocus: handleFocusSpy, + }); + + expect(handleFocusSpy).toBeCalled(); + }); +}); diff --git a/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte b/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte index 9d69bdeeb2..119efe71b5 100644 --- a/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte @@ -4,7 +4,6 @@ import Icon from '$lib/components/elements/icon.svelte'; import { TUNABLES } from '$lib/utils/tunables'; import { mdiEyeOffOutline } from '@mdi/js'; - import { onMount } from 'svelte'; import { fade } from 'svelte/transition'; interface Props { @@ -37,7 +36,6 @@ circle = false, hidden = false, border = false, - preload = true, hiddenIconClass = 'text-white', onComplete = undefined, }: Props = $props(); @@ -49,8 +47,6 @@ let loaded = $state(false); let errored = $state(false); - let img = $state(); - const setLoaded = () => { loaded = true; onComplete?.(); @@ -59,11 +55,13 @@ errored = true; onComplete?.(); }; - onMount(() => { - if (img?.complete) { - setLoaded(); + + function mount(elem: HTMLImageElement) { + if (elem.complete) { + loaded = true; + onComplete?.(); } - }); + } let optionalClasses = $derived( [ @@ -82,10 +80,9 @@ {:else} - import { intersectionObserver } from '$lib/actions/intersection-observer'; import Icon from '$lib/components/elements/icon.svelte'; import { ProjectionType } from '$lib/constants'; import { getAssetThumbnailUrl, isSharedLink } from '$lib/utils'; @@ -22,69 +21,49 @@ import ImageThumbnail from './image-thumbnail.svelte'; import VideoThumbnail from './video-thumbnail.svelte'; import { currentUrlReplaceAssetId } from '$lib/utils/navigation'; - import { AssetStore } from '$lib/stores/assets.store'; - - import type { DateGroup } from '$lib/utils/timeline-util'; - - import { generateId } from '$lib/utils/generate-id'; - import { onDestroy } from 'svelte'; import { TUNABLES } from '$lib/utils/tunables'; import { thumbhash } from '$lib/actions/thumbhash'; interface Props { asset: AssetResponseDto; - dateGroup?: DateGroup | undefined; - assetStore?: AssetStore | undefined; groupIndex?: number; thumbnailSize?: number | undefined; thumbnailWidth?: number | undefined; thumbnailHeight?: number | undefined; selected?: boolean; + focussed?: boolean; selectionCandidate?: boolean; disabled?: boolean; readonly?: boolean; showArchiveIcon?: boolean; showStackedIcon?: boolean; disableMouseOver?: boolean; - intersectionConfig?: { - root?: HTMLElement; - bottom?: string; - top?: string; - left?: string; - priority?: number; - disabled?: boolean; - }; - retrieveElement?: boolean; - onIntersected?: (() => void) | undefined; + onClick?: ((asset: AssetResponseDto) => void) | undefined; - onRetrieveElement?: ((elment: HTMLElement) => void) | undefined; onSelect?: ((asset: AssetResponseDto) => void) | undefined; onMouseEvent?: ((event: { isMouseOver: boolean; selectedGroupIndex: number }) => void) | undefined; + handleFocus?: (() => void) | undefined; class?: string; } let { - asset, - dateGroup = undefined, - assetStore = undefined, + asset = $bindable(), groupIndex = 0, thumbnailSize = undefined, thumbnailWidth = undefined, thumbnailHeight = undefined, selected = false, + focussed = false, selectionCandidate = false, disabled = false, readonly = false, showArchiveIcon = false, showStackedIcon = true, disableMouseOver = false, - intersectionConfig = {}, - retrieveElement = false, - onIntersected = undefined, onClick = undefined, - onRetrieveElement = undefined, onSelect = undefined, onMouseEvent = undefined, + handleFocus = undefined, class: className = '', }: Props = $props(); @@ -92,34 +71,24 @@ IMAGE_THUMBNAIL: { THUMBHASH_FADE_DURATION }, } = TUNABLES; - const componentId = generateId(); - let element: HTMLElement | undefined = $state(); + let focussableElement: HTMLElement | undefined = $state(); let mouseOver = $state(false); - let intersecting = $state(false); - let lastRetrievedElement: HTMLElement | undefined = $state(); let loaded = $state(false); $effect(() => { - if (!retrieveElement) { - lastRetrievedElement = undefined; - } - }); - $effect(() => { - if (retrieveElement && element && lastRetrievedElement !== element) { - lastRetrievedElement = element; - onRetrieveElement?.(element); + if (focussed && document.activeElement !== focussableElement) { + focussableElement?.focus(); } }); let width = $derived(thumbnailSize || thumbnailWidth || 235); let height = $derived(thumbnailSize || thumbnailHeight || 235); - let display = $derived(intersecting); const onIconClickedHandler = (e?: MouseEvent) => { e?.stopPropagation(); e?.preventDefault(); if (!disabled) { - onSelect?.(asset); + onSelect?.($state.snapshot(asset)); } }; @@ -128,7 +97,7 @@ onIconClickedHandler(); return; } - onClick?.(asset); + onClick?.($state.snapshot(asset)); }; const handleClick = (e: MouseEvent) => { if (e.ctrlKey || e.metaKey) { @@ -139,68 +108,18 @@ callClickHandlers(); }; - const _onMouseEnter = () => { + const onMouseEnter = () => { mouseOver = true; onMouseEvent?.({ isMouseOver: true, selectedGroupIndex: groupIndex }); }; - const onMouseEnter = () => { - if (dateGroup && assetStore) { - assetStore.taskManager.queueScrollSensitiveTask({ componentId, task: () => _onMouseEnter() }); - } else { - _onMouseEnter(); - } - }; - const onMouseLeave = () => { - if (dateGroup && assetStore) { - assetStore.taskManager.queueScrollSensitiveTask({ componentId, task: () => (mouseOver = false) }); - } else { - mouseOver = false; - } + mouseOver = false; }; - - const _onIntersect = () => { - intersecting = true; - onIntersected?.(); - }; - - const onIntersect = () => { - if (intersecting === true) { - return; - } - if (dateGroup && assetStore) { - assetStore.taskManager.intersectedThumbnail(componentId, dateGroup, asset, () => void _onIntersect()); - } else { - void _onIntersect(); - } - }; - - const onSeparate = () => { - if (intersecting === false) { - return; - } - if (dateGroup && assetStore) { - assetStore.taskManager.separatedThumbnail(componentId, dateGroup, asset, () => (intersecting = false)); - } else { - intersecting = false; - } - }; - - onDestroy(() => { - assetStore?.taskManager.removeAllTasksForComponent(componentId); - }); diff --git a/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte b/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte index 9188ab9a4f..fc3cb2e951 100644 --- a/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte @@ -3,12 +3,8 @@ import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; import { mdiAlertCircleOutline, mdiPauseCircleOutline, mdiPlayCircleOutline } from '@mdi/js'; import Icon from '$lib/components/elements/icon.svelte'; - import { AssetStore } from '$lib/stores/assets.store'; - import { generateId } from '$lib/utils/generate-id'; - import { onDestroy } from 'svelte'; interface Props { - assetStore?: AssetStore | undefined; url: string; durationInSeconds?: number; enablePlayback?: boolean; @@ -20,7 +16,6 @@ } let { - assetStore = undefined, url, durationInSeconds = 0, enablePlayback = $bindable(false), @@ -31,7 +26,6 @@ pauseIcon = mdiPauseCircleOutline, }: Props = $props(); - const componentId = generateId(); let remainingSeconds = $state(durationInSeconds); let loading = $state(true); let error = $state(false); @@ -49,48 +43,28 @@ } }); const onMouseEnter = () => { - if (assetStore) { - assetStore.taskManager.queueScrollSensitiveTask({ - componentId, - task: () => { - if (playbackOnIconHover) { - enablePlayback = true; - } - }, - }); - } else { - if (playbackOnIconHover) { - enablePlayback = true; - } + if (playbackOnIconHover) { + enablePlayback = true; } }; const onMouseLeave = () => { - if (assetStore) { - assetStore.taskManager.queueScrollSensitiveTask({ - componentId, - task: () => { - if (playbackOnIconHover) { - enablePlayback = false; - } - }, - }); - } else { - if (playbackOnIconHover) { - enablePlayback = false; - } + if (playbackOnIconHover) { + enablePlayback = false; } }; - - onDestroy(() => { - assetStore?.taskManager.removeAllTasksForComponent(componentId); - });
    {#if showTime} - {Duration.fromObject({ seconds: remainingSeconds }).toFormat('m:ss')} + {#if remainingSeconds < 60} + {Duration.fromObject({ seconds: remainingSeconds }).toFormat('m:ss')} + {:else if remainingSeconds < 3600} + {Duration.fromObject({ seconds: remainingSeconds }).toFormat('mm:ss')} + {:else} + {Duration.fromObject({ seconds: remainingSeconds }).toFormat('h:mm:ss')} + {/if} {/if} diff --git a/web/src/lib/components/faces-page/assign-face-side-panel.svelte b/web/src/lib/components/faces-page/assign-face-side-panel.svelte index 59bcf6a84c..19a99fdb94 100644 --- a/web/src/lib/components/faces-page/assign-face-side-panel.svelte +++ b/web/src/lib/components/faces-page/assign-face-side-panel.svelte @@ -6,7 +6,7 @@ import { mdiArrowLeftThin, mdiClose, mdiMagnify, mdiPlus } from '@mdi/js'; import { linear } from 'svelte/easing'; import { fly } from 'svelte/transition'; - import { photoViewerImgElement } from '$lib/stores/assets.store'; + import { photoViewerImgElement } from '$lib/stores/assets-store.svelte'; import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte'; import LoadingSpinner from '../shared-components/loading-spinner.svelte'; import SearchPeople from '$lib/components/faces-page/people-search.svelte'; diff --git a/web/src/lib/components/faces-page/people-card.svelte b/web/src/lib/components/faces-page/people-card.svelte index 4e341c5743..d12855c54f 100644 --- a/web/src/lib/components/faces-page/people-card.svelte +++ b/web/src/lib/components/faces-page/people-card.svelte @@ -6,7 +6,6 @@ import { getPeopleThumbnailUrl } from '$lib/utils'; import { type PersonResponseDto } from '@immich/sdk'; import { - mdiAccountEditOutline, mdiAccountMultipleCheckOutline, mdiCalendarEditOutline, mdiDotsVertical, @@ -22,22 +21,13 @@ interface Props { person: PersonResponseDto; preload?: boolean; - onChangeName: () => void; onSetBirthDate: () => void; onMergePeople: () => void; onHidePerson: () => void; onToggleFavorite: () => void; } - let { - person, - preload = false, - onChangeName, - onSetBirthDate, - onMergePeople, - onHidePerson, - onToggleFavorite, - }: Props = $props(); + let { person, preload = false, onSetBirthDate, onMergePeople, onHidePerson, onToggleFavorite }: Props = $props(); let showVerticalDots = $state(false); @@ -63,21 +53,14 @@ altText={person.name} title={person.name} widthStyle="100%" + circle /> {#if person.isFavorite} -
    +
    {/if}
    - {#if person.name} - - {person.name} - - {/if} {#if showVerticalDots} @@ -91,7 +74,6 @@ title={$t('show_person_options')} > - -
    +
    {#each people as person, index (person.id)} {#if hasNextPage && index === people.length - 1}
    diff --git a/web/src/lib/components/faces-page/person-side-panel.svelte b/web/src/lib/components/faces-page/person-side-panel.svelte index b0e0f8fcc4..73c3ea7ae5 100644 --- a/web/src/lib/components/faces-page/person-side-panel.svelte +++ b/web/src/lib/components/faces-page/person-side-panel.svelte @@ -25,7 +25,7 @@ import AssignFaceSidePanel from './assign-face-side-panel.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import { zoomImageToBase64 } from '$lib/utils/people-utils'; - import { photoViewerImgElement } from '$lib/stores/assets.store'; + import { photoViewerImgElement } from '$lib/stores/assets-store.svelte'; import { t } from 'svelte-i18n'; import { dialogController } from '$lib/components/shared-components/dialog/dialog'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; diff --git a/web/src/lib/components/forms/api-key-form.svelte b/web/src/lib/components/forms/api-key-form.svelte index 086d7708c3..bba7398655 100644 --- a/web/src/lib/components/forms/api-key-form.svelte +++ b/web/src/lib/components/forms/api-key-form.svelte @@ -1,9 +1,9 @@ - +
  • @@ -948,7 +801,9 @@ } .bucket { - contain: layout size; - transition: height 0.2s ease-out; + contain: layout size paint; + transform-style: flat; + backface-visibility: hidden; + transform-origin: center center; } diff --git a/web/src/lib/components/photos-page/measure-date-group.svelte b/web/src/lib/components/photos-page/measure-date-group.svelte deleted file mode 100644 index a435f89fb5..0000000000 --- a/web/src/lib/components/photos-page/measure-date-group.svelte +++ /dev/null @@ -1,91 +0,0 @@ - - - - -
    - {#each bucket.dateGroups as dateGroup (dateGroup.date)} -
    -
    $assetStore.updateBucketDateGroup(bucket, dateGroup, { height })}> -
    - - {dateGroup.groupTitle} - -
    - -
    -
    -
    - {/each} -
    diff --git a/web/src/lib/components/photos-page/memory-lane.svelte b/web/src/lib/components/photos-page/memory-lane.svelte index e2be2818b6..77c556834e 100644 --- a/web/src/lib/components/photos-page/memory-lane.svelte +++ b/web/src/lib/components/photos-page/memory-lane.svelte @@ -27,7 +27,7 @@ }; let canScrollLeft = $derived(scrollLeftPosition > 0); - let canScrollRight = $derived(Math.ceil(scrollLeftPosition) < innerWidth - offsetWidth); + let canScrollRight = $derived(Math.ceil(scrollLeftPosition) < Math.floor(innerWidth - offsetWidth)); const scrollBy = 400; const scrollLeft = () => memoryLaneElement?.scrollBy({ left: -scrollBy, behavior: 'smooth' }); @@ -38,7 +38,8 @@
    (offsetWidth = width)} onscroll={onScroll} > @@ -49,9 +50,11 @@ {/if} @@ -60,9 +63,11 @@ {/if} @@ -72,7 +77,7 @@ {#each $memoryStore as memory (memory.id)} {#if memory.assets.length > 0} interface Props { - title?: string | null; - height?: string | null; + height: number; } - let { title = null, height = null }: Props = $props(); + let { height = 0 }: Props = $props(); -
    - {#if title} -
    - {title} -
    - {/if} -
    +
    +
    diff --git a/web/src/lib/components/shared-components/tree/breadcrumbs.svelte b/web/src/lib/components/shared-components/tree/breadcrumbs.svelte index 6ed915e683..7da2215a77 100644 --- a/web/src/lib/components/shared-components/tree/breadcrumbs.svelte +++ b/web/src/lib/components/shared-components/tree/breadcrumbs.svelte @@ -45,7 +45,7 @@ onclick={() => {}} /> - {#each pathSegments as segment, index (segment)} + {#each pathSegments as segment, index (index)} {@const isLastSegment = index === pathSegments.length - 1}
  • diff --git a/web/src/lib/components/user-settings-page/device-list.svelte b/web/src/lib/components/user-settings-page/device-list.svelte index ab9159eae9..96870046df 100644 --- a/web/src/lib/components/user-settings-page/device-list.svelte +++ b/web/src/lib/components/user-settings-page/device-list.svelte @@ -1,11 +1,11 @@
    @@ -447,8 +467,12 @@ {#if assetInteraction.isAllUserOwned} assetStore.triggerUpdate()} - /> + onFavorite={(ids, isFavorite) => + assetStore.updateAssetOp(ids, (asset) => { + asset.isFavorite = isFavorite; + return { remove: false }; + })} + > {/if} @@ -462,11 +486,7 @@ onClick={() => updateThumbnailUsingCurrentSelection()} /> {/if} - assetStore.triggerUpdate()} - /> + {/if} {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned} @@ -489,6 +509,7 @@ { + assetStore.suspendTransitions = true; viewMode = AlbumPageViewMode.SELECT_ASSETS; oldAt = { at: $gridScrollTarget?.at }; await navigate( @@ -583,127 +604,117 @@ {/if}
    - - {#key albumKey} - {#if viewMode === AlbumPageViewMode.SELECT_ASSETS} - - {:else} - 0} - isSelectionMode={viewMode === AlbumPageViewMode.SELECT_THUMBNAIL} - singleSelect={viewMode === AlbumPageViewMode.SELECT_THUMBNAIL} - showArchiveIcon - onSelect={({ id }) => handleUpdateThumbnail(id)} - onEscape={handleEscape} - > - {#if viewMode !== AlbumPageViewMode.SELECT_THUMBNAIL} - -
    - (album.albumName = albumName)} - /> + + {#if viewMode !== AlbumPageViewMode.SELECT_ASSETS} + {#if viewMode !== AlbumPageViewMode.SELECT_THUMBNAIL} + +
    + (album.albumName = albumName)} + /> - {#if album.assetCount > 0} - - {/if} + {#if album.assetCount > 0} + + {/if} - - {#if album.albumUsers.length > 0 || (album.hasSharedLink && isOwned)} -
    - - {#if album.hasSharedLink && isOwned} - (viewMode = AlbumPageViewMode.LINK_SHARING)} - /> - {/if} + + {#if album.albumUsers.length > 0 || (album.hasSharedLink && isOwned)} +
    + + {#if album.hasSharedLink && isOwned} + (viewMode = AlbumPageViewMode.LINK_SHARING)} + /> + {/if} - - - - - {#each album.albumUsers.filter(({ role }) => role === AlbumUserRole.Editor) as { user } (user.id)} - - {/each} - - - {#if albumHasViewers} - (viewMode = AlbumPageViewMode.VIEW_USERS)} - /> - {/if} - - {#if isOwned} - (viewMode = AlbumPageViewMode.SELECT_USERS)} - title={$t('add_more_users')} - /> - {/if} -
    - {/if} - - -
    - {/if} - - {#if album.assetCount === 0} -
    -
    -

    {$t('add_photos').toUpperCase()}

    - -
    -
    - {/if} -
    - {/if} - {#if showActivityStatus} -
    - -
    + + {#each album.albumUsers.filter(({ role }) => role === AlbumUserRole.Editor) as { user } (user.id)} + + {/each} + + + {#if albumHasViewers} + (viewMode = AlbumPageViewMode.VIEW_USERS)} + /> + {/if} + + {#if isOwned} + (viewMode = AlbumPageViewMode.SELECT_USERS)} + title={$t('add_more_users')} + /> + {/if} +
    + {/if} + + +
  • + {/if} + + {#if album.assetCount === 0} +
    +
    +

    {$t('add_photos').toUpperCase()}

    + +
    +
    + {/if} {/if} - {/key} + + + {#if showActivityStatus} +
    + +
    + {/if} {#if album.albumUsers.length > 0 && album && isShowActivity && $user && !$showAssetViewer} diff --git a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte index 3ef28cd657..1093997412 100644 --- a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -12,20 +12,23 @@ import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import { AssetAction } from '$lib/constants'; - import { AssetStore } from '$lib/stores/assets.store'; + import type { PageData } from './$types'; import { mdiPlus, mdiDotsVertical } from '@mdi/js'; import { t } from 'svelte-i18n'; import { onDestroy } from 'svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; + import { AssetStore } from '$lib/stores/assets-store.svelte'; interface Props { data: PageData; } let { data }: Props = $props(); + const assetStore = new AssetStore(); + void assetStore.updateOptions({ isArchived: true }); + onDestroy(() => assetStore.destroy()); - const assetStore = new AssetStore({ isArchived: true }); const assetInteraction = new AssetInteraction(); const handleEscape = () => { @@ -34,10 +37,6 @@ return; } }; - - onDestroy(() => { - assetStore.destroy(); - }); {#if assetInteraction.selectionActive} @@ -45,14 +44,28 @@ assets={assetInteraction.selectedAssets} clearSelect={() => assetInteraction.clearMultiselect()} > - assetStore.removeAssets(assetIds)} /> + + assetStore.updateAssetOp(ids, (asset) => { + asset.isArchived = isArchived; + return { remove: false }; + })} + /> - assetStore.triggerUpdate()} /> + + assetStore.updateAssetOp(ids, (asset) => { + asset.isFavorite = isFavorite; + return { remove: false }; + })} + /> assetStore.removeAssets(assetIds)} /> diff --git a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte index 94436a3dc9..120281b07e 100644 --- a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -14,7 +14,7 @@ import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import { AssetAction } from '$lib/constants'; - import { AssetStore } from '$lib/stores/assets.store'; + import { AssetStore } from '$lib/stores/assets-store.svelte'; import type { PageData } from './$types'; import { mdiDotsVertical, mdiPlus } from '@mdi/js'; import { t } from 'svelte-i18n'; @@ -29,7 +29,10 @@ let { data }: Props = $props(); - const assetStore = new AssetStore({ isFavorite: true }); + const assetStore = new AssetStore(); + void assetStore.updateOptions({ isFavorite: true }); + onDestroy(() => assetStore.destroy()); + const assetInteraction = new AssetInteraction(); const handleEscape = () => { @@ -38,10 +41,6 @@ return; } }; - - onDestroy(() => { - assetStore.destroy(); - }); diff --git a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte index bcedeb0bc5..160c236049 100644 --- a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -7,7 +7,7 @@ import TreeItemThumbnails from '$lib/components/shared-components/tree/tree-item-thumbnails.svelte'; import TreeItems from '$lib/components/shared-components/tree/tree-items.svelte'; import { AppRoute, QueryParameter } from '$lib/constants'; - import type { Viewport } from '$lib/stores/assets.store'; + import type { Viewport } from '$lib/stores/assets-store.svelte'; import { foldersStore } from '$lib/stores/folders.svelte'; import { buildTree, normalizeTreePath } from '$lib/utils/tree-utils'; import { mdiDotsVertical, mdiFolder, mdiFolderHome, mdiFolderOutline, mdiPlus, mdiSelectAll } from '@mdi/js'; @@ -98,7 +98,19 @@ cancelMultiselect(assetInteraction)} /> cancelMultiselect(assetInteraction)} shared /> - + { + if (data.pathAssets && data.pathAssets.length > 0) { + for (const id of ids) { + const asset = data.pathAssets.find((asset) => asset.id === id); + if (asset) { + asset.isFavorite = isFavorite; + } + } + } + }} + /> diff --git a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte index 2239a21cd5..0a71f35ff2 100644 --- a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -123,7 +123,7 @@ async function navigateRandom() { if (viewingAssets.length <= 0) { - return null; + return undefined; } const index = Math.floor(Math.random() * viewingAssets.length); const asset = await setAssetId(viewingAssets[index]); diff --git a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 9060f84a3b..22a0d82cf4 100644 --- a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -8,7 +8,7 @@ import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte'; import { AppRoute } from '$lib/constants'; - import { AssetStore } from '$lib/stores/assets.store'; + import { AssetStore } from '$lib/stores/assets-store.svelte'; import { onDestroy } from 'svelte'; import type { PageData } from './$types'; import { mdiPlus, mdiArrowLeft } from '@mdi/js'; @@ -21,7 +21,9 @@ let { data }: Props = $props(); - const assetStore = new AssetStore({ userId: data.partner.id, isArchived: false, withStacked: true }); + const assetStore = new AssetStore(); + $effect(() => void assetStore.updateOptions({ userId: data.partner.id, isArchived: false, withStacked: true })); + onDestroy(() => assetStore.destroy()); const assetInteraction = new AssetInteraction(); const handleEscape = () => { @@ -30,10 +32,6 @@ return; } }; - - onDestroy(() => { - assetStore.destroy(); - });
    diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index f3ea5d8638..45e18fd398 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -11,7 +11,6 @@ import SearchPeople from '$lib/components/faces-page/people-search.svelte'; import SetBirthDateModal from '$lib/components/faces-page/set-birth-date-modal.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; - import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; import { notificationController, NotificationType, @@ -46,10 +45,9 @@ let selectHidden = $state(false); let searchName = $state(''); - let showChangeNameModal = $state(false); let showSetBirthDateModal = $state(false); let showMergeModal = $state(false); - let personName = $state(''); + let newName = $state(''); let currentPage = $state(1); let nextPage = $state(data.people.hasNextPage ? 2 : null); let personMerge1 = $state(); @@ -57,10 +55,9 @@ let potentialMergePeople: PersonResponseDto[] = $state([]); let edittingPerson: PersonResponseDto | null = $state(null); let searchedPeopleLocal: PersonResponseDto[] = $state([]); - // let handleSearchPeople: (force?: boolean, name?: string) => Promise = $state(); - let changeNameInputEl = $state(); let innerHeight = $state(0); let searchPeopleElement = $state>(); + onMount(() => { const getSearchedPeople = $page.url.searchParams.get(QueryParameter.SEARCHED_PEOPLE); if (getSearchedPeople) { @@ -158,7 +155,7 @@ } catch (error) { handleError(error, $t('errors.unable_to_save_name')); } - if (personToBeMergedIn.name !== personName && edittingPerson.id === personToBeMergedIn.id) { + if (personToBeMergedIn.name !== newName && edittingPerson.id === personToBeMergedIn.id) { /* * * If the user merges one of the suggested people into the person he's editing it, it's merging the suggested person AND renames @@ -166,11 +163,11 @@ * */ try { - await updatePerson({ id: personToBeMergedIn.id, personUpdateDto: { name: personName } }); + await updatePerson({ id: personToBeMergedIn.id, personUpdateDto: { name: newName } }); for (const person of people) { if (person.id === personToBeMergedIn.id) { - person.name = personName; + person.name = newName; break; } } @@ -184,15 +181,6 @@ } }; - const handleChangeName = (detail: PersonResponseDto) => { - showChangeNameModal = true; - personName = detail.name; - personMerge1 = detail; - edittingPerson = detail; - - setTimeout(() => changeNameInputEl?.focus(), 100); - }; - const handleSetBirthDate = (detail: PersonResponseDto) => { showSetBirthDateModal = true; edittingPerson = detail; @@ -212,7 +200,6 @@ return person; }); - showChangeNameModal = false; notificationController.show({ message: $t('changed_visibility_successfully'), type: NotificationType.Info, @@ -247,46 +234,6 @@ ); }; - const submitNameChange = async (event: Event) => { - event.preventDefault(); - - potentialMergePeople = []; - showChangeNameModal = false; - if (!edittingPerson || personName === edittingPerson.name) { - return; - } - if (personName === '') { - await changeName(); - return; - } - const data = await searchPerson({ name: personName, withHidden: true }); - - // We check if another person has the same name as the name entered by the user - - const existingPerson = data.find( - (person: PersonResponseDto) => - person.name.toLowerCase() === personName.toLowerCase() && - edittingPerson && - person.id !== edittingPerson.id && - person.name, - ); - if (existingPerson) { - personMerge2 = existingPerson; - showMergeModal = true; - potentialMergePeople = people - .filter( - (person: PersonResponseDto) => - personMerge2?.name.toLowerCase() === person.name.toLowerCase() && - person.id !== personMerge2.id && - person.id !== personMerge1?.id && - !person.isHidden, - ) - .slice(0, 3); - return; - } - await changeName(); - }; - const submitBirthDateChange = async (value: string) => { showSetBirthDateModal = false; if (!edittingPerson || value === edittingPerson.birthDate) { @@ -314,33 +261,6 @@ } }; - const changeName = async () => { - showMergeModal = false; - showChangeNameModal = false; - - if (!edittingPerson) { - return; - } - try { - const updatedPerson = await updatePerson({ - id: edittingPerson.id, - personUpdateDto: { name: personName }, - }); - people = people.map((person: PersonResponseDto) => { - if (person.id === updatedPerson.id) { - return updatedPerson; - } - return person; - }); - notificationController.show({ - message: $t('change_name_successfully'), - type: NotificationType.Info, - }); - } catch (error) { - handleError(error, $t('errors.unable_to_save_name')); - } - }; - const onResetSearchBar = async () => { await clearQueryParam(QueryParameter.SEARCHED_PEOPLE, $page.url); }; @@ -353,12 +273,74 @@ let countVisiblePeople = $derived(searchName ? searchedPeopleLocal.length : data.people.total - data.people.hidden); let showPeople = $derived(searchName ? searchedPeopleLocal : visiblePeople); - // const submitNameChange = (event: Event) => { - // event.preventDefault(); - // if (searchPeopleElement) { - // handlePromiseError(searchPeopleElement.searchPeople(true, searchName)); - // } - // }; + const onNameChangeInputFocus = (person: PersonResponseDto) => { + edittingPerson = person; + newName = person.name; + }; + + const onNameChangeSubmit = async (name: string, targetPerson: PersonResponseDto) => { + try { + if (name == targetPerson.name || showMergeModal) { + return; + } + + if (name === '') { + await updateName(targetPerson.id, ''); + return; + } + + const personWithSimilarName = await findPeopleWithSimilarName(name, targetPerson.id); + if (personWithSimilarName) { + personMerge1 = targetPerson; + personMerge2 = personWithSimilarName; + potentialMergePeople = people + .filter( + (person: PersonResponseDto) => + personMerge2?.name.toLowerCase() === person.name.toLowerCase() && + person.id !== personMerge2.id && + person.id !== personMerge1?.id && + !person.isHidden, + ) + .slice(0, 3); + showMergeModal = true; + return; + } + await updateName(targetPerson.id, name); + } catch (error) { + handleError(error, $t('errors.unable_to_save_name')); + } + }; + + const onNameChangeInputUpdate = (event: Event) => { + if (event.target) { + newName = (event.target as HTMLInputElement).value; + } + }; + + const updateName = async (id: string, name: string) => { + await updatePerson({ + id, + personUpdateDto: { name }, + }); + + newName = ''; + }; + + const findPeopleWithSimilarName = async (name: string, personId: string) => { + const searchResult = await searchPerson({ name, withHidden: true }); + return searchResult.find( + (person) => person.name.toLowerCase() === name.toLowerCase() && person.id !== personId && person.name, + ); + }; + + const handleMergeCancel = async () => { + if (!personMerge1) { + return; + } + + await updateName(personMerge1.id, newName); + showMergeModal = false; + }; @@ -368,8 +350,10 @@ {personMerge1} {personMerge2} {potentialMergePeople} - onClose={() => (showMergeModal = false)} - onReject={changeName} + onClose={() => { + showMergeModal = false; + }} + onReject={() => handleMergeCancel()} onConfirm={handleMergeSamePerson} /> {/if} @@ -425,15 +409,30 @@ {#if countVisiblePeople > 0 && (!searchName || searchedPeopleLocal.length > 0)} {#snippet children({ person, index })} - handleChangeName(person)} - onSetBirthDate={() => handleSetBirthDate(person)} - onMergePeople={() => handleMergePeople(person)} - onHidePerson={() => handleHidePerson(person)} - onToggleFavorite={() => handleToggleFavorite(person)} - /> +
    + handleSetBirthDate(person)} + onMergePeople={() => handleMergePeople(person)} + onHidePerson={() => handleHidePerson(person)} + onToggleFavorite={() => handleToggleFavorite(person)} + /> + +
    onNameChangeSubmit(newName, person)}> + onNameChangeInputFocus(person)} + onfocusout={() => onNameChangeSubmit(newName, person)} + oninput={(event) => onNameChangeInputUpdate(event)} + /> +
    +
    {/snippet}
    {:else} @@ -447,35 +446,6 @@ {/if} - {#if showChangeNameModal} - (showChangeNameModal = false)}> -
    -
    - - -
    -
    - - {#snippet stickyBottom()} - - - {/snippet} -
    - {/if} - {#if showSetBirthDateModal} {#if selectHidden} - + {/if} diff --git a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte index f025b7b50e..614e33b774 100644 --- a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -33,7 +33,7 @@ import { AppRoute, PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import { AssetStore } from '$lib/stores/assets.store'; + import { AssetStore } from '$lib/stores/assets-store.svelte'; import { preferences } from '$lib/stores/user.store'; import { websocketEvents } from '$lib/stores/websocket'; import { getPeopleThumbnailUrl, handlePromiseError } from '$lib/utils'; @@ -62,6 +62,8 @@ import { onDestroy, onMount } from 'svelte'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; + import { locale } from '$lib/stores/preferences.store'; + import { DateTime } from 'luxon'; interface Props { data: PageData; @@ -72,18 +74,9 @@ let numberOfAssets = $state(data.statistics.assets); let { isViewing: showAssetViewer } = assetViewingStore; - const assetStoreOptions = { isArchived: false, personId: data.person.id }; - const assetStore = new AssetStore(assetStoreOptions); - - $effect(() => { - // Check to trigger rebuild the timeline when navigating between people from the info panel - const change = assetStoreOptions.personId !== data.person.id; - assetStoreOptions.personId = data.person.id; - handlePromiseError(assetStore.updateOptions(assetStoreOptions)); - if (change) { - assetStore.triggerUpdate(); - } - }); + const assetStore = new AssetStore(); + $effect(() => void assetStore.updateOptions({ isArchived: false, personId: data.person.id })); + onDestroy(() => assetStore.destroy()); const assetInteraction = new AssetInteraction(); @@ -156,7 +149,7 @@ }); const handleUnmerge = () => { - $assetStore.removeAssets(assetInteraction.selectedAssetsArray.map((a) => a.id)); + assetStore.removeAssets(assetInteraction.selectedAssetsArray.map((a) => a.id)); assetInteraction.clearMultiselect(); viewMode = PersonPageViewMode.VIEW_ASSETS; }; @@ -358,13 +351,10 @@ }; const handleDeleteAssets = async (assetIds: string[]) => { - $assetStore.removeAssets(assetIds); + assetStore.removeAssets(assetIds); await updateAssetCount(); }; - onDestroy(() => { - assetStore.destroy(); - }); let person = $derived(data.person); let thumbnailData = $derived(getPeopleThumbnailUrl(person)); @@ -420,7 +410,14 @@ - assetStore.triggerUpdate()} /> + + assetStore.updateAssetOp(ids, (asset) => { + asset.isFavorite = isFavorite; + return { remove: false }; + })} + /> $assetStore.removeAssets(assetIds)} + onArchive={(assetIds) => assetStore.removeAssets(assetIds)} /> {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned} @@ -543,12 +540,28 @@ heightStyle="3.375rem" />

    {person.name || $t('add_a_name')}

    -

    +

    {$t('assets_count', { values: { count: numberOfAssets } })}

    + {#if person.birthDate} +

    + {$t('person_birthdate', { + values: { + date: DateTime.fromISO(person.birthDate).toLocaleString( + { + month: 'numeric', + day: 'numeric', + year: 'numeric', + }, + { locale: $locale }, + ), + }, + })} +

    + {/if}
    diff --git a/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte index ff99599c51..e262893f33 100644 --- a/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte @@ -22,7 +22,7 @@ import { AssetAction } from '$lib/constants'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import { AssetStore } from '$lib/stores/assets.store'; + import { AssetStore } from '$lib/stores/assets-store.svelte'; import { isFaceEditMode } from '$lib/stores/face-edit.svelte'; import { preferences, user } from '$lib/stores/user.store'; import type { OnLink, OnUnlink } from '$lib/utils/actions'; @@ -33,7 +33,10 @@ import { t } from 'svelte-i18n'; let { isViewing: showAssetViewer } = assetViewingStore; - const assetStore = new AssetStore({ isArchived: false, withStacked: true, withPartners: true }); + const assetStore = new AssetStore(); + void assetStore.updateOptions({ isArchived: false, withStacked: true, withPartners: true }); + onDestroy(() => assetStore.destroy()); + const assetInteraction = new AssetInteraction(); let selectedAssets = $derived(assetInteraction.selectedAssetsArray); @@ -67,10 +70,6 @@ assetStore.updateAssets([still]); }; - onDestroy(() => { - assetStore.destroy(); - }); - beforeNavigate(() => { isFaceEditMode.value = false; }); @@ -88,7 +87,14 @@
    - assetStore.triggerUpdate()} /> + + assetStore.updateAssetOp(ids, (asset) => { + asset.isFavorite = isFavorite; + return { remove: false }; + })} + > {#if assetInteraction.selectedAssets.size > 1 || isAssetStackSelected} diff --git a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte index c2f0ed619c..c7f62cba0b 100644 --- a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -1,6 +1,6 @@ {#if assetInteraction.selectionActive} diff --git a/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte index d517dad943..d5a7128a4c 100644 --- a/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -186,7 +186,6 @@ shape="round" variant="ghost" color="secondary" - size="large" icon={mdiKeyboard} title={$t('show_keyboard_shortcuts')} onclick={() => (isShowKeyboardShortcut = !isShowKeyboardShortcut)} diff --git a/web/src/routes/+error.svelte b/web/src/routes/+error.svelte index 95f28e1539..9da0d52ff0 100644 --- a/web/src/routes/+error.svelte +++ b/web/src/routes/+error.svelte @@ -1,6 +1,6 @@ - + diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 2706ead46e..f3b3930e12 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -3,7 +3,7 @@ import { page } from '$app/state'; import { shortcut } from '$lib/actions/shortcut'; import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte'; - import Error from '$lib/components/error.svelte'; + import ErrorLayout from '$lib/components/layouts/ErrorLayout.svelte'; import AppleHeader from '$lib/components/shared-components/apple-header.svelte'; import DialogWrapper from '$lib/components/shared-components/dialog/dialog-wrapper.svelte'; import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte'; @@ -17,11 +17,11 @@ import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket'; import { copyToClipboard, setKey } from '$lib/utils'; import { isAssetViewerRoute, isSharedLinkRoute } from '$lib/utils/navigation'; - import { onDestroy, onMount, type Snippet } from 'svelte'; - import { run } from 'svelte/legacy'; import { setTranslations } from '@immich/ui'; - import '../app.css'; + import { onDestroy, onMount, type Snippet } from 'svelte'; import { t } from 'svelte-i18n'; + import { run } from 'svelte/legacy'; + import '../app.css'; interface Props { children?: Snippet; @@ -141,7 +141,7 @@ /> {#if page.data.error} - + {:else} {@render children?.()} {/if} diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index b3ac52bd7c..aa89fc0480 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -9,7 +9,7 @@
    - {$t('welcome_to_immich')} + {$t('welcome_to_immich')} diff --git a/web/src/routes/admin/jobs-status/+page.svelte b/web/src/routes/admin/jobs-status/+page.svelte index a3e3d6eb04..21381081e0 100644 --- a/web/src/routes/admin/jobs-status/+page.svelte +++ b/web/src/routes/admin/jobs-status/+page.svelte @@ -46,6 +46,7 @@ { title: $t('admin.user_cleanup_job'), value: ManualJobName.UserCleanup }, { title: $t('admin.memory_cleanup_job'), value: ManualJobName.MemoryCleanup }, { title: $t('admin.memory_generate_job'), value: ManualJobName.MemoryCreate }, + { title: $t('admin.backup_database'), value: ManualJobName.BackupDatabase }, ].map(({ value, title }) => ({ id: value, label: title, value })); const handleCancel = () => (isOpen = false); diff --git a/web/src/routes/admin/library-management/+page.svelte b/web/src/routes/admin/library-management/+page.svelte index 3e3a5491fa..9a598101e1 100644 --- a/web/src/routes/admin/library-management/+page.svelte +++ b/web/src/routes/admin/library-management/+page.svelte @@ -357,12 +357,6 @@
    - {#if renameLibrary === index} - -
    - (renameLibrary = undefined)} /> -
    - {/if} {#if editImportPaths === index}
    @@ -394,3 +388,11 @@
    + +{#if renameLibrary !== undefined} + (renameLibrary = undefined)} + /> +{/if} diff --git a/web/src/routes/admin/user-management/+page.svelte b/web/src/routes/admin/user-management/+page.svelte index 6fe8dea1f3..9ec2e9eab1 100644 --- a/web/src/routes/admin/user-management/+page.svelte +++ b/web/src/routes/admin/user-management/+page.svelte @@ -155,7 +155,7 @@ onConfirm={() => (shouldShowPasswordResetSuccess = false)} onCancel={() => (shouldShowPasswordResetSuccess = false)} hideCancelButton={true} - confirmColor="green" + confirmColor="success" > {#snippet promptSnippet()}
    @@ -222,7 +222,7 @@ {#if !immichUser.deletedAt} editUserHandler(immichUser)} @@ -231,7 +231,7 @@ {#if immichUser.id !== $user.id} deleteUserHandler(immichUser)} @@ -242,7 +242,7 @@ {#if immichUser.deletedAt && immichUser.status === UserStatus.Deleted} 0); const errorMessage = $derived(passwordConfirm.length === 0 || valid ? '' : $t('password_does_not_match')); - const onSubmit = async (event: Event) => { - event.preventDefault(); - + const onSubmit = async () => { if (!valid) { return; } - await updateMyUser({ userUpdateMeDto: { password: String(password) } }); + await updateMyUser({ userUpdateMeDto: { password } }); await goto(AppRoute.AUTH_LOGIN); resetSavedUser(); await logout(); @@ -34,7 +32,7 @@ -
    + {$t('hi_user', { values: { name: $user.name, email: $user.email } })} diff --git a/web/src/routes/auth/onboarding/+page.svelte b/web/src/routes/auth/onboarding/+page.svelte index 3f31dd587a..9950b0d09d 100644 --- a/web/src/routes/auth/onboarding/+page.svelte +++ b/web/src/routes/auth/onboarding/+page.svelte @@ -5,7 +5,7 @@ import { page } from '$app/stores'; import OnboardingHello from '$lib/components/onboarding-page/onboarding-hello.svelte'; import OnboardingPrivacy from '$lib/components/onboarding-page/onboarding-privacy.svelte'; - import OnboadingStorageTemplate from '$lib/components/onboarding-page/onboarding-storage-template.svelte'; + import OnboardingStorageTemplate from '$lib/components/onboarding-page/onboarding-storage-template.svelte'; import OnboardingTheme from '$lib/components/onboarding-page/onboarding-theme.svelte'; import { AppRoute, QueryParameter } from '$lib/constants'; import { retrieveServerConfig } from '$lib/stores/server-config.store'; @@ -18,7 +18,7 @@ component: | typeof OnboardingHello | typeof OnboardingTheme - | typeof OnboadingStorageTemplate + | typeof OnboardingStorageTemplate | typeof OnboardingPrivacy; } @@ -26,7 +26,7 @@ { name: 'hello', component: OnboardingHello }, { name: 'theme', component: OnboardingTheme }, { name: 'privacy', component: OnboardingPrivacy }, - { name: 'storage', component: OnboadingStorageTemplate }, + { name: 'storage', component: OnboardingStorageTemplate }, ]; run(() => { diff --git a/web/src/routes/link/+page.ts b/web/src/routes/link/+page.ts index caa4108bfa..1bd0f4c856 100644 --- a/web/src/routes/link/+page.ts +++ b/web/src/routes/link/+page.ts @@ -2,14 +2,14 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const load = (({ url }) => { - enum LinkTarget { - HOME = 'home', - UNSUBSCRIBE = 'unsubscribe', - VIEW_ASSET = 'view_asset', - ACTIVATE_LICENSE = 'activate_license', - } +enum LinkTarget { + HOME = 'home', + UNSUBSCRIBE = 'unsubscribe', + VIEW_ASSET = 'view_asset', + ACTIVATE_LICENSE = 'activate_license', +} +export const load = (({ url }) => { const queryParams = url.searchParams; const target = queryParams.get('target') as LinkTarget; switch (target) { diff --git a/web/static/immich-logo-no-bg.svg b/web/static/immich-logo-no-bg.svg deleted file mode 100644 index 376fa6f3e8..0000000000 --- a/web/static/immich-logo-no-bg.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - diff --git a/web/static/immich-logo.svg b/web/static/immich-logo.svg deleted file mode 100644 index a3522c233f..0000000000 --- a/web/static/immich-logo.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/web/tsconfig.json b/web/tsconfig.json index 31aef23e31..c7bc16f52b 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -4,7 +4,7 @@ "checkJs": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "module": "es2020", + "module": "es2022", "moduleResolution": "bundler", "resolveJsonModule": true, "skipLibCheck": true, diff --git a/web/vite.config.js b/web/vite.config.js index a2e7393df9..0ffc0e0eb1 100644 --- a/web/vite.config.js +++ b/web/vite.config.js @@ -14,6 +14,9 @@ const upstream = { }; export default defineConfig({ + build: { + target: 'es2022', + }, resolve: { alias: { 'xmlhttprequest-ssl': './node_modules/engine.io-client/lib/xmlhttprequest.js',