You've already forked immich
							
							
				mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 00:18:28 +02:00 
			
		
		
		
	fix(server): Server freezes when getting statistic (#994)
* fix(server): Server freezes when getting statistic * remove dead code
This commit is contained in:
		| @@ -9,7 +9,6 @@ import 'package:openapi/api.dart'; | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **userId** | **String** |  |  | ||||
| **objects** | **int** |  |  | ||||
| **videos** | **int** |  |  | ||||
| **photos** | **int** |  |  | ||||
| **usageRaw** | **int** |  |  | ||||
|   | ||||
| @@ -43,51 +43,48 @@ class AlbumResponseDto { | ||||
|   List<AssetResponseDto> assets; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) => | ||||
|       identical(this, other) || | ||||
|       other is AlbumResponseDto && | ||||
|           other.assetCount == assetCount && | ||||
|           other.id == id && | ||||
|           other.ownerId == ownerId && | ||||
|           other.albumName == albumName && | ||||
|           other.createdAt == createdAt && | ||||
|           other.albumThumbnailAssetId == albumThumbnailAssetId && | ||||
|           other.shared == shared && | ||||
|           other.sharedUsers == sharedUsers && | ||||
|           other.assets == assets; | ||||
|   bool operator ==(Object other) => identical(this, other) || other is AlbumResponseDto && | ||||
|      other.assetCount == assetCount && | ||||
|      other.id == id && | ||||
|      other.ownerId == ownerId && | ||||
|      other.albumName == albumName && | ||||
|      other.createdAt == createdAt && | ||||
|      other.albumThumbnailAssetId == albumThumbnailAssetId && | ||||
|      other.shared == shared && | ||||
|      other.sharedUsers == sharedUsers && | ||||
|      other.assets == assets; | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => | ||||
|       // ignore: unnecessary_parenthesis | ||||
|       (assetCount.hashCode) + | ||||
|       (id.hashCode) + | ||||
|       (ownerId.hashCode) + | ||||
|       (albumName.hashCode) + | ||||
|       (createdAt.hashCode) + | ||||
|       (albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) + | ||||
|       (shared.hashCode) + | ||||
|       (sharedUsers.hashCode) + | ||||
|       (assets.hashCode); | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (assetCount.hashCode) + | ||||
|     (id.hashCode) + | ||||
|     (ownerId.hashCode) + | ||||
|     (albumName.hashCode) + | ||||
|     (createdAt.hashCode) + | ||||
|     (albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) + | ||||
|     (shared.hashCode) + | ||||
|     (sharedUsers.hashCode) + | ||||
|     (assets.hashCode); | ||||
|  | ||||
|   @override | ||||
|   String toString() => | ||||
|       'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]'; | ||||
|   String toString() => 'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]'; | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final _json = <String, dynamic>{}; | ||||
|     _json[r'assetCount'] = assetCount; | ||||
|     _json[r'id'] = id; | ||||
|     _json[r'ownerId'] = ownerId; | ||||
|     _json[r'albumName'] = albumName; | ||||
|     _json[r'createdAt'] = createdAt; | ||||
|       _json[r'assetCount'] = assetCount; | ||||
|       _json[r'id'] = id; | ||||
|       _json[r'ownerId'] = ownerId; | ||||
|       _json[r'albumName'] = albumName; | ||||
|       _json[r'createdAt'] = createdAt; | ||||
|     if (albumThumbnailAssetId != null) { | ||||
|       _json[r'albumThumbnailAssetId'] = albumThumbnailAssetId; | ||||
|     } else { | ||||
|       _json[r'albumThumbnailAssetId'] = null; | ||||
|     } | ||||
|     _json[r'shared'] = shared; | ||||
|     _json[r'sharedUsers'] = sharedUsers; | ||||
|     _json[r'assets'] = assets; | ||||
|       _json[r'shared'] = shared; | ||||
|       _json[r'sharedUsers'] = sharedUsers; | ||||
|       _json[r'assets'] = assets; | ||||
|     return _json; | ||||
|   } | ||||
|  | ||||
| @@ -101,13 +98,13 @@ class AlbumResponseDto { | ||||
|       // Ensure that the map contains the required keys. | ||||
|       // Note 1: the values aren't checked for validity beyond being non-null. | ||||
|       // Note 2: this code is stripped in release mode! | ||||
|       // assert(() { | ||||
|       //   requiredKeys.forEach((key) { | ||||
|       //     assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.'); | ||||
|       //     assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.'); | ||||
|       //   }); | ||||
|       //   return true; | ||||
|       // }()); | ||||
|       assert(() { | ||||
|         requiredKeys.forEach((key) { | ||||
|           assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.'); | ||||
|           assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.'); | ||||
|         }); | ||||
|         return true; | ||||
|       }()); | ||||
|  | ||||
|       return AlbumResponseDto( | ||||
|         assetCount: mapValueOfType<int>(json, r'assetCount')!, | ||||
| @@ -115,8 +112,7 @@ class AlbumResponseDto { | ||||
|         ownerId: mapValueOfType<String>(json, r'ownerId')!, | ||||
|         albumName: mapValueOfType<String>(json, r'albumName')!, | ||||
|         createdAt: mapValueOfType<String>(json, r'createdAt')!, | ||||
|         albumThumbnailAssetId: | ||||
|             mapValueOfType<String>(json, r'albumThumbnailAssetId'), | ||||
|         albumThumbnailAssetId: mapValueOfType<String>(json, r'albumThumbnailAssetId'), | ||||
|         shared: mapValueOfType<bool>(json, r'shared')!, | ||||
|         sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers'])!, | ||||
|         assets: AssetResponseDto.listFromJson(json[r'assets'])!, | ||||
| @@ -125,10 +121,7 @@ class AlbumResponseDto { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   static List<AlbumResponseDto>? listFromJson( | ||||
|     dynamic json, { | ||||
|     bool growable = false, | ||||
|   }) { | ||||
|   static List<AlbumResponseDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <AlbumResponseDto>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
| @@ -156,18 +149,12 @@ class AlbumResponseDto { | ||||
|   } | ||||
|  | ||||
|   // maps a json object with a list of AlbumResponseDto-objects as value to a dart map | ||||
|   static Map<String, List<AlbumResponseDto>> mapListFromJson( | ||||
|     dynamic json, { | ||||
|     bool growable = false, | ||||
|   }) { | ||||
|   static Map<String, List<AlbumResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<AlbumResponseDto>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = AlbumResponseDto.listFromJson( | ||||
|           entry.value, | ||||
|           growable: growable, | ||||
|         ); | ||||
|         final value = AlbumResponseDto.listFromJson(entry.value, growable: growable,); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
| @@ -189,3 +176,4 @@ class AlbumResponseDto { | ||||
|     'assets', | ||||
|   }; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -79,74 +79,71 @@ class AssetResponseDto { | ||||
|   String? livePhotoVideoId; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) => | ||||
|       identical(this, other) || | ||||
|       other is AssetResponseDto && | ||||
|           other.type == type && | ||||
|           other.id == id && | ||||
|           other.deviceAssetId == deviceAssetId && | ||||
|           other.ownerId == ownerId && | ||||
|           other.deviceId == deviceId && | ||||
|           other.originalPath == originalPath && | ||||
|           other.resizePath == resizePath && | ||||
|           other.createdAt == createdAt && | ||||
|           other.modifiedAt == modifiedAt && | ||||
|           other.isFavorite == isFavorite && | ||||
|           other.mimeType == mimeType && | ||||
|           other.duration == duration && | ||||
|           other.webpPath == webpPath && | ||||
|           other.encodedVideoPath == encodedVideoPath && | ||||
|           other.exifInfo == exifInfo && | ||||
|           other.smartInfo == smartInfo && | ||||
|           other.livePhotoVideoId == livePhotoVideoId; | ||||
|   bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto && | ||||
|      other.type == type && | ||||
|      other.id == id && | ||||
|      other.deviceAssetId == deviceAssetId && | ||||
|      other.ownerId == ownerId && | ||||
|      other.deviceId == deviceId && | ||||
|      other.originalPath == originalPath && | ||||
|      other.resizePath == resizePath && | ||||
|      other.createdAt == createdAt && | ||||
|      other.modifiedAt == modifiedAt && | ||||
|      other.isFavorite == isFavorite && | ||||
|      other.mimeType == mimeType && | ||||
|      other.duration == duration && | ||||
|      other.webpPath == webpPath && | ||||
|      other.encodedVideoPath == encodedVideoPath && | ||||
|      other.exifInfo == exifInfo && | ||||
|      other.smartInfo == smartInfo && | ||||
|      other.livePhotoVideoId == livePhotoVideoId; | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => | ||||
|       // ignore: unnecessary_parenthesis | ||||
|       (type.hashCode) + | ||||
|       (id.hashCode) + | ||||
|       (deviceAssetId.hashCode) + | ||||
|       (ownerId.hashCode) + | ||||
|       (deviceId.hashCode) + | ||||
|       (originalPath.hashCode) + | ||||
|       (resizePath == null ? 0 : resizePath!.hashCode) + | ||||
|       (createdAt.hashCode) + | ||||
|       (modifiedAt.hashCode) + | ||||
|       (isFavorite.hashCode) + | ||||
|       (mimeType == null ? 0 : mimeType!.hashCode) + | ||||
|       (duration.hashCode) + | ||||
|       (webpPath == null ? 0 : webpPath!.hashCode) + | ||||
|       (encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) + | ||||
|       (exifInfo == null ? 0 : exifInfo!.hashCode) + | ||||
|       (smartInfo == null ? 0 : smartInfo!.hashCode) + | ||||
|       (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode); | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (type.hashCode) + | ||||
|     (id.hashCode) + | ||||
|     (deviceAssetId.hashCode) + | ||||
|     (ownerId.hashCode) + | ||||
|     (deviceId.hashCode) + | ||||
|     (originalPath.hashCode) + | ||||
|     (resizePath == null ? 0 : resizePath!.hashCode) + | ||||
|     (createdAt.hashCode) + | ||||
|     (modifiedAt.hashCode) + | ||||
|     (isFavorite.hashCode) + | ||||
|     (mimeType == null ? 0 : mimeType!.hashCode) + | ||||
|     (duration.hashCode) + | ||||
|     (webpPath == null ? 0 : webpPath!.hashCode) + | ||||
|     (encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) + | ||||
|     (exifInfo == null ? 0 : exifInfo!.hashCode) + | ||||
|     (smartInfo == null ? 0 : smartInfo!.hashCode) + | ||||
|     (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode); | ||||
|  | ||||
|   @override | ||||
|   String toString() => | ||||
|       'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId]'; | ||||
|   String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId]'; | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final _json = <String, dynamic>{}; | ||||
|     _json[r'type'] = type; | ||||
|     _json[r'id'] = id; | ||||
|     _json[r'deviceAssetId'] = deviceAssetId; | ||||
|     _json[r'ownerId'] = ownerId; | ||||
|     _json[r'deviceId'] = deviceId; | ||||
|     _json[r'originalPath'] = originalPath; | ||||
|       _json[r'type'] = type; | ||||
|       _json[r'id'] = id; | ||||
|       _json[r'deviceAssetId'] = deviceAssetId; | ||||
|       _json[r'ownerId'] = ownerId; | ||||
|       _json[r'deviceId'] = deviceId; | ||||
|       _json[r'originalPath'] = originalPath; | ||||
|     if (resizePath != null) { | ||||
|       _json[r'resizePath'] = resizePath; | ||||
|     } else { | ||||
|       _json[r'resizePath'] = null; | ||||
|     } | ||||
|     _json[r'createdAt'] = createdAt; | ||||
|     _json[r'modifiedAt'] = modifiedAt; | ||||
|     _json[r'isFavorite'] = isFavorite; | ||||
|       _json[r'createdAt'] = createdAt; | ||||
|       _json[r'modifiedAt'] = modifiedAt; | ||||
|       _json[r'isFavorite'] = isFavorite; | ||||
|     if (mimeType != null) { | ||||
|       _json[r'mimeType'] = mimeType; | ||||
|     } else { | ||||
|       _json[r'mimeType'] = null; | ||||
|     } | ||||
|     _json[r'duration'] = duration; | ||||
|       _json[r'duration'] = duration; | ||||
|     if (webpPath != null) { | ||||
|       _json[r'webpPath'] = webpPath; | ||||
|     } else { | ||||
| @@ -185,13 +182,13 @@ class AssetResponseDto { | ||||
|       // Ensure that the map contains the required keys. | ||||
|       // Note 1: the values aren't checked for validity beyond being non-null. | ||||
|       // Note 2: this code is stripped in release mode! | ||||
|       // assert(() { | ||||
|       //   requiredKeys.forEach((key) { | ||||
|       //     assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.'); | ||||
|       //     assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.'); | ||||
|       //   }); | ||||
|       //   return true; | ||||
|       // }()); | ||||
|       assert(() { | ||||
|         requiredKeys.forEach((key) { | ||||
|           assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.'); | ||||
|           assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.'); | ||||
|         }); | ||||
|         return true; | ||||
|       }()); | ||||
|  | ||||
|       return AssetResponseDto( | ||||
|         type: AssetTypeEnum.fromJson(json[r'type'])!, | ||||
| @@ -216,10 +213,7 @@ class AssetResponseDto { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   static List<AssetResponseDto>? listFromJson( | ||||
|     dynamic json, { | ||||
|     bool growable = false, | ||||
|   }) { | ||||
|   static List<AssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <AssetResponseDto>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
| @@ -247,18 +241,12 @@ class AssetResponseDto { | ||||
|   } | ||||
|  | ||||
|   // maps a json object with a list of AssetResponseDto-objects as value to a dart map | ||||
|   static Map<String, List<AssetResponseDto>> mapListFromJson( | ||||
|     dynamic json, { | ||||
|     bool growable = false, | ||||
|   }) { | ||||
|   static Map<String, List<AssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<AssetResponseDto>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = AssetResponseDto.listFromJson( | ||||
|           entry.value, | ||||
|           growable: growable, | ||||
|         ); | ||||
|         final value = AssetResponseDto.listFromJson(entry.value, growable: growable,); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
| @@ -286,3 +274,4 @@ class AssetResponseDto { | ||||
|     'livePhotoVideoId', | ||||
|   }; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -14,7 +14,6 @@ class UsageByUserDto { | ||||
|   /// Returns a new [UsageByUserDto] instance. | ||||
|   UsageByUserDto({ | ||||
|     required this.userId, | ||||
|     required this.objects, | ||||
|     required this.videos, | ||||
|     required this.photos, | ||||
|     required this.usageRaw, | ||||
| @@ -23,8 +22,6 @@ class UsageByUserDto { | ||||
|  | ||||
|   String userId; | ||||
|  | ||||
|   int objects; | ||||
|  | ||||
|   int videos; | ||||
|  | ||||
|   int photos; | ||||
| @@ -36,7 +33,6 @@ class UsageByUserDto { | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is UsageByUserDto && | ||||
|      other.userId == userId && | ||||
|      other.objects == objects && | ||||
|      other.videos == videos && | ||||
|      other.photos == photos && | ||||
|      other.usageRaw == usageRaw && | ||||
| @@ -46,19 +42,17 @@ class UsageByUserDto { | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (userId.hashCode) + | ||||
|     (objects.hashCode) + | ||||
|     (videos.hashCode) + | ||||
|     (photos.hashCode) + | ||||
|     (usageRaw.hashCode) + | ||||
|     (usage.hashCode); | ||||
|  | ||||
|   @override | ||||
|   String toString() => 'UsageByUserDto[userId=$userId, objects=$objects, videos=$videos, photos=$photos, usageRaw=$usageRaw, usage=$usage]'; | ||||
|   String toString() => 'UsageByUserDto[userId=$userId, videos=$videos, photos=$photos, usageRaw=$usageRaw, usage=$usage]'; | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final _json = <String, dynamic>{}; | ||||
|       _json[r'userId'] = userId; | ||||
|       _json[r'objects'] = objects; | ||||
|       _json[r'videos'] = videos; | ||||
|       _json[r'photos'] = photos; | ||||
|       _json[r'usageRaw'] = usageRaw; | ||||
| @@ -86,7 +80,6 @@ class UsageByUserDto { | ||||
|  | ||||
|       return UsageByUserDto( | ||||
|         userId: mapValueOfType<String>(json, r'userId')!, | ||||
|         objects: mapValueOfType<int>(json, r'objects')!, | ||||
|         videos: mapValueOfType<int>(json, r'videos')!, | ||||
|         photos: mapValueOfType<int>(json, r'photos')!, | ||||
|         usageRaw: mapValueOfType<int>(json, r'usageRaw')!, | ||||
| @@ -141,7 +134,6 @@ class UsageByUserDto { | ||||
|   /// The list of required keys that must be present in a JSON. | ||||
|   static const requiredKeys = <String>{ | ||||
|     'userId', | ||||
|     'objects', | ||||
|     'videos', | ||||
|     'photos', | ||||
|     'usageRaw', | ||||
|   | ||||
| @@ -43,46 +43,43 @@ class UserResponseDto { | ||||
|   DateTime? deletedAt; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) => | ||||
|       identical(this, other) || | ||||
|       other is UserResponseDto && | ||||
|           other.id == id && | ||||
|           other.email == email && | ||||
|           other.firstName == firstName && | ||||
|           other.lastName == lastName && | ||||
|           other.createdAt == createdAt && | ||||
|           other.profileImagePath == profileImagePath && | ||||
|           other.shouldChangePassword == shouldChangePassword && | ||||
|           other.isAdmin == isAdmin && | ||||
|           other.deletedAt == deletedAt; | ||||
|   bool operator ==(Object other) => identical(this, other) || other is UserResponseDto && | ||||
|      other.id == id && | ||||
|      other.email == email && | ||||
|      other.firstName == firstName && | ||||
|      other.lastName == lastName && | ||||
|      other.createdAt == createdAt && | ||||
|      other.profileImagePath == profileImagePath && | ||||
|      other.shouldChangePassword == shouldChangePassword && | ||||
|      other.isAdmin == isAdmin && | ||||
|      other.deletedAt == deletedAt; | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => | ||||
|       // ignore: unnecessary_parenthesis | ||||
|       (id.hashCode) + | ||||
|       (email.hashCode) + | ||||
|       (firstName.hashCode) + | ||||
|       (lastName.hashCode) + | ||||
|       (createdAt.hashCode) + | ||||
|       (profileImagePath.hashCode) + | ||||
|       (shouldChangePassword.hashCode) + | ||||
|       (isAdmin.hashCode) + | ||||
|       (deletedAt == null ? 0 : deletedAt!.hashCode); | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (id.hashCode) + | ||||
|     (email.hashCode) + | ||||
|     (firstName.hashCode) + | ||||
|     (lastName.hashCode) + | ||||
|     (createdAt.hashCode) + | ||||
|     (profileImagePath.hashCode) + | ||||
|     (shouldChangePassword.hashCode) + | ||||
|     (isAdmin.hashCode) + | ||||
|     (deletedAt == null ? 0 : deletedAt!.hashCode); | ||||
|  | ||||
|   @override | ||||
|   String toString() => | ||||
|       'UserResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, createdAt=$createdAt, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, isAdmin=$isAdmin, deletedAt=$deletedAt]'; | ||||
|   String toString() => 'UserResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, createdAt=$createdAt, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, isAdmin=$isAdmin, deletedAt=$deletedAt]'; | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final _json = <String, dynamic>{}; | ||||
|     _json[r'id'] = id; | ||||
|     _json[r'email'] = email; | ||||
|     _json[r'firstName'] = firstName; | ||||
|     _json[r'lastName'] = lastName; | ||||
|     _json[r'createdAt'] = createdAt; | ||||
|     _json[r'profileImagePath'] = profileImagePath; | ||||
|     _json[r'shouldChangePassword'] = shouldChangePassword; | ||||
|     _json[r'isAdmin'] = isAdmin; | ||||
|       _json[r'id'] = id; | ||||
|       _json[r'email'] = email; | ||||
|       _json[r'firstName'] = firstName; | ||||
|       _json[r'lastName'] = lastName; | ||||
|       _json[r'createdAt'] = createdAt; | ||||
|       _json[r'profileImagePath'] = profileImagePath; | ||||
|       _json[r'shouldChangePassword'] = shouldChangePassword; | ||||
|       _json[r'isAdmin'] = isAdmin; | ||||
|     if (deletedAt != null) { | ||||
|       _json[r'deletedAt'] = deletedAt!.toUtc().toIso8601String(); | ||||
|     } else { | ||||
| @@ -101,13 +98,13 @@ class UserResponseDto { | ||||
|       // Ensure that the map contains the required keys. | ||||
|       // Note 1: the values aren't checked for validity beyond being non-null. | ||||
|       // Note 2: this code is stripped in release mode! | ||||
|       // assert(() { | ||||
|       //   requiredKeys.forEach((key) { | ||||
|       //     assert(json.containsKey(key), 'Required key "UserResponseDto[$key]" is missing from JSON.'); | ||||
|       //     assert(json[key] != null, 'Required key "UserResponseDto[$key]" has a null value in JSON.'); | ||||
|       //   }); | ||||
|       //   return true; | ||||
|       // }()); | ||||
|       assert(() { | ||||
|         requiredKeys.forEach((key) { | ||||
|           assert(json.containsKey(key), 'Required key "UserResponseDto[$key]" is missing from JSON.'); | ||||
|           assert(json[key] != null, 'Required key "UserResponseDto[$key]" has a null value in JSON.'); | ||||
|         }); | ||||
|         return true; | ||||
|       }()); | ||||
|  | ||||
|       return UserResponseDto( | ||||
|         id: mapValueOfType<String>(json, r'id')!, | ||||
| @@ -116,8 +113,7 @@ class UserResponseDto { | ||||
|         lastName: mapValueOfType<String>(json, r'lastName')!, | ||||
|         createdAt: mapValueOfType<String>(json, r'createdAt')!, | ||||
|         profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!, | ||||
|         shouldChangePassword: | ||||
|             mapValueOfType<bool>(json, r'shouldChangePassword')!, | ||||
|         shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword')!, | ||||
|         isAdmin: mapValueOfType<bool>(json, r'isAdmin')!, | ||||
|         deletedAt: mapDateTime(json, r'deletedAt', ''), | ||||
|       ); | ||||
| @@ -125,10 +121,7 @@ class UserResponseDto { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   static List<UserResponseDto>? listFromJson( | ||||
|     dynamic json, { | ||||
|     bool growable = false, | ||||
|   }) { | ||||
|   static List<UserResponseDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <UserResponseDto>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
| @@ -156,18 +149,12 @@ class UserResponseDto { | ||||
|   } | ||||
|  | ||||
|   // maps a json object with a list of UserResponseDto-objects as value to a dart map | ||||
|   static Map<String, List<UserResponseDto>> mapListFromJson( | ||||
|     dynamic json, { | ||||
|     bool growable = false, | ||||
|   }) { | ||||
|   static Map<String, List<UserResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<UserResponseDto>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = UserResponseDto.listFromJson( | ||||
|           entry.value, | ||||
|           growable: growable, | ||||
|         ); | ||||
|         final value = UserResponseDto.listFromJson(entry.value, growable: growable,); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
| @@ -189,3 +176,4 @@ class UserResponseDto { | ||||
|     'deletedAt', | ||||
|   }; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,6 @@ export class ServerStatsResponseDto { | ||||
|   constructor() { | ||||
|     this.photos = 0; | ||||
|     this.videos = 0; | ||||
|     this.objects = 0; | ||||
|     this.usageByUser = []; | ||||
|     this.usageRaw = 0; | ||||
|     this.usage = ''; | ||||
| @@ -34,7 +33,6 @@ export class ServerStatsResponseDto { | ||||
|       { | ||||
|         photos: 1, | ||||
|         videos: 1, | ||||
|         objects: 1, | ||||
|         diskUsageRaw: 1, | ||||
|       }, | ||||
|     ], | ||||
|   | ||||
| @@ -3,16 +3,15 @@ import { ApiProperty } from '@nestjs/swagger'; | ||||
| export class UsageByUserDto { | ||||
|   constructor(userId: string) { | ||||
|     this.userId = userId; | ||||
|     this.objects = 0; | ||||
|     this.videos = 0; | ||||
|     this.photos = 0; | ||||
|     this.usageRaw = 0; | ||||
|     this.usage = '0B'; | ||||
|   } | ||||
|  | ||||
|   @ApiProperty({ type: 'string' }) | ||||
|   userId: string; | ||||
|   @ApiProperty({ type: 'integer' }) | ||||
|   objects: number; | ||||
|   @ApiProperty({ type: 'integer' }) | ||||
|   videos: number; | ||||
|   @ApiProperty({ type: 'integer' }) | ||||
|   photos: number; | ||||
|   | ||||
| @@ -7,8 +7,6 @@ import { UsageByUserDto } from './response-dto/usage-by-user-response.dto'; | ||||
| import { AssetEntity } from '@app/database/entities/asset.entity'; | ||||
| import { Repository } from 'typeorm'; | ||||
| import { InjectRepository } from '@nestjs/typeorm'; | ||||
| import path from 'path'; | ||||
| import { readdirSync, statSync } from 'fs'; | ||||
| import { asHumanReadable } from '../../utils/human-readable.util'; | ||||
|  | ||||
| @Injectable() | ||||
| @@ -35,59 +33,46 @@ export class ServerInfoService { | ||||
|   } | ||||
|  | ||||
|   async getStats(): Promise<ServerStatsResponseDto> { | ||||
|     const res = await this.assetRepository | ||||
|       .createQueryBuilder('asset') | ||||
|       .select(`COUNT(asset.id)`, 'count') | ||||
|       .addSelect(`asset.type`, 'type') | ||||
|       .addSelect(`asset.userId`, 'userId') | ||||
|       .groupBy('asset.type, asset.userId') | ||||
|       .addGroupBy('asset.type') | ||||
|     const serverStats = new ServerStatsResponseDto(); | ||||
|  | ||||
|     type UserStatsQueryResponse = { | ||||
|       assetType: string; | ||||
|       assetCount: string; | ||||
|       totalSizeInBytes: string; | ||||
|       userId: string; | ||||
|     }; | ||||
|  | ||||
|     const userStatsQueryResponse: UserStatsQueryResponse[] = await this.assetRepository | ||||
|       .createQueryBuilder('a') | ||||
|       .select('COUNT(a.id)', 'assetCount') | ||||
|       .addSelect('SUM(ei.fileSizeInByte)', 'totalSizeInBytes') | ||||
|       .addSelect('a."userId"') | ||||
|       .addSelect('a.type', 'assetType') | ||||
|       .where('a.isVisible = true') | ||||
|       .leftJoin('a.exifInfo', 'ei') | ||||
|       .groupBy('a."userId"') | ||||
|       .addGroupBy('a.type') | ||||
|       .getRawMany(); | ||||
|  | ||||
|     const serverStats = new ServerStatsResponseDto(); | ||||
|     const tmpMap = new Map<string, UsageByUserDto>(); | ||||
|     const getUsageByUser = (id: string) => tmpMap.get(id) || new UsageByUserDto(id); | ||||
|     res.map((item) => { | ||||
|       const usage: UsageByUserDto = getUsageByUser(item.userId); | ||||
|       if (item.type === 'IMAGE') { | ||||
|         usage.photos = parseInt(item.count); | ||||
|         serverStats.photos += usage.photos; | ||||
|       } else if (item.type === 'VIDEO') { | ||||
|         usage.videos = parseInt(item.count); | ||||
|         serverStats.videos += usage.videos; | ||||
|       } | ||||
|       tmpMap.set(item.userId, usage); | ||||
|  | ||||
|     userStatsQueryResponse.forEach((r) => { | ||||
|       const usageByUser = getUsageByUser(r.userId); | ||||
|       usageByUser.photos += r.assetType === 'IMAGE' ? parseInt(r.assetCount) : 0; | ||||
|       usageByUser.videos += r.assetType === 'VIDEO' ? parseInt(r.assetCount) : 0; | ||||
|       usageByUser.usageRaw += parseInt(r.totalSizeInBytes); | ||||
|       usageByUser.usage = asHumanReadable(usageByUser.usageRaw); | ||||
|  | ||||
|       serverStats.photos += r.assetType === 'IMAGE' ? parseInt(r.assetCount) : 0; | ||||
|       serverStats.videos += r.assetType === 'VIDEO' ? parseInt(r.assetCount) : 0; | ||||
|       serverStats.usageRaw += parseInt(r.totalSizeInBytes); | ||||
|       serverStats.usage = asHumanReadable(serverStats.usageRaw); | ||||
|       tmpMap.set(r.userId, usageByUser); | ||||
|     }); | ||||
|  | ||||
|     for (const userId of tmpMap.keys()) { | ||||
|       const usage = getUsageByUser(userId); | ||||
|       const userDiskUsage = await ServerInfoService.getDirectoryStats(path.join(APP_UPLOAD_LOCATION, userId)); | ||||
|       usage.usageRaw = userDiskUsage.size; | ||||
|       usage.objects = userDiskUsage.fileCount; | ||||
|       usage.usage = asHumanReadable(usage.usageRaw); | ||||
|       serverStats.usageRaw += usage.usageRaw; | ||||
|       serverStats.objects += usage.objects; | ||||
|     } | ||||
|     serverStats.usage = asHumanReadable(serverStats.usageRaw); | ||||
|     serverStats.usageByUser = Array.from(tmpMap.values()); | ||||
|  | ||||
|     return serverStats; | ||||
|   } | ||||
|  | ||||
|   private static async getDirectoryStats(dirPath: string) { | ||||
|     let size = 0; | ||||
|     let fileCount = 0; | ||||
|     for (const filename of readdirSync(dirPath)) { | ||||
|       const absFilename = path.join(dirPath, filename); | ||||
|       const fileStat = statSync(absFilename); | ||||
|       if (fileStat.isFile()) { | ||||
|         size += fileStat.size; | ||||
|         fileCount += 1; | ||||
|       } else if (fileStat.isDirectory()) { | ||||
|         const subDirStat = await ServerInfoService.getDirectoryStats(absFilename); | ||||
|         size += subDirStat.size; | ||||
|         fileCount += subDirStat.fileCount; | ||||
|       } | ||||
|     } | ||||
|     return { size, fileCount }; | ||||
|   } | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -1632,12 +1632,6 @@ export interface UsageByUserDto { | ||||
|      * @memberof UsageByUserDto | ||||
|      */ | ||||
|     'userId': string; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {number} | ||||
|      * @memberof UsageByUserDto | ||||
|      */ | ||||
|     'objects': number; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {number} | ||||
|   | ||||
| @@ -2,7 +2,6 @@ | ||||
| 	import { ServerStatsResponseDto, UserResponseDto } from '@api'; | ||||
| 	import CameraIris from 'svelte-material-icons/CameraIris.svelte'; | ||||
| 	import PlayCircle from 'svelte-material-icons/PlayCircle.svelte'; | ||||
| 	import FileImageOutline from 'svelte-material-icons/FileImageOutline.svelte'; | ||||
| 	import Memory from 'svelte-material-icons/Memory.svelte'; | ||||
| 	import StatsCard from './stats-card.svelte'; | ||||
| 	export let stats: ServerStatsResponseDto; | ||||
| @@ -27,7 +26,6 @@ | ||||
| 		<div class="flex mt-5 justify-between"> | ||||
| 			<StatsCard logo={CameraIris} title={'PHOTOS'} value={stats.photos.toString()} /> | ||||
| 			<StatsCard logo={PlayCircle} title={'VIDEOS'} value={stats.videos.toString()} /> | ||||
| 			<StatsCard logo={FileImageOutline} title={'OBJECTS'} value={stats.objects.toString()} /> | ||||
| 			<StatsCard logo={Memory} title={'STORAGE'} value={spaceUsage} unit={spaceUnit} /> | ||||
| 		</div> | ||||
| 	</div> | ||||
| @@ -39,11 +37,10 @@ | ||||
| 				class="border rounded-md mb-4 bg-gray-50 dark:bg-immich-dark-gray dark:border-immich-dark-gray flex text-immich-primary dark:text-immich-dark-primary  w-full h-12" | ||||
| 			> | ||||
| 				<tr class="flex w-full place-items-center"> | ||||
| 					<th class="text-center w-1/5 font-medium text-sm">User</th> | ||||
| 					<th class="text-center w-1/5 font-medium text-sm">Photos</th> | ||||
| 					<th class="text-center w-1/5 font-medium text-sm">Videos</th> | ||||
| 					<th class="text-center w-1/5 font-medium text-sm">Objects</th> | ||||
| 					<th class="text-center w-1/5 font-medium text-sm">Size</th> | ||||
| 					<th class="text-center w-1/4 font-medium text-sm">User</th> | ||||
| 					<th class="text-center w-1/4 font-medium text-sm">Photos</th> | ||||
| 					<th class="text-center w-1/4 font-medium text-sm">Videos</th> | ||||
| 					<th class="text-center w-1/4 font-medium text-sm">Size</th> | ||||
| 				</tr> | ||||
| 			</thead> | ||||
| 			<tbody | ||||
| @@ -57,11 +54,10 @@ | ||||
| 								: 'bg-immich-bg dark:bg-immich-dark-gray/50' | ||||
| 						}`} | ||||
| 					> | ||||
| 						<td class="text-sm px-2 w-1/5 text-ellipsis">{getFullName(user.userId)}</td> | ||||
| 						<td class="text-sm px-2 w-1/5 text-ellipsis">{user.photos}</td> | ||||
| 						<td class="text-sm px-2 w-1/5 text-ellipsis">{user.videos}</td> | ||||
| 						<td class="text-sm px-2 w-1/5 text-ellipsis">{user.objects}</td> | ||||
| 						<td class="text-sm px-2 w-1/5 text-ellipsis">{user.usage}</td> | ||||
| 						<td class="text-sm px-2 w-1/4 text-ellipsis">{getFullName(user.userId)}</td> | ||||
| 						<td class="text-sm px-2 w-1/4 text-ellipsis">{user.photos}</td> | ||||
| 						<td class="text-sm px-2 w-1/4 text-ellipsis">{user.videos}</td> | ||||
| 						<td class="text-sm px-2 w-1/4 text-ellipsis">{user.usage}</td> | ||||
| 					</tr> | ||||
| 				{/each} | ||||
| 			</tbody> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|  | ||||
| 	$: zeros = () => { | ||||
| 		let result = ''; | ||||
| 		const maxLength = 9; | ||||
| 		const maxLength = 13; | ||||
| 		const valueLength = parseInt(value).toString().length; | ||||
| 		const zeroLength = maxLength - valueLength; | ||||
| 		for (let i = 0; i < zeroLength; i++) { | ||||
| @@ -18,7 +18,7 @@ | ||||
| </script> | ||||
|  | ||||
| <div | ||||
| 	class="w-[180px] h-[140px] bg-immich-gray dark:bg-immich-dark-gray rounded-3xl p-5 flex flex-col justify-between" | ||||
| 	class="w-[250px] h-[140px] bg-immich-gray dark:bg-immich-dark-gray rounded-3xl p-5 flex flex-col justify-between" | ||||
| > | ||||
| 	<div class="flex place-items-center gap-4 text-immich-primary dark:text-immich-dark-primary"> | ||||
| 		<svelte:component this={logo} size="40" /> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user