From 6159c83fd234341a8460b445452e0e9a95e37250 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 25 Oct 2022 09:51:03 -0500 Subject: [PATCH] feat(mobile) duplicated asset upload handling mechanism (#853) --- mobile/assets/i18n/de-DE.json | 3 +- mobile/lib/constants/hive_box.dart | 4 + mobile/lib/main.dart | 3 + .../background.service.dart | 8 +- .../models/error_upload_asset.model.dart | 2 + .../models/hive_duplicated_assets.model.dart | 57 ++++++++++ .../hive_duplicated_assets.model.g.dart | Bin 0 -> 1184 bytes .../backup/providers/backup.provider.dart | 44 ++++++-- .../backup/services/backup.service.dart | 92 +++++++++++---- .../backup/views/backup_controller_page.dart | 1 - mobile/openapi/.openapi-generator/FILES | 6 + mobile/openapi/README.md | Bin 9354 -> 9594 bytes mobile/openapi/doc/AssetApi.md | Bin 23103 -> 24771 bytes mobile/openapi/doc/CheckExistingAssetsDto.md | Bin 0 -> 485 bytes .../doc/CheckExistingAssetsResponseDto.md | Bin 0 -> 458 bytes mobile/openapi/lib/api.dart | Bin 3408 -> 3507 bytes mobile/openapi/lib/api/asset_api.dart | Bin 28410 -> 30407 bytes mobile/openapi/lib/api_client.dart | Bin 13846 -> 14054 bytes .../lib/model/check_existing_assets_dto.dart | Bin 0 -> 3891 bytes .../check_existing_assets_response_dto.dart | Bin 0 -> 3768 bytes .../test/check_existing_assets_dto_test.dart | Bin 0 -> 735 bytes ...eck_existing_assets_response_dto_test.dart | Bin 0 -> 652 bytes .../src/api-v1/album/album.service.spec.ts | 1 + .../src/api-v1/asset/asset-repository.ts | 17 +++ .../src/api-v1/asset/asset.controller.ts | 16 +++ .../src/api-v1/asset/asset.service.spec.ts | 1 + .../immich/src/api-v1/asset/asset.service.ts | 9 ++ .../asset/dto/check-existing-assets.dto.ts | 9 ++ .../check-existing-assets-response.dto.ts | 7 ++ server/immich-openapi-specs.json | 2 +- web/src/api/open-api/api.ts | 105 ++++++++++++++++++ web/src/lib/utils/file-uploader.ts | 6 +- 32 files changed, 355 insertions(+), 38 deletions(-) create mode 100644 mobile/lib/modules/backup/models/hive_duplicated_assets.model.dart create mode 100644 mobile/lib/modules/backup/models/hive_duplicated_assets.model.g.dart create mode 100644 mobile/openapi/doc/CheckExistingAssetsDto.md create mode 100644 mobile/openapi/doc/CheckExistingAssetsResponseDto.md create mode 100644 mobile/openapi/lib/model/check_existing_assets_dto.dart create mode 100644 mobile/openapi/lib/model/check_existing_assets_response_dto.dart create mode 100644 mobile/openapi/test/check_existing_assets_dto_test.dart create mode 100644 mobile/openapi/test/check_existing_assets_response_dto_test.dart create mode 100644 server/apps/immich/src/api-v1/asset/dto/check-existing-assets.dto.ts create mode 100644 server/apps/immich/src/api-v1/asset/response-dto/check-existing-assets-response.dto.ts diff --git a/mobile/assets/i18n/de-DE.json b/mobile/assets/i18n/de-DE.json index b8eb778732..c8e7957504 100644 --- a/mobile/assets/i18n/de-DE.json +++ b/mobile/assets/i18n/de-DE.json @@ -24,6 +24,7 @@ "backup_controller_page_backup_selected": "Ausgewählt: ", "backup_controller_page_backup_sub": "Gesicherte Fotos und Videos", "backup_controller_page_cancel": "Abbrechen", + "backup_background_service_default_notification": "Suche nach neuen assets…", "backup_controller_page_created": "Erstellt: {}", "backup_controller_page_desc_backup": "Aktiviere die Sicherung um Elemente automatisch auf den Server zu laden.", "backup_controller_page_excluded": "Ausgeschlossen: ", @@ -123,4 +124,4 @@ "version_announcement_overlay_text_2": "Bitte nehm dir die Zeit und lese das ", "version_announcement_overlay_text_3": " und achte darauf, dass deine docker-compose und .env Dateien aktuell sind, vor allem wenn du ein System für automatische Updates benutzt (z.B. Watchtower).", "version_announcement_overlay_title": "Neue Server-Version verfügbar \uD83C\uDF89" -} \ No newline at end of file +} diff --git a/mobile/lib/constants/hive_box.dart b/mobile/lib/constants/hive_box.dart index 7faf6555f6..5a65c248aa 100644 --- a/mobile/lib/constants/hive_box.dart +++ b/mobile/lib/constants/hive_box.dart @@ -25,3 +25,7 @@ const String backgroundBackupInfoBox = "immichBackgroundBackupInfoBox"; // Box const String backupFailedSince = "immichBackupFailedSince"; // Key 1 const String backupRequireWifi = "immichBackupRequireWifi"; // Key 2 const String backupRequireCharging = "immichBackupRequireCharging"; // Key 3 + +// Duplicate asset +const String duplicatedAssetsBox = "immichDuplicatedAssetsBox"; // Box +const String duplicatedAssetsKey = "immichDuplicatedAssetsKey"; // Key 1 diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 363908a31f..c68ec501d0 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -10,6 +10,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/modules/backup/background_service/background.service.dart'; import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart'; +import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model.dart'; import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart'; import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; @@ -30,12 +31,14 @@ void main() async { Hive.registerAdapter(HiveSavedLoginInfoAdapter()); Hive.registerAdapter(HiveBackupAlbumsAdapter()); + Hive.registerAdapter(HiveDuplicatedAssetsAdapter()); await Hive.openBox(userInfoBox); await Hive.openBox(hiveLoginInfoBox); await Hive.openBox(hiveBackupInfoBox); await Hive.openBox(hiveGithubReleaseInfoBox); await Hive.openBox(userSettingInfoBox); + await Hive.openBox(duplicatedAssetsBox); SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( diff --git a/mobile/lib/modules/backup/background_service/background.service.dart b/mobile/lib/modules/backup/background_service/background.service.dart index f5a0086b5b..c4ecfb4fce 100644 --- a/mobile/lib/modules/backup/background_service/background.service.dart +++ b/mobile/lib/modules/backup/background_service/background.service.dart @@ -14,6 +14,7 @@ import 'package:immich_mobile/modules/backup/background_service/localization.dar import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart'; import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart'; import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart'; +import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model.dart'; import 'package:immich_mobile/modules/backup/services/backup.service.dart'; import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; @@ -316,10 +317,13 @@ class BackgroundService { Hive.registerAdapter(HiveSavedLoginInfoAdapter()); Hive.registerAdapter(HiveBackupAlbumsAdapter()); + Hive.registerAdapter(HiveDuplicatedAssetsAdapter()); + await Hive.openBox(userInfoBox); await Hive.openBox(hiveLoginInfoBox); await Hive.openBox(userSettingInfoBox); await Hive.openBox(backgroundBackupInfoBox); + await Hive.openBox(duplicatedAssetsBox); ApiService apiService = ApiService(); apiService.setEndpoint(Hive.box(userInfoBox).get(serverEndpointKey)); @@ -410,7 +414,7 @@ class BackgroundService { final bool ok = await backupService.backupAsset( toUpload, _cancellationToken!, - notifyTotalProgress ? _onAssetUploaded : (assetId, deviceId) {}, + notifyTotalProgress ? _onAssetUploaded : (assetId, deviceId, isDup) {}, notifySingleProgress ? _onProgress : (sent, total) {}, notifySingleProgress ? _onSetCurrentBackupAsset : (asset) {}, _onBackupError, @@ -429,7 +433,7 @@ class BackgroundService { return "$percent% ($_uploadedAssetsCount/$_assetsToUploadCount)"; } - void _onAssetUploaded(String deviceAssetId, String deviceId) { + void _onAssetUploaded(String deviceAssetId, String deviceId, bool isDup) { debugPrint("Uploaded $deviceAssetId from $deviceId"); _uploadedAssetsCount++; _updateNotification( diff --git a/mobile/lib/modules/backup/models/error_upload_asset.model.dart b/mobile/lib/modules/backup/models/error_upload_asset.model.dart index 365a5a1696..6585b7c54e 100644 --- a/mobile/lib/modules/backup/models/error_upload_asset.model.dart +++ b/mobile/lib/modules/backup/models/error_upload_asset.model.dart @@ -45,8 +45,10 @@ class ErrorUploadAsset extends Equatable { List get props { return [ id, + createdAt, fileName, fileType, + asset, errorMessage, ]; } diff --git a/mobile/lib/modules/backup/models/hive_duplicated_assets.model.dart b/mobile/lib/modules/backup/models/hive_duplicated_assets.model.dart new file mode 100644 index 0000000000..6313be8f9f --- /dev/null +++ b/mobile/lib/modules/backup/models/hive_duplicated_assets.model.dart @@ -0,0 +1,57 @@ +import 'dart:convert'; + +import 'package:collection/collection.dart'; +import 'package:hive/hive.dart'; + +part 'hive_duplicated_assets.model.g.dart'; + +@HiveType(typeId: 2) +class HiveDuplicatedAssets { + @HiveField(0, defaultValue: []) + List duplicatedAssetIds; + + HiveDuplicatedAssets({ + required this.duplicatedAssetIds, + }); + + HiveDuplicatedAssets copyWith({ + List? duplicatedAssetIds, + }) { + return HiveDuplicatedAssets( + duplicatedAssetIds: duplicatedAssetIds ?? this.duplicatedAssetIds, + ); + } + + Map toMap() { + return { + 'duplicatedAssetIds': duplicatedAssetIds, + }; + } + + factory HiveDuplicatedAssets.fromMap(Map map) { + return HiveDuplicatedAssets( + duplicatedAssetIds: List.from(map['duplicatedAssetIds']), + ); + } + + String toJson() => json.encode(toMap()); + + factory HiveDuplicatedAssets.fromJson(String source) => + HiveDuplicatedAssets.fromMap(json.decode(source)); + + @override + String toString() => + 'HiveDuplicatedAssets(duplicatedAssetIds: $duplicatedAssetIds)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is HiveDuplicatedAssets && + listEquals(other.duplicatedAssetIds, duplicatedAssetIds); + } + + @override + int get hashCode => duplicatedAssetIds.hashCode; +} diff --git a/mobile/lib/modules/backup/models/hive_duplicated_assets.model.g.dart b/mobile/lib/modules/backup/models/hive_duplicated_assets.model.g.dart new file mode 100644 index 0000000000000000000000000000000000000000..20f6554984ac8943464df56ddbbbd7b0edaba620 GIT binary patch literal 1184 zcmb_bQE%EX5Pr|ExTiK*Szvnvk_{T#B5g!!k*2DuDia@#s}LjG(4m$8-m{Zzv_yMq zUSgj=fA`(zyKWaQC$q`*Xg*2cJWVF>8Ilxc=^TEh$@JnD#(m3z7;Bns5hYB5DwTWaCR0PXXqb>{Gj95_ElbmW~0hhXZ#ZzOwyQ*rPVd%Eb?=L}1u=g(L6H|Cxce_;kAdhGuXF5NpQG+P1J1J~J zNA0wbvH-cRhJ~E-)5Q~JG{q*j?GwedNb1ni&@>mm$%XDT=Pxhyxx<6_|7e_?)M)!& cYD0u8v_orO%n^i#!8hn;A{t<|A9#E373@h~asU7T literal 0 HcmV?d00001 diff --git a/mobile/lib/modules/backup/providers/backup.provider.dart b/mobile/lib/modules/backup/providers/backup.provider.dart index 7c628aedcf..172f6d6c4b 100644 --- a/mobile/lib/modules/backup/providers/backup.provider.dart +++ b/mobile/lib/modules/backup/providers/backup.provider.dart @@ -10,6 +10,7 @@ import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart'; import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart'; import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart'; +import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model.dart'; import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart'; import 'package:immich_mobile/modules/backup/background_service/background.service.dart'; import 'package:immich_mobile/modules/backup/services/backup.service.dart'; @@ -296,6 +297,7 @@ class BackupNotifier extends StateNotifier { /// Those assets are unique and are used as the total assets /// Future _updateBackupAssetCount() async { + Set duplicatedAssetIds = _backupService.getDuplicatedAssetIds(); Set assetsFromSelectedAlbums = {}; Set assetsFromExcludedAlbums = {}; @@ -326,9 +328,15 @@ class BackupNotifier extends StateNotifier { // Find asset that were backup from selected albums Set selectedAlbumsBackupAssets = Set.from(allUniqueAssets.map((e) => e.id)); + selectedAlbumsBackupAssets .removeWhere((assetId) => !allAssetsInDatabase.contains(assetId)); + // Remove duplicated asset from all unique assets + allUniqueAssets.removeWhere( + (asset) => duplicatedAssetIds.contains(asset.id), + ); + if (allUniqueAssets.isEmpty) { debugPrint("No Asset On Device"); state = state.copyWith( @@ -455,14 +463,26 @@ class BackupNotifier extends StateNotifier { ); } - void _onAssetUploaded(String deviceAssetId, String deviceId) { - state = state.copyWith( - selectedAlbumsBackupAssetsIds: { - ...state.selectedAlbumsBackupAssetsIds, - deviceAssetId - }, - allAssetsInDatabase: [...state.allAssetsInDatabase, deviceAssetId], - ); + void _onAssetUploaded( + String deviceAssetId, + String deviceId, + bool isDuplicated, + ) { + if (isDuplicated) { + state = state.copyWith( + allUniqueAssets: state.allUniqueAssets + .where((asset) => asset.id != deviceAssetId) + .toSet(), + ); + } else { + state = state.copyWith( + selectedAlbumsBackupAssetsIds: { + ...state.selectedAlbumsBackupAssetsIds, + deviceAssetId + }, + allAssetsInDatabase: [...state.allAssetsInDatabase, deviceAssetId], + ); + } if (state.allUniqueAssets.length - state.selectedAlbumsBackupAssetsIds.length == @@ -564,6 +584,7 @@ class BackupNotifier extends StateNotifier { albums.lastExcludedBackupTime, ); } + await Hive.openBox(duplicatedAssetsBox); final Box backgroundBox = await Hive.openBox(backgroundBackupInfoBox); state = state.copyWith( backupProgress: previous, @@ -608,6 +629,13 @@ class BackupNotifier extends StateNotifier { } catch (error) { debugPrint("[_notifyBackgroundServiceCanRun] failed to close box"); } + try { + if (Hive.isBoxOpen(duplicatedAssetsBox)) { + await Hive.box(duplicatedAssetsBox).close(); + } + } catch (error) { + debugPrint("[_notifyBackgroundServiceCanRun] failed to close box"); + } try { if (Hive.isBoxOpen(backgroundBackupInfoBox)) { await Hive.box(backgroundBackupInfoBox).close(); diff --git a/mobile/lib/modules/backup/services/backup.service.dart b/mobile/lib/modules/backup/services/backup.service.dart index 92655e4142..653528848a 100644 --- a/mobile/lib/modules/backup/services/backup.service.dart +++ b/mobile/lib/modules/backup/services/backup.service.dart @@ -19,6 +19,8 @@ import 'package:http_parser/http_parser.dart'; import 'package:path/path.dart' as p; import 'package:cancellation_token_http/http.dart' as http; +import '../models/hive_duplicated_assets.model.dart'; + final backupServiceProvider = Provider( (ref) => BackupService( ref.watch(apiServiceProvider), @@ -41,6 +43,29 @@ class BackupService { } } + void _saveDuplicatedAssetIdToLocalStorage(List deviceAssetIds) { + HiveDuplicatedAssets duplicatedAssets = + Hive.box(duplicatedAssetsBox) + .get(duplicatedAssetsKey) ?? + HiveDuplicatedAssets(duplicatedAssetIds: []); + + duplicatedAssets.duplicatedAssetIds = + {...duplicatedAssets.duplicatedAssetIds, ...deviceAssetIds}.toList(); + + Hive.box(duplicatedAssetsBox) + .put(duplicatedAssetsKey, duplicatedAssets); + } + + /// Get duplicated asset id from Hive storage + Set getDuplicatedAssetIds() { + HiveDuplicatedAssets duplicatedAssets = + Hive.box(duplicatedAssetsBox) + .get(duplicatedAssetsKey) ?? + HiveDuplicatedAssets(duplicatedAssetIds: []); + + return duplicatedAssets.duplicatedAssetIds.toSet(); + } + /// Returns all assets newer than the last successful backup per album Future> buildUploadCandidates( HiveBackupAlbums backupAlbums, @@ -140,34 +165,47 @@ class BackupService { Future> removeAlreadyUploadedAssets( List candidates, ) async { - final String deviceId = Hive.box(userInfoBox).get(deviceIdKey); - if (candidates.length < 10) { - final List duplicateResponse = - await Future.wait( - candidates.map( - (e) => _apiService.assetApi.checkDuplicateAsset( - CheckDuplicateAssetDto(deviceAssetId: e.id, deviceId: deviceId), - ), + if (candidates.isEmpty) { + return candidates; + } + final Set duplicatedAssetIds = getDuplicatedAssetIds(); + candidates = duplicatedAssetIds.isEmpty + ? candidates + : candidates + .whereNot((asset) => duplicatedAssetIds.contains(asset.id)) + .toList(); + if (candidates.isEmpty) { + return candidates; + } + final Set existing = {}; + try { + final String deviceId = Hive.box(userInfoBox).get(deviceIdKey); + final CheckExistingAssetsResponseDto? duplicates = + await _apiService.assetApi.checkExistingAssets( + CheckExistingAssetsDto( + deviceAssetIds: candidates.map((e) => e.id).toList(), + deviceId: deviceId, ), ); - return candidates - .whereIndexed((i, e) => duplicateResponse[i]?.isExist == false) - .toList(); - } else { - final List? allAssetsInDatabase = await getDeviceBackupAsset(); - - if (allAssetsInDatabase == null) { - return candidates; + if (duplicates != null) { + existing.addAll(duplicates.existingIds); + } + } on ApiException { + // workaround for older server versions or when checking for too many assets at once + final List? allAssetsInDatabase = await getDeviceBackupAsset(); + if (allAssetsInDatabase != null) { + existing.addAll(allAssetsInDatabase); } - final Set inDb = allAssetsInDatabase.toSet(); - return candidates.whereNot((e) => inDb.contains(e.id)).toList(); } + return existing.isEmpty + ? candidates + : candidates.whereNot((e) => existing.contains(e.id)).toList(); } Future backupAsset( Iterable assetList, http.CancellationToken cancelToken, - Function(String, String) singleAssetDoneCb, + Function(String, String, bool) uploadSuccessCb, Function(int, int) uploadProgressCb, Function(CurrentUploadAsset) setCurrentUploadAssetCb, Function(ErrorUploadAsset) errorCb, @@ -176,6 +214,7 @@ class BackupService { String savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey); File? file; bool anyErrors = false; + final List duplicatedAssetIds = []; for (var entity in assetList) { try { @@ -235,8 +274,13 @@ class BackupService { var response = await req.send(cancellationToken: cancelToken); - if (response.statusCode == 201) { - singleAssetDoneCb(entity.id, deviceId); + if (response.statusCode == 200) { + // asset is a duplicate (already exists on the server) + duplicatedAssetIds.add(entity.id); + uploadSuccessCb(entity.id, deviceId, true); + } else if (response.statusCode == 201) { + // stored a new asset on the server + uploadSuccessCb(entity.id, deviceId, false); } else { var data = await response.stream.bytesToString(); var error = jsonDecode(data); @@ -260,7 +304,8 @@ class BackupService { } } on http.CancelledException { debugPrint("Backup was cancelled by the user"); - return false; + anyErrors = true; + break; } catch (e) { debugPrint("ERROR backupAsset: ${e.toString()}"); anyErrors = true; @@ -271,6 +316,9 @@ class BackupService { } } } + if (duplicatedAssetIds.isNotEmpty) { + _saveDuplicatedAssetIdToLocalStorage(duplicatedAssetIds); + } return !anyErrors; } diff --git a/mobile/lib/modules/backup/views/backup_controller_page.dart b/mobile/lib/modules/backup/views/backup_controller_page.dart index 6f668576b9..1cd74118cc 100644 --- a/mobile/lib/modules/backup/views/backup_controller_page.dart +++ b/mobile/lib/modules/backup/views/backup_controller_page.dart @@ -419,7 +419,6 @@ class BackupControllerPage extends HookConsumerWidget { ActionChip( avatar: Icon( Icons.info, - size: 24, color: Colors.red[400], ), elevation: 1, diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 4e4d0ac35a..9594a725a4 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -19,6 +19,8 @@ doc/AssetTypeEnum.md doc/AuthenticationApi.md doc/CheckDuplicateAssetDto.md doc/CheckDuplicateAssetResponseDto.md +doc/CheckExistingAssetsDto.md +doc/CheckExistingAssetsResponseDto.md doc/CreateAlbumDto.md doc/CreateDeviceInfoDto.md doc/CreateProfileImageResponseDto.md @@ -93,6 +95,8 @@ lib/model/asset_response_dto.dart lib/model/asset_type_enum.dart lib/model/check_duplicate_asset_dto.dart lib/model/check_duplicate_asset_response_dto.dart +lib/model/check_existing_assets_dto.dart +lib/model/check_existing_assets_response_dto.dart lib/model/create_album_dto.dart lib/model/create_device_info_dto.dart lib/model/create_profile_image_response_dto.dart @@ -133,3 +137,5 @@ lib/model/user_count_response_dto.dart lib/model/user_response_dto.dart lib/model/validate_access_token_response_dto.dart pubspec.yaml +test/check_existing_assets_dto_test.dart +test/check_existing_assets_response_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 11325beaf5006c3761981ac9a0a656efb5dd186e..d99c0e1112d769ee499be910e5fd3abeff4c2d17 100644 GIT binary patch delta 221 zcmeD3{N=SliCZ{1BQ-hOwIZ{)Br`AFvA8(3q5)SVdUDLYq|? zdAaLRG&zEmXlZfTDL5k=1`!WREiTB(|K$Sut& z$t=i8Re(6hQvvK2h5S5)l8jV^;?$zD)FOq%ycC6^)RNMoykdpKoE(T6pr;gc6-tX! zQxuXa6_OH@v(t<6OMxmu%+i9*g^G6>J+Zq%!4{i;fR5A9v{um9$0j*B-bZh;0w=2y zEI=V)fX$PWKPm@9f*MurMstt$ delta 19 bcmX?nka7PO#tk}*o0}NfxHoST;`IXnP`w89 diff --git a/mobile/openapi/doc/CheckExistingAssetsDto.md b/mobile/openapi/doc/CheckExistingAssetsDto.md new file mode 100644 index 0000000000000000000000000000000000000000..878e200fdff93417dc8baf7914a27ec463dbe578 GIT binary patch literal 485 zcma)2!Ab)$6ujpv9_*nuknLSXsO}yX+EUcpvan(E+J^2XA$b)AKi*`!YW1SIgg0+y z9y1wOhekS`4_0BHKOC?-majUHHv44=NTDFhSeC)cN(FR47@6Q?ca(c%ZQCm8iRr;v zPcWPC>&bVKyc1z|lc=N9*2kqMje%|Cfbb5_Z!UcWhR$n8+L}0REJcR6coDpqg!pS_ zoGX0M9gayV6*!L)&Q~$t?GyQ!@ot17S}WYiQ%^t^I%@(!-85eYm71hy)u)KDy1tJ^ zB+)t*2z|6}nyWltJeJF1UFL(zllP*jX>e3d0<})yr-5Pi-tzpi$0hLq9Ce L2Y(%(GsZpu3@(>6 literal 0 HcmV?d00001 diff --git a/mobile/openapi/doc/CheckExistingAssetsResponseDto.md b/mobile/openapi/doc/CheckExistingAssetsResponseDto.md new file mode 100644 index 0000000000000000000000000000000000000000..c9486005fdaa86900a070cd9a5810ddee848da5a GIT binary patch literal 458 zcma)2L2AP=5WMRZ3qGVV$ho(rkkmc|k`U6~jKRX%BqFv}NE>LOkFVq!n)cG_B4#wR ztCdgc;_OuKEIUF!JvH?pbA+iiMq%`muVRV2~-9?>Hv~8=E zXJH0Mo?$le_tX4E+K~jaha?>*?R|XoY%qwO8W29<_1&eb#L#);*pQ8AM;sOM=0x%a zN%(jQC)imCg@^lhPr8lsOwcsnaFKw3HoSl zn!BP{9I9oxt%^Zsn!4+NmR0(-Jd&4VKVc5v?&W^{Pj75XqScO!SqRA*i@rlL7sU^je16dU}|6q+^2LOct B76JeO delta 16 XcmdlieL-r&Pu9r|Y;2p8*aF!BJLd)Y diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index 7c03109f43550dabcf5db01327af51b83d86d3b4..43de6a312491d28db6bb1f6c521db7fcb56d4166 100644 GIT binary patch delta 657 zcmex$m+|;p#tqt>ljS6MlbkbBle3EzGSd`tOLIyx3vyBw5{rvdONtdzD>92q6!P;F zN-|Ovic^cqQi~K4^HLOwQcFsU@`@D_b8;YRGV{_EbQMaAQ&SX@Dix9vle5!{@=Jj# zLCn&E$$=7flMR@()LcOZ0u?)g&37ruk5xzp*^VkcIWSV$3yXMoW=V!eNlAfcURu5e z*wv`|faYN}?k!Uw8`vxAlkJ18Cf6}@@S$qld|tPgaq@={x`c&ckcKvP$72tSL>|d_ zEb4<&iwpAeic^7}wztFPZBjxgF-CK8JR7SXYOtV$HlMC{NHggH?O$7i{@(1Ps diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 9688dfa96cb12a64e834bf94c228c11cf6fce0b0..234e05d3916ce47b59d90643470c74728c886ce9 100644 GIT binary patch delta 93 zcmbQ1^DKA6XL+8C)Z}c}ip=7Y%)Io;`=n%fol!*YNofkAh&UD(r%o2sR20S#Eh(O? MsG`03le~%;0O1-V-~a#s delta 12 TcmaEsJ1u9!XZg*C6_mvQEl~yM diff --git a/mobile/openapi/lib/model/check_existing_assets_dto.dart b/mobile/openapi/lib/model/check_existing_assets_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..e2693653100456ffb24d80db241a620c00eec221 GIT binary patch literal 3891 zcmds4ZExE)5dQ98aVd&g#uRzYr@@(>28}y7ZDSzK1`LKF&=MW9l|_xD;u)#``|e0l zmJ_*K_hrBW#IneH--pXz@201}jBm!bmv`eUcsKbl4&iDFH`6=#IK7&@ z`~3vPNb*g}lo_5yug^O4D(-Wsc)XMN#8EXTcOc4ObeCE8VW1$Xpwin^>giP%H&kR$aF`%S6s~oXPwi#mtK3 z@lQX-$%-l6>7Y9UatX5FIV(hj|L2`fk}<8}?TRN~#(xTJM1Fs%H8=XoNO|QUz#!)vn6*pKLD*0e9-&&hOh6U^%u47etWmgA28aVKN*D)kKoXl-FI8n&Lp*KZ{!143R8*t3n?>7hGIr47!HH!;xkV$MP|iS z4~8RfDNk4yP{5(z`TEs;193EQuU^?RIJsvh9X<&c+QcU*#&kS-ksG+@23Ab3-pZ6? zyw+t6TEc416RtH=Tf!Q`XT`P9RxCJ7Sme$0;D_e^B;D^kOJJ2?Bi)z4=B(RZ_}QVq z;mgDKus*2YnWXcs_4tt$Rh3a_i=88q5g2)`WsV*lo|YnK8N{D)4RQ2L{Bd{g%Hc$2 zs(a|YJ4daD>z(iOqXe2N$6)0tOY>_^2;1EWZxm%2QRxibM&kn|EKOw7pBen% zux15OgkeZCcA;j7FMO+sb1KCfX*j$dSeYt^g#NC9glLYZfP7A|i&U5`EcjMp<`*1^ z1ag_5QZk>KJ0o*=u7p|*pBj0mDA1J15daxy8Uw_=lPU}(Rjy1>Ba}k(@xG^UF@!No zRzZNl?=wYWl`5qx^5;brD;Prewi+UUityq{x4p#8`3nkkEwrYC5sKjY)AT0lVwTB| z`)hcckrJE?EtX52ZCXRf&95QB5v=u>Kda;?iY7KZx4WKzx{kcZRW6R2S8bC z>u<~i&5K%(!nlCom^hw>#CuJgj)LBS`rygHB9kthNjf03{zm;fUEXaA?&p+s7=kAt zOM?^8Nv2# z4xqlH#z+*nNVi^hgTRXIYl^FC>#G{ko8a1?>HDi|%|#XqjavOpl`&~Er)YU>=!nRZ zi!ZK!9+VnY>n_8?<14Y5&6RYQ!i_L1T%zuxJ9K+RPMt97R>K?bDX2t0Daa;5tOY5A zu4An>A~h0d#mqnLXtdEp(>ERd?=VFbtFza@Rap1&zN5qc0Bn7@!@CVdNx1S?1RCyi z$-%47y2Pyo2fW2;jGvl}xzW1Ell$f5mEV(YO&_C2OKnfDyl(1Lt2l@353BPI^4VW_ CCg%qL literal 0 HcmV?d00001 diff --git a/mobile/openapi/lib/model/check_existing_assets_response_dto.dart b/mobile/openapi/lib/model/check_existing_assets_response_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..538482fc1c3580f6f3f783b47da3d56f816c1b9e GIT binary patch literal 3768 zcmds4-*4MC5PtVxaVd&e1{8VC)8NcbgT@(>wlR=q4})O{v_!{jWl|%ldPeI1eRrg2 z%Zq0h-NS$dNNiE}j+bt?)$PL7Z;zw1Ux)X@hpWfoHQd~O7{+itg8R`Ud>mch z-u!-oVkG%CXWI79lQ-urdK6DmX`arMPG_RvJ*Z@5c%JftFSxXEcovINX}ed06-%}< zX<6A+^FJz~QC+eH{wC{$8|NS;35fcXOV78U87Z74qrI7;eo2HzoS!*H*!E z3!sbXc;L3u(f|X=moPr8H38wEXFNgm!?F>10I+jm5?;f&%?w(LYQ~C)T*u+f zw{QL%h@+`FJ$1;v{4VzXL2yD^c;Xi3%*@}ZoFg1(X~Q}qo8%cchUpcN1?xZOMi?g+ zH3Cn94WFN83VfQtD*q*NQMV5lW85&^f8BVPv|Wlnva(Kp4EYMVEV2Q5xHC$keP79$ zkgS08GoE1)Ux+`iFMQg($XK@10h*_!p!ECUfw8Yd}~7Us+Lc z12e4_wdJupLLSdhpiQ(NW9*X{WK|%sh($|b9K9P=D>a{C_c`L~Dnsg-lt`IkA6t>7 z*z$9gQx*Nek=D3$h+J~(WP79`(+DndIEkzXFPsDT8q_6lB1y(Jt+TCNN^sJ5?D|2EI0$Gs6FqBA$d_Jej0XR8S6N;0aJY^-TSCAF11utJ5oz(3sMaTC>{Q#XG8zUtVxKbE>aAt!IJHa zsp;8tKpjRQCM?hWBx-2-D$ukhEIT!+wXC8bqmafPgm2D@oAPzRn)l9;T+-CqTeSe( zXN6{4<90e?G+bKE0wJb@^>Kyl`}+OU@TIBi7tJ1QcES`V10K?r=`Vb&^cFik?y!{-hKP<7Llm;`*J@#S)+{HnGT zc8*8Xg|xdKuF#oRLL2pMj#~*T(@Iaak%D`eMC?1(ngdcN!M&XN-bsKqnr!iy$;n4dF*67f& literal 0 HcmV?d00001 diff --git a/mobile/openapi/test/check_existing_assets_dto_test.dart b/mobile/openapi/test/check_existing_assets_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..df4c51e707342a5be1b382ec350f9bdd362216ba GIT binary patch literal 735 zcma)3OH0E*5WeSEjHk9xjrJtcKH-skKXOnK-lEM;30tYqEt z9}EkH$EN`sK`6scHe7%f2#w>#I9kK=Pod|nr}S%6gjyY)tIhKy4^o?L`0#O??ScYRTuH!okRt}1s{^k zz&UfyndEtv=P>)XsQ#RnSLO9=Q5JAEzbL0rRB%--;Ib;_XaBbZ7Lg~74t%t?y}Osi z)^yedRILlDH9a1{U(?+r0SR~au+X;-9+N%8>RWEg*Ec@^XBd_ZR-Qj`rXtg)F zob8x;boc|pNSw#(3lK$PParFS(0P8$5^n-o}6i5rHvg=;57pf zZfmUW!K6qv!qG1y#*@QLh+S}lACIE&FNDz)KE;yUg;@K0!$IsF1AZEvHw5DbTRrp) zPr4uETho@|_HH6-kP?cVO9k)}!3Wt^;5FQTn{xl5rfF^K6fkln { getAssetWithNoEXIF: jest.fn(), getAssetWithNoThumbnail: jest.fn(), getAssetWithNoSmartInfo: jest.fn(), + getExistingAssets: jest.fn(), }; sut = new AlbumService(albumRepositoryMock, assetRepositoryMock); diff --git a/server/apps/immich/src/api-v1/asset/asset-repository.ts b/server/apps/immich/src/api-v1/asset/asset-repository.ts index 089819af37..ed6c05a09c 100644 --- a/server/apps/immich/src/api-v1/asset/asset-repository.ts +++ b/server/apps/immich/src/api-v1/asset/asset-repository.ts @@ -10,6 +10,9 @@ import { AssetCountByTimeBucket } from './response-dto/asset-count-by-time-group import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto'; import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto'; +import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; +import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto'; +import { In } from 'typeorm/find-options/operator/In'; export interface IAssetRepository { create( @@ -32,6 +35,7 @@ export interface IAssetRepository { getAssetWithNoThumbnail(): Promise; getAssetWithNoEXIF(): Promise; getAssetWithNoSmartInfo(): Promise; + getExistingAssets(userId: string, checkDuplicateAssetDto: CheckExistingAssetsDto): Promise; } export const ASSET_REPOSITORY = 'ASSET_REPOSITORY'; @@ -279,4 +283,17 @@ export class AssetRepository implements IAssetRepository { relations: ['exifInfo'], }); } + + async getExistingAssets(userId: string, checkDuplicateAssetDto: CheckExistingAssetsDto): Promise { + const existingAssets = await this.assetRepository.find({ + select: {deviceAssetId: true}, + where: { + deviceAssetId: In(checkDuplicateAssetDto.deviceAssetIds), + deviceId: checkDuplicateAssetDto.deviceId, + userId, + }, + }); + return new CheckExistingAssetsResponseDto(existingAssets.map(a => a.deviceAssetId)); + } + } diff --git a/server/apps/immich/src/api-v1/asset/asset.controller.ts b/server/apps/immich/src/api-v1/asset/asset.controller.ts index 956ccc6d45..88479cbf7e 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -48,6 +48,8 @@ import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-buck import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; import { QueryFailedError } from 'typeorm'; import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto'; +import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; +import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto'; @UseGuards(JwtAuthGuard) @ApiBearerAuth() @@ -74,6 +76,7 @@ export class AssetController { @GetAuthUser() authUser: AuthUserDto, @UploadedFile() file: Express.Multer.File, @Body(ValidationPipe) assetInfo: CreateAssetDto, + @Response({ passthrough: true }) res: Res, ): Promise { const checksum = await this.assetService.calculateChecksum(file.path); @@ -111,6 +114,7 @@ export class AssetController { if (err instanceof QueryFailedError && (err as any).constraint === 'UQ_userid_checksum') { const existedAsset = await this.assetService.getAssetByChecksum(authUser.id, checksum); + res.status(200); // normal POST is 201. we use 200 to indicate the asset already exists return new AssetFileUploadResponseDto(existedAsset.id); } @@ -254,4 +258,16 @@ export class AssetController { ): Promise { return await this.assetService.checkDuplicatedAsset(authUser, checkDuplicateAssetDto); } + + /** + * Checks if multiple assets exist on the server and returns all existing - used by background backup + */ + @Post('/exist') + @HttpCode(200) + async checkExistingAssets( + @GetAuthUser() authUser: AuthUserDto, + @Body(ValidationPipe) checkExistingAssetsDto: CheckExistingAssetsDto, + ): Promise { + return await this.assetService.checkExistingAssets(authUser, checkExistingAssetsDto); + } } diff --git a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts index 89305dcd4b..282e63fc8c 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts @@ -110,6 +110,7 @@ describe('AssetService', () => { getAssetWithNoEXIF: jest.fn(), getAssetWithNoThumbnail: jest.fn(), getAssetWithNoSmartInfo: jest.fn(), + getExistingAssets: jest.fn(), }; sui = new AssetService(assetRepositoryMock, a); diff --git a/server/apps/immich/src/api-v1/asset/asset.service.ts b/server/apps/immich/src/api-v1/asset/asset.service.ts index bad4a8b14b..b5a0ad4339 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -37,6 +37,8 @@ import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-buck import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto'; import { timeUtils } from '@app/common/utils'; +import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; +import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto'; const fileInfo = promisify(stat); @@ -466,6 +468,13 @@ export class AssetService { return new CheckDuplicateAssetResponseDto(isDuplicated, res?.id); } + async checkExistingAssets( + authUser: AuthUserDto, + checkExistingAssetsDto: CheckExistingAssetsDto, + ): Promise { + return this._assetRepository.getExistingAssets(authUser.id, checkExistingAssetsDto); + } + async getAssetCountByTimeBucket( authUser: AuthUserDto, getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, diff --git a/server/apps/immich/src/api-v1/asset/dto/check-existing-assets.dto.ts b/server/apps/immich/src/api-v1/asset/dto/check-existing-assets.dto.ts new file mode 100644 index 0000000000..6101afcdf7 --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/dto/check-existing-assets.dto.ts @@ -0,0 +1,9 @@ +import { IsNotEmpty } from 'class-validator'; + +export class CheckExistingAssetsDto { + @IsNotEmpty() + deviceAssetIds!: string[]; + + @IsNotEmpty() + deviceId!: string; +} \ No newline at end of file diff --git a/server/apps/immich/src/api-v1/asset/response-dto/check-existing-assets-response.dto.ts b/server/apps/immich/src/api-v1/asset/response-dto/check-existing-assets-response.dto.ts new file mode 100644 index 0000000000..31d2d0daab --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/response-dto/check-existing-assets-response.dto.ts @@ -0,0 +1,7 @@ +export class CheckExistingAssetsResponseDto { + constructor(existingIds: string[]) { + this.existingIds = existingIds; + } + existingIds: string[]; + } + \ No newline at end of file diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 95bf47651f..abf6c7f8b2 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1 +1 @@ -{"openapi":"3.0.0","paths":{"/user":{"get":{"operationId":"getAllUsers","parameters":[{"name":"isAll","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}}}}}},"tags":["User"],"security":[{"bearer":[]}]},"post":{"operationId":"createUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]},"put":{"operationId":"updateUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/info/{userId}":{"get":{"operationId":"getUserById","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"]}},"/user/me":{"get":{"operationId":"getMyUserInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/count":{"get":{"operationId":"getUserCount","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCountResponseDto"}}}}},"tags":["User"]}},"/user/profile-image":{"post":{"operationId":"createProfileImage","parameters":[],"requestBody":{"required":true,"description":"A new avatar for the user","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateProfileImageDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProfileImageResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image/{userId}":{"get":{"operationId":"getProfileImage","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["User"]}},"/asset/upload":{"post":{"operationId":"uploadFile","parameters":[],"requestBody":{"required":true,"description":"Asset Upload Information","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/AssetFileUploadDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetFileUploadResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/download":{"get":{"operationId":"downloadFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file":{"get":{"operationId":"serveFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/thumbnail/{assetId}":{"get":{"operationId":"getAssetThumbnail","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}},{"name":"format","required":false,"in":"query","schema":{"$ref":"#/components/schemas/ThumbnailFormat"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-objects":{"get":{"operationId":"getCuratedObjects","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedObjectsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-locations":{"get":{"operationId":"getCuratedLocations","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedLocationsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search-terms":{"get":{"operationId":"getAssetSearchTerms","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search":{"post":{"operationId":"searchAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchAssetDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/count-by-time-bucket":{"post":{"operationId":"getAssetCountByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetCountByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByTimeBucketResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/count-by-user-id":{"get":{"operationId":"getAssetCountByUserId","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByUserIdResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DeleteAssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/time-bucket":{"post":{"operationId":"getAssetByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/{deviceId}":{"get":{"operationId":"getUserAssetsByDeviceId","summary":"","description":"Get all asset of a device that are in the database, ID only.","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/assetById/{assetId}":{"get":{"operationId":"getAssetById","summary":"","description":"Get a single asset's information","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/check":{"post":{"operationId":"checkDuplicateAsset","summary":"","description":"Check duplicated asset before uploading - for Web upload used","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/auth/login":{"post":{"operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentialDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["Authentication"]}},"/auth/admin-sign-up":{"post":{"operationId":"adminSignUp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignupResponseDto"}}}},"400":{"description":"The server already has an admin"}},"tags":["Authentication"]}},"/auth/validateToken":{"post":{"operationId":"validateAccessToken","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAccessTokenResponseDto"}}}}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/auth/logout":{"post":{"operationId":"logout","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutResponseDto"}}}}},"tags":["Authentication"]}},"/device-info":{"post":{"operationId":"createDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeviceInfoDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeviceInfoDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]}},"/server-info":{"get":{"operationId":"getServerInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerInfoResponseDto"}}}}},"tags":["Server Info"]}},"/server-info/ping":{"get":{"operationId":"pingServer","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerPingResponse"}}}}},"tags":["Server Info"]}},"/server-info/version":{"get":{"operationId":"getServerVersion","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerVersionReponseDto"}}}}},"tags":["Server Info"]}},"/server-info/stats":{"get":{"operationId":"getStats","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerStatsResponseDto"}}}}},"tags":["Server Info"]}},"/album/count-by-user-id":{"get":{"operationId":"getAlbumCountByUserId","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumCountResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album":{"post":{"operationId":"createAlbum","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlbumDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"get":{"operationId":"getAllAlbums","parameters":[{"name":"shared","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"assetId","required":false,"in":"query","description":"Only returns albums that contain the asset\nIgnores the shared parameter\nundefined: get all albums","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/users":{"put":{"operationId":"addUsersToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddUsersDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/assets":{"put":{"operationId":"addAssetsToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"removeAssetFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}":{"get":{"operationId":"getAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAlbumDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/user/{userId}":{"delete":{"operationId":"removeUserFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}},"/jobs":{"get":{"operationId":"getAllJobsStatus","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllJobStatusResponseDto"}}}}},"tags":["Job"],"security":[{"bearer":[]}]}},"/jobs/{jobId}":{"get":{"operationId":"getJobStatus","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"$ref":"#/components/schemas/JobId"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatusResponseDto"}}}}},"tags":["Job"],"security":[{"bearer":[]}]},"put":{"operationId":"sendJobCommand","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"$ref":"#/components/schemas/JobId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobCommandDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"number"}}}}},"tags":["Job"],"security":[{"bearer":[]}]}}},"info":{"title":"Immich","description":"Immich API","version":"1.17.0","contact":{}},"tags":[],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"bearer":{"scheme":"Bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"}},"schemas":{"UserResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"},"profileImagePath":{"type":"string"},"shouldChangePassword":{"type":"boolean"},"isAdmin":{"type":"boolean"}},"required":["id","email","firstName","lastName","createdAt","profileImagePath","shouldChangePassword","isAdmin"]},"CreateUserDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"UserCountResponseDto":{"type":"object","properties":{"userCount":{"type":"integer"}},"required":["userCount"]},"UpdateUserDto":{"type":"object","properties":{"id":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"isAdmin":{"type":"boolean"},"shouldChangePassword":{"type":"boolean"},"profileImagePath":{"type":"string"}},"required":["id"]},"CreateProfileImageDto":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]},"CreateProfileImageResponseDto":{"type":"object","properties":{"userId":{"type":"string"},"profileImagePath":{"type":"string"}},"required":["userId","profileImagePath"]},"AssetFileUploadDto":{"type":"object","properties":{"assetData":{"type":"string","format":"binary"}},"required":["assetData"]},"AssetFileUploadResponseDto":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},"ThumbnailFormat":{"type":"string","enum":["JPEG","WEBP"]},"CuratedObjectsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","object","resizePath","deviceAssetId","deviceId"]},"CuratedLocationsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"city":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","city","resizePath","deviceAssetId","deviceId"]},"SearchAssetDto":{"type":"object","properties":{"searchTerm":{"type":"string"}},"required":["searchTerm"]},"AssetTypeEnum":{"type":"string","enum":["IMAGE","VIDEO","AUDIO","OTHER"]},"ExifResponseDto":{"type":"object","properties":{"id":{"type":"integer","nullable":true,"default":null,"format":"int64"},"fileSizeInByte":{"type":"integer","nullable":true,"default":null,"format":"int64"},"make":{"type":"string","nullable":true,"default":null},"model":{"type":"string","nullable":true,"default":null},"imageName":{"type":"string","nullable":true,"default":null},"exifImageWidth":{"type":"number","nullable":true,"default":null},"exifImageHeight":{"type":"number","nullable":true,"default":null},"orientation":{"type":"string","nullable":true,"default":null},"dateTimeOriginal":{"format":"date-time","type":"string","nullable":true,"default":null},"modifyDate":{"format":"date-time","type":"string","nullable":true,"default":null},"lensModel":{"type":"string","nullable":true,"default":null},"fNumber":{"type":"number","nullable":true,"default":null},"focalLength":{"type":"number","nullable":true,"default":null},"iso":{"type":"number","nullable":true,"default":null},"exposureTime":{"type":"number","nullable":true,"default":null},"latitude":{"type":"number","nullable":true,"default":null},"longitude":{"type":"number","nullable":true,"default":null},"city":{"type":"string","nullable":true,"default":null},"state":{"type":"string","nullable":true,"default":null},"country":{"type":"string","nullable":true,"default":null}}},"SmartInfoResponseDto":{"type":"object","properties":{"id":{"type":"string"},"tags":{"nullable":true,"type":"array","items":{"type":"string"}},"objects":{"nullable":true,"type":"array","items":{"type":"string"}}}},"AssetResponseDto":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/AssetTypeEnum"},"id":{"type":"string"},"deviceAssetId":{"type":"string"},"ownerId":{"type":"string"},"deviceId":{"type":"string"},"originalPath":{"type":"string"},"resizePath":{"type":"string","nullable":true},"createdAt":{"type":"string"},"modifiedAt":{"type":"string"},"isFavorite":{"type":"boolean"},"mimeType":{"type":"string","nullable":true},"duration":{"type":"string"},"webpPath":{"type":"string","nullable":true},"encodedVideoPath":{"type":"string","nullable":true},"exifInfo":{"$ref":"#/components/schemas/ExifResponseDto"},"smartInfo":{"$ref":"#/components/schemas/SmartInfoResponseDto"}},"required":["type","id","deviceAssetId","ownerId","deviceId","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath","encodedVideoPath"]},"TimeGroupEnum":{"type":"string","enum":["day","month"]},"GetAssetCountByTimeBucketDto":{"type":"object","properties":{"timeGroup":{"$ref":"#/components/schemas/TimeGroupEnum"}},"required":["timeGroup"]},"AssetCountByTimeBucket":{"type":"object","properties":{"timeBucket":{"type":"string"},"count":{"type":"integer"}},"required":["timeBucket","count"]},"AssetCountByTimeBucketResponseDto":{"type":"object","properties":{"totalCount":{"type":"integer"},"buckets":{"type":"array","items":{"$ref":"#/components/schemas/AssetCountByTimeBucket"}}},"required":["totalCount","buckets"]},"AssetCountByUserIdResponseDto":{"type":"object","properties":{"photos":{"type":"integer"},"videos":{"type":"integer"}},"required":["photos","videos"]},"GetAssetByTimeBucketDto":{"type":"object","properties":{"timeBucket":{"title":"Array of date time buckets","example":["2015-06-01T00:00:00.000Z","2016-02-01T00:00:00.000Z","2016-03-01T00:00:00.000Z"],"type":"array","items":{"type":"string"}}},"required":["timeBucket"]},"DeleteAssetDto":{"type":"object","properties":{"ids":{"title":"Array of asset IDs to delete","example":["bf973405-3f2a-48d2-a687-2ed4167164be","dd41870b-5d00-46d2-924e-1d8489a0aa0f","fad77c3f-deef-4e7e-9608-14c1aa4e559a"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"DeleteAssetStatus":{"type":"string","enum":["SUCCESS","FAILED"]},"DeleteAssetResponseDto":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/DeleteAssetStatus"},"id":{"type":"string"}},"required":["status","id"]},"CheckDuplicateAssetDto":{"type":"object","properties":{"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["deviceAssetId","deviceId"]},"CheckDuplicateAssetResponseDto":{"type":"object","properties":{"isExist":{"type":"boolean"},"id":{"type":"string"}},"required":["isExist"]},"LoginCredentialDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"}},"required":["email","password"]},"LoginResponseDto":{"type":"object","properties":{"accessToken":{"type":"string","readOnly":true},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true},"firstName":{"type":"string","readOnly":true},"lastName":{"type":"string","readOnly":true},"profileImagePath":{"type":"string","readOnly":true},"isAdmin":{"type":"boolean","readOnly":true},"shouldChangePassword":{"type":"boolean","readOnly":true}},"required":["accessToken","userId","userEmail","firstName","lastName","profileImagePath","isAdmin","shouldChangePassword"]},"SignUpDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"Admin"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"AdminSignupResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"}},"required":["id","email","firstName","lastName","createdAt"]},"ValidateAccessTokenResponseDto":{"type":"object","properties":{"authStatus":{"type":"boolean"}},"required":["authStatus"]},"LogoutResponseDto":{"type":"object","properties":{"successful":{"type":"boolean","readOnly":true}},"required":["successful"]},"DeviceTypeEnum":{"type":"string","enum":["IOS","ANDROID","WEB"]},"CreateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"DeviceInfoResponseDto":{"type":"object","properties":{"id":{"type":"integer"},"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"userId":{"type":"string"},"deviceId":{"type":"string"},"createdAt":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["id","deviceType","userId","deviceId","createdAt","isAutoBackup"]},"UpdateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"ServerInfoResponseDto":{"type":"object","properties":{"diskSizeRaw":{"type":"integer","format":"int64"},"diskUseRaw":{"type":"integer","format":"int64"},"diskAvailableRaw":{"type":"integer","format":"int64"},"diskUsagePercentage":{"type":"number","format":"float"},"diskSize":{"type":"string"},"diskUse":{"type":"string"},"diskAvailable":{"type":"string"}},"required":["diskSizeRaw","diskUseRaw","diskAvailableRaw","diskUsagePercentage","diskSize","diskUse","diskAvailable"]},"ServerPingResponse":{"type":"object","properties":{"res":{"type":"string","readOnly":true,"example":"pong"}},"required":["res"]},"ServerVersionReponseDto":{"type":"object","properties":{"major":{"type":"integer"},"minor":{"type":"integer"},"patch":{"type":"integer"},"build":{"type":"integer"}},"required":["major","minor","patch","build"]},"UsageByUserDto":{"type":"object","properties":{"userId":{"type":"string"},"objects":{"type":"integer"},"videos":{"type":"integer"},"photos":{"type":"integer"},"usageRaw":{"type":"integer","format":"int64"},"usage":{"type":"string"}},"required":["userId","objects","videos","photos","usageRaw","usage"]},"ServerStatsResponseDto":{"type":"object","properties":{"photos":{"type":"integer"},"videos":{"type":"integer"},"objects":{"type":"integer"},"usageRaw":{"type":"integer","format":"int64"},"usage":{"type":"string"},"usageByUser":{"title":"Array of usage for each user","example":[{"photos":1,"videos":1,"objects":1,"diskUsageRaw":1}],"type":"array","items":{"$ref":"#/components/schemas/UsageByUserDto"}}},"required":["photos","videos","objects","usageRaw","usage","usageByUser"]},"AlbumCountResponseDto":{"type":"object","properties":{"owned":{"type":"integer"},"shared":{"type":"integer"},"sharing":{"type":"integer"}},"required":["owned","shared","sharing"]},"CreateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"sharedWithUserIds":{"type":"array","items":{"type":"string"}},"assetIds":{"type":"array","items":{"type":"string"}}},"required":["albumName"]},"AlbumResponseDto":{"type":"object","properties":{"assetCount":{"type":"integer"},"id":{"type":"string"},"ownerId":{"type":"string"},"albumName":{"type":"string"},"createdAt":{"type":"string"},"albumThumbnailAssetId":{"type":"string","nullable":true},"shared":{"type":"boolean"},"sharedUsers":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}},"required":["assetCount","id","ownerId","albumName","createdAt","albumThumbnailAssetId","shared","sharedUsers","assets"]},"AddUsersDto":{"type":"object","properties":{"sharedUserIds":{"type":"array","items":{"type":"string"}}},"required":["sharedUserIds"]},"AddAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"albumThumbnailAssetId":{"type":"string"}}},"JobCounts":{"type":"object","properties":{"active":{"type":"integer"},"completed":{"type":"integer"},"failed":{"type":"integer"},"delayed":{"type":"integer"},"waiting":{"type":"integer"}},"required":["active","completed","failed","delayed","waiting"]},"AllJobStatusResponseDto":{"type":"object","properties":{"thumbnailGenerationQueueCount":{"$ref":"#/components/schemas/JobCounts"},"metadataExtractionQueueCount":{"$ref":"#/components/schemas/JobCounts"},"videoConversionQueueCount":{"$ref":"#/components/schemas/JobCounts"},"machineLearningQueueCount":{"$ref":"#/components/schemas/JobCounts"},"isThumbnailGenerationActive":{"type":"boolean"},"isMetadataExtractionActive":{"type":"boolean"},"isVideoConversionActive":{"type":"boolean"},"isMachineLearningActive":{"type":"boolean"}},"required":["thumbnailGenerationQueueCount","metadataExtractionQueueCount","videoConversionQueueCount","machineLearningQueueCount","isThumbnailGenerationActive","isMetadataExtractionActive","isVideoConversionActive","isMachineLearningActive"]},"JobId":{"type":"string","enum":["thumbnail-generation","metadata-extraction","video-conversion","machine-learning"]},"JobStatusResponseDto":{"type":"object","properties":{"isActive":{"type":"boolean"},"queueCount":{"type":"object"}},"required":["isActive","queueCount"]},"JobCommand":{"type":"string","enum":["start","stop"]},"JobCommandDto":{"type":"object","properties":{"command":{"$ref":"#/components/schemas/JobCommand"}},"required":["command"]}}}} \ No newline at end of file +{"openapi":"3.0.0","paths":{"/user":{"get":{"operationId":"getAllUsers","parameters":[{"name":"isAll","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}}}}}},"tags":["User"],"security":[{"bearer":[]}]},"post":{"operationId":"createUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]},"put":{"operationId":"updateUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/info/{userId}":{"get":{"operationId":"getUserById","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"]}},"/user/me":{"get":{"operationId":"getMyUserInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/count":{"get":{"operationId":"getUserCount","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCountResponseDto"}}}}},"tags":["User"]}},"/user/profile-image":{"post":{"operationId":"createProfileImage","parameters":[],"requestBody":{"required":true,"description":"A new avatar for the user","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateProfileImageDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProfileImageResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image/{userId}":{"get":{"operationId":"getProfileImage","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["User"]}},"/asset/upload":{"post":{"operationId":"uploadFile","parameters":[],"requestBody":{"required":true,"description":"Asset Upload Information","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/AssetFileUploadDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetFileUploadResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/download":{"get":{"operationId":"downloadFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file":{"get":{"operationId":"serveFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/thumbnail/{assetId}":{"get":{"operationId":"getAssetThumbnail","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}},{"name":"format","required":false,"in":"query","schema":{"$ref":"#/components/schemas/ThumbnailFormat"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-objects":{"get":{"operationId":"getCuratedObjects","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedObjectsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-locations":{"get":{"operationId":"getCuratedLocations","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedLocationsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search-terms":{"get":{"operationId":"getAssetSearchTerms","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search":{"post":{"operationId":"searchAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchAssetDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/count-by-time-bucket":{"post":{"operationId":"getAssetCountByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetCountByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByTimeBucketResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/count-by-user-id":{"get":{"operationId":"getAssetCountByUserId","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByUserIdResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DeleteAssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/time-bucket":{"post":{"operationId":"getAssetByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/{deviceId}":{"get":{"operationId":"getUserAssetsByDeviceId","summary":"","description":"Get all asset of a device that are in the database, ID only.","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/assetById/{assetId}":{"get":{"operationId":"getAssetById","summary":"","description":"Get a single asset's information","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/check":{"post":{"operationId":"checkDuplicateAsset","summary":"","description":"Check duplicated asset before uploading - for Web upload used","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/exist":{"post":{"operationId":"checkExistingAssets","summary":"","description":"Checks if multiple assets exist on the server and returns all existing - used by background backup","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckExistingAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckExistingAssetsResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/auth/login":{"post":{"operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentialDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["Authentication"]}},"/auth/admin-sign-up":{"post":{"operationId":"adminSignUp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignupResponseDto"}}}},"400":{"description":"The server already has an admin"}},"tags":["Authentication"]}},"/auth/validateToken":{"post":{"operationId":"validateAccessToken","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAccessTokenResponseDto"}}}}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/auth/logout":{"post":{"operationId":"logout","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutResponseDto"}}}}},"tags":["Authentication"]}},"/device-info":{"post":{"operationId":"createDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeviceInfoDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeviceInfoDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]}},"/server-info":{"get":{"operationId":"getServerInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerInfoResponseDto"}}}}},"tags":["Server Info"]}},"/server-info/ping":{"get":{"operationId":"pingServer","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerPingResponse"}}}}},"tags":["Server Info"]}},"/server-info/version":{"get":{"operationId":"getServerVersion","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerVersionReponseDto"}}}}},"tags":["Server Info"]}},"/server-info/stats":{"get":{"operationId":"getStats","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerStatsResponseDto"}}}}},"tags":["Server Info"]}},"/album/count-by-user-id":{"get":{"operationId":"getAlbumCountByUserId","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumCountResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album":{"post":{"operationId":"createAlbum","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlbumDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"get":{"operationId":"getAllAlbums","parameters":[{"name":"shared","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"assetId","required":false,"in":"query","description":"Only returns albums that contain the asset\nIgnores the shared parameter\nundefined: get all albums","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/users":{"put":{"operationId":"addUsersToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddUsersDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/assets":{"put":{"operationId":"addAssetsToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"removeAssetFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}":{"get":{"operationId":"getAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAlbumDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/user/{userId}":{"delete":{"operationId":"removeUserFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}},"/jobs":{"get":{"operationId":"getAllJobsStatus","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllJobStatusResponseDto"}}}}},"tags":["Job"],"security":[{"bearer":[]}]}},"/jobs/{jobId}":{"get":{"operationId":"getJobStatus","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"$ref":"#/components/schemas/JobId"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatusResponseDto"}}}}},"tags":["Job"],"security":[{"bearer":[]}]},"put":{"operationId":"sendJobCommand","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"$ref":"#/components/schemas/JobId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobCommandDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"number"}}}}},"tags":["Job"],"security":[{"bearer":[]}]}}},"info":{"title":"Immich","description":"Immich API","version":"1.17.0","contact":{}},"tags":[],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"bearer":{"scheme":"Bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"}},"schemas":{"UserResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"},"profileImagePath":{"type":"string"},"shouldChangePassword":{"type":"boolean"},"isAdmin":{"type":"boolean"}},"required":["id","email","firstName","lastName","createdAt","profileImagePath","shouldChangePassword","isAdmin"]},"CreateUserDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"UserCountResponseDto":{"type":"object","properties":{"userCount":{"type":"integer"}},"required":["userCount"]},"UpdateUserDto":{"type":"object","properties":{"id":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"isAdmin":{"type":"boolean"},"shouldChangePassword":{"type":"boolean"},"profileImagePath":{"type":"string"}},"required":["id"]},"CreateProfileImageDto":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]},"CreateProfileImageResponseDto":{"type":"object","properties":{"userId":{"type":"string"},"profileImagePath":{"type":"string"}},"required":["userId","profileImagePath"]},"AssetFileUploadDto":{"type":"object","properties":{"assetData":{"type":"string","format":"binary"}},"required":["assetData"]},"AssetFileUploadResponseDto":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},"ThumbnailFormat":{"type":"string","enum":["JPEG","WEBP"]},"CuratedObjectsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","object","resizePath","deviceAssetId","deviceId"]},"CuratedLocationsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"city":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","city","resizePath","deviceAssetId","deviceId"]},"SearchAssetDto":{"type":"object","properties":{"searchTerm":{"type":"string"}},"required":["searchTerm"]},"AssetTypeEnum":{"type":"string","enum":["IMAGE","VIDEO","AUDIO","OTHER"]},"ExifResponseDto":{"type":"object","properties":{"id":{"type":"integer","nullable":true,"default":null,"format":"int64"},"fileSizeInByte":{"type":"integer","nullable":true,"default":null,"format":"int64"},"make":{"type":"string","nullable":true,"default":null},"model":{"type":"string","nullable":true,"default":null},"imageName":{"type":"string","nullable":true,"default":null},"exifImageWidth":{"type":"number","nullable":true,"default":null},"exifImageHeight":{"type":"number","nullable":true,"default":null},"orientation":{"type":"string","nullable":true,"default":null},"dateTimeOriginal":{"format":"date-time","type":"string","nullable":true,"default":null},"modifyDate":{"format":"date-time","type":"string","nullable":true,"default":null},"lensModel":{"type":"string","nullable":true,"default":null},"fNumber":{"type":"number","nullable":true,"default":null},"focalLength":{"type":"number","nullable":true,"default":null},"iso":{"type":"number","nullable":true,"default":null},"exposureTime":{"type":"number","nullable":true,"default":null},"latitude":{"type":"number","nullable":true,"default":null},"longitude":{"type":"number","nullable":true,"default":null},"city":{"type":"string","nullable":true,"default":null},"state":{"type":"string","nullable":true,"default":null},"country":{"type":"string","nullable":true,"default":null}}},"SmartInfoResponseDto":{"type":"object","properties":{"id":{"type":"string"},"tags":{"nullable":true,"type":"array","items":{"type":"string"}},"objects":{"nullable":true,"type":"array","items":{"type":"string"}}}},"AssetResponseDto":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/AssetTypeEnum"},"id":{"type":"string"},"deviceAssetId":{"type":"string"},"ownerId":{"type":"string"},"deviceId":{"type":"string"},"originalPath":{"type":"string"},"resizePath":{"type":"string","nullable":true},"createdAt":{"type":"string"},"modifiedAt":{"type":"string"},"isFavorite":{"type":"boolean"},"mimeType":{"type":"string","nullable":true},"duration":{"type":"string"},"webpPath":{"type":"string","nullable":true},"encodedVideoPath":{"type":"string","nullable":true},"exifInfo":{"$ref":"#/components/schemas/ExifResponseDto"},"smartInfo":{"$ref":"#/components/schemas/SmartInfoResponseDto"}},"required":["type","id","deviceAssetId","ownerId","deviceId","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath","encodedVideoPath"]},"TimeGroupEnum":{"type":"string","enum":["day","month"]},"GetAssetCountByTimeBucketDto":{"type":"object","properties":{"timeGroup":{"$ref":"#/components/schemas/TimeGroupEnum"}},"required":["timeGroup"]},"AssetCountByTimeBucket":{"type":"object","properties":{"timeBucket":{"type":"string"},"count":{"type":"integer"}},"required":["timeBucket","count"]},"AssetCountByTimeBucketResponseDto":{"type":"object","properties":{"totalCount":{"type":"integer"},"buckets":{"type":"array","items":{"$ref":"#/components/schemas/AssetCountByTimeBucket"}}},"required":["totalCount","buckets"]},"AssetCountByUserIdResponseDto":{"type":"object","properties":{"photos":{"type":"integer"},"videos":{"type":"integer"}},"required":["photos","videos"]},"GetAssetByTimeBucketDto":{"type":"object","properties":{"timeBucket":{"title":"Array of date time buckets","example":["2015-06-01T00:00:00.000Z","2016-02-01T00:00:00.000Z","2016-03-01T00:00:00.000Z"],"type":"array","items":{"type":"string"}}},"required":["timeBucket"]},"DeleteAssetDto":{"type":"object","properties":{"ids":{"title":"Array of asset IDs to delete","example":["bf973405-3f2a-48d2-a687-2ed4167164be","dd41870b-5d00-46d2-924e-1d8489a0aa0f","fad77c3f-deef-4e7e-9608-14c1aa4e559a"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"DeleteAssetStatus":{"type":"string","enum":["SUCCESS","FAILED"]},"DeleteAssetResponseDto":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/DeleteAssetStatus"},"id":{"type":"string"}},"required":["status","id"]},"CheckDuplicateAssetDto":{"type":"object","properties":{"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["deviceAssetId","deviceId"]},"CheckDuplicateAssetResponseDto":{"type":"object","properties":{"isExist":{"type":"boolean"},"id":{"type":"string"}},"required":["isExist"]},"CheckExistingAssetsDto":{"type":"object","properties":{"deviceAssetIds":{"type":"array","items":{"type":"string"}},"deviceId":{"type":"string"}},"required":["deviceAssetIds","deviceId"]},"CheckExistingAssetsResponseDto":{"type":"object","properties":{"existingIds":{"type":"array","items":{"type":"string"}}},"required":["existingIds"]},"LoginCredentialDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"}},"required":["email","password"]},"LoginResponseDto":{"type":"object","properties":{"accessToken":{"type":"string","readOnly":true},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true},"firstName":{"type":"string","readOnly":true},"lastName":{"type":"string","readOnly":true},"profileImagePath":{"type":"string","readOnly":true},"isAdmin":{"type":"boolean","readOnly":true},"shouldChangePassword":{"type":"boolean","readOnly":true}},"required":["accessToken","userId","userEmail","firstName","lastName","profileImagePath","isAdmin","shouldChangePassword"]},"SignUpDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"Admin"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"AdminSignupResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"}},"required":["id","email","firstName","lastName","createdAt"]},"ValidateAccessTokenResponseDto":{"type":"object","properties":{"authStatus":{"type":"boolean"}},"required":["authStatus"]},"LogoutResponseDto":{"type":"object","properties":{"successful":{"type":"boolean","readOnly":true}},"required":["successful"]},"DeviceTypeEnum":{"type":"string","enum":["IOS","ANDROID","WEB"]},"CreateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"DeviceInfoResponseDto":{"type":"object","properties":{"id":{"type":"integer"},"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"userId":{"type":"string"},"deviceId":{"type":"string"},"createdAt":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["id","deviceType","userId","deviceId","createdAt","isAutoBackup"]},"UpdateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"ServerInfoResponseDto":{"type":"object","properties":{"diskSizeRaw":{"type":"integer","format":"int64"},"diskUseRaw":{"type":"integer","format":"int64"},"diskAvailableRaw":{"type":"integer","format":"int64"},"diskUsagePercentage":{"type":"number","format":"float"},"diskSize":{"type":"string"},"diskUse":{"type":"string"},"diskAvailable":{"type":"string"}},"required":["diskSizeRaw","diskUseRaw","diskAvailableRaw","diskUsagePercentage","diskSize","diskUse","diskAvailable"]},"ServerPingResponse":{"type":"object","properties":{"res":{"type":"string","readOnly":true,"example":"pong"}},"required":["res"]},"ServerVersionReponseDto":{"type":"object","properties":{"major":{"type":"integer"},"minor":{"type":"integer"},"patch":{"type":"integer"},"build":{"type":"integer"}},"required":["major","minor","patch","build"]},"UsageByUserDto":{"type":"object","properties":{"userId":{"type":"string"},"objects":{"type":"integer"},"videos":{"type":"integer"},"photos":{"type":"integer"},"usageRaw":{"type":"integer","format":"int64"},"usage":{"type":"string"}},"required":["userId","objects","videos","photos","usageRaw","usage"]},"ServerStatsResponseDto":{"type":"object","properties":{"photos":{"type":"integer"},"videos":{"type":"integer"},"objects":{"type":"integer"},"usageRaw":{"type":"integer","format":"int64"},"usage":{"type":"string"},"usageByUser":{"title":"Array of usage for each user","example":[{"photos":1,"videos":1,"objects":1,"diskUsageRaw":1}],"type":"array","items":{"$ref":"#/components/schemas/UsageByUserDto"}}},"required":["photos","videos","objects","usageRaw","usage","usageByUser"]},"AlbumCountResponseDto":{"type":"object","properties":{"owned":{"type":"integer"},"shared":{"type":"integer"},"sharing":{"type":"integer"}},"required":["owned","shared","sharing"]},"CreateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"sharedWithUserIds":{"type":"array","items":{"type":"string"}},"assetIds":{"type":"array","items":{"type":"string"}}},"required":["albumName"]},"AlbumResponseDto":{"type":"object","properties":{"assetCount":{"type":"integer"},"id":{"type":"string"},"ownerId":{"type":"string"},"albumName":{"type":"string"},"createdAt":{"type":"string"},"albumThumbnailAssetId":{"type":"string","nullable":true},"shared":{"type":"boolean"},"sharedUsers":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}},"required":["assetCount","id","ownerId","albumName","createdAt","albumThumbnailAssetId","shared","sharedUsers","assets"]},"AddUsersDto":{"type":"object","properties":{"sharedUserIds":{"type":"array","items":{"type":"string"}}},"required":["sharedUserIds"]},"AddAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"albumThumbnailAssetId":{"type":"string"}}},"JobCounts":{"type":"object","properties":{"active":{"type":"integer"},"completed":{"type":"integer"},"failed":{"type":"integer"},"delayed":{"type":"integer"},"waiting":{"type":"integer"}},"required":["active","completed","failed","delayed","waiting"]},"AllJobStatusResponseDto":{"type":"object","properties":{"thumbnailGenerationQueueCount":{"$ref":"#/components/schemas/JobCounts"},"metadataExtractionQueueCount":{"$ref":"#/components/schemas/JobCounts"},"videoConversionQueueCount":{"$ref":"#/components/schemas/JobCounts"},"machineLearningQueueCount":{"$ref":"#/components/schemas/JobCounts"},"isThumbnailGenerationActive":{"type":"boolean"},"isMetadataExtractionActive":{"type":"boolean"},"isVideoConversionActive":{"type":"boolean"},"isMachineLearningActive":{"type":"boolean"}},"required":["thumbnailGenerationQueueCount","metadataExtractionQueueCount","videoConversionQueueCount","machineLearningQueueCount","isThumbnailGenerationActive","isMetadataExtractionActive","isVideoConversionActive","isMachineLearningActive"]},"JobId":{"type":"string","enum":["thumbnail-generation","metadata-extraction","video-conversion","machine-learning"]},"JobStatusResponseDto":{"type":"object","properties":{"isActive":{"type":"boolean"},"queueCount":{"type":"object"}},"required":["isActive","queueCount"]},"JobCommand":{"type":"string","enum":["start","stop"]},"JobCommandDto":{"type":"object","properties":{"command":{"$ref":"#/components/schemas/JobCommand"}},"required":["command"]}}}} \ No newline at end of file diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 5f43f44947..ebf1c791e2 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -452,6 +452,38 @@ export interface CheckDuplicateAssetResponseDto { */ 'id'?: string; } +/** + * + * @export + * @interface CheckExistingAssetsDto + */ +export interface CheckExistingAssetsDto { + /** + * + * @type {Array} + * @memberof CheckExistingAssetsDto + */ + 'deviceAssetIds': Array; + /** + * + * @type {string} + * @memberof CheckExistingAssetsDto + */ + 'deviceId': string; +} +/** + * + * @export + * @interface CheckExistingAssetsResponseDto + */ +export interface CheckExistingAssetsResponseDto { + /** + * + * @type {Array} + * @memberof CheckExistingAssetsResponseDto + */ + 'existingIds': Array; +} /** * * @export @@ -2334,6 +2366,46 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, + /** + * Checks if multiple assets exist on the server and returns all existing - used by background backup + * @summary + * @param {CheckExistingAssetsDto} checkExistingAssetsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + checkExistingAssets: async (checkExistingAssetsDto: CheckExistingAssetsDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'checkExistingAssetsDto' is not null or undefined + assertParamExists('checkExistingAssets', 'checkExistingAssetsDto', checkExistingAssetsDto) + const localVarPath = `/asset/exist`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(checkExistingAssetsDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {DeleteAssetDto} deleteAssetDto @@ -2953,6 +3025,17 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.checkDuplicateAsset(checkDuplicateAssetDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * Checks if multiple assets exist on the server and returns all existing - used by background backup + * @summary + * @param {CheckExistingAssetsDto} checkExistingAssetsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async checkExistingAssets(checkExistingAssetsDto: CheckExistingAssetsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.checkExistingAssets(checkExistingAssetsDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {DeleteAssetDto} deleteAssetDto @@ -3128,6 +3211,16 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath checkDuplicateAsset(checkDuplicateAssetDto: CheckDuplicateAssetDto, options?: any): AxiosPromise { return localVarFp.checkDuplicateAsset(checkDuplicateAssetDto, options).then((request) => request(axios, basePath)); }, + /** + * Checks if multiple assets exist on the server and returns all existing - used by background backup + * @summary + * @param {CheckExistingAssetsDto} checkExistingAssetsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + checkExistingAssets(checkExistingAssetsDto: CheckExistingAssetsDto, options?: any): AxiosPromise { + return localVarFp.checkExistingAssets(checkExistingAssetsDto, options).then((request) => request(axios, basePath)); + }, /** * * @param {DeleteAssetDto} deleteAssetDto @@ -3290,6 +3383,18 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).checkDuplicateAsset(checkDuplicateAssetDto, options).then((request) => request(this.axios, this.basePath)); } + /** + * Checks if multiple assets exist on the server and returns all existing - used by background backup + * @summary + * @param {CheckExistingAssetsDto} checkExistingAssetsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public checkExistingAssets(checkExistingAssetsDto: CheckExistingAssetsDto, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).checkExistingAssets(checkExistingAssetsDto, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {DeleteAssetDto} deleteAssetDto diff --git a/web/src/lib/utils/file-uploader.ts b/web/src/lib/utils/file-uploader.ts index 742269bd97..043ff93197 100644 --- a/web/src/lib/utils/file-uploader.ts +++ b/web/src/lib/utils/file-uploader.ts @@ -200,12 +200,12 @@ async function fileUploader(asset: File, uploadType: UploadType) { } // TODO: This should have a proper type // eslint-disable-next-line @typescript-eslint/no-explicit-any -function handleUploadError(asset: File, respBody?: any) { - const extraMsg = respBody ? ' ' + respBody.message : ''; +function handleUploadError(asset: File, respBody: any, extraMessage?: string) { + const extraMsg = respBody ? ' ' + respBody?.message : ''; notificationController.show({ type: NotificationType.Error, - message: `Cannot upload file ${asset.name}!${extraMsg}`, + message: `Cannot upload file ${asset.name} ${extraMsg}${extraMessage}`, timeout: 5000 }); }