mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	fix(server): Server freezes when getting statistic (#994)
* fix(server): Server freezes when getting statistic * remove dead code
This commit is contained in:
		
							parent
							
								
									b3e51cc849
								
							
						
					
					
						commit
						41ffa0c015
					
				@ -9,7 +9,6 @@ import 'package:openapi/api.dart';
 | 
			
		||||
Name | Type | Description | Notes
 | 
			
		||||
------------ | ------------- | ------------- | -------------
 | 
			
		||||
**userId** | **String** |  | 
 | 
			
		||||
**objects** | **int** |  | 
 | 
			
		||||
**videos** | **int** |  | 
 | 
			
		||||
**photos** | **int** |  | 
 | 
			
		||||
**usageRaw** | **int** |  | 
 | 
			
		||||
 | 
			
		||||
@ -43,9 +43,7 @@ class AlbumResponseDto {
 | 
			
		||||
  List<AssetResponseDto> assets;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) =>
 | 
			
		||||
      identical(this, other) ||
 | 
			
		||||
      other is AlbumResponseDto &&
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is AlbumResponseDto &&
 | 
			
		||||
     other.assetCount == assetCount &&
 | 
			
		||||
     other.id == id &&
 | 
			
		||||
     other.ownerId == ownerId &&
 | 
			
		||||
@ -70,8 +68,7 @@ class AlbumResponseDto {
 | 
			
		||||
    (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>{};
 | 
			
		||||
@ -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,9 +79,7 @@ class AssetResponseDto {
 | 
			
		||||
  String? livePhotoVideoId;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) =>
 | 
			
		||||
      identical(this, other) ||
 | 
			
		||||
      other is AssetResponseDto &&
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
 | 
			
		||||
     other.type == type &&
 | 
			
		||||
     other.id == id &&
 | 
			
		||||
     other.deviceAssetId == deviceAssetId &&
 | 
			
		||||
@ -122,8 +120,7 @@ class AssetResponseDto {
 | 
			
		||||
    (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>{};
 | 
			
		||||
@ -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,9 +43,7 @@ class UserResponseDto {
 | 
			
		||||
  DateTime? deletedAt;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) =>
 | 
			
		||||
      identical(this, other) ||
 | 
			
		||||
      other is UserResponseDto &&
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is UserResponseDto &&
 | 
			
		||||
     other.id == id &&
 | 
			
		||||
     other.email == email &&
 | 
			
		||||
     other.firstName == firstName &&
 | 
			
		||||
@ -70,8 +68,7 @@ class UserResponseDto {
 | 
			
		||||
    (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>{};
 | 
			
		||||
@ -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" />
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user