mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	feat: adding photo & video storage space to server stats (#14125)
* expose detailed user storage stats + display them in the storage per user table * chore: openapi & sql * fix: fix test stubs * fix: formatting errors, e2e test and server test * fix: upper lower case typo in spec file --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
		
							parent
							
								
									24ae4ecff1
								
							
						
					
					
						commit
						f5c4af73aa
					
				@ -163,11 +163,15 @@ describe('/server', () => {
 | 
				
			|||||||
      expect(body).toEqual({
 | 
					      expect(body).toEqual({
 | 
				
			||||||
        photos: 0,
 | 
					        photos: 0,
 | 
				
			||||||
        usage: 0,
 | 
					        usage: 0,
 | 
				
			||||||
 | 
					        usagePhotos: 0,
 | 
				
			||||||
 | 
					        usageVideos: 0,
 | 
				
			||||||
        usageByUser: [
 | 
					        usageByUser: [
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            quotaSizeInBytes: null,
 | 
					            quotaSizeInBytes: null,
 | 
				
			||||||
            photos: 0,
 | 
					            photos: 0,
 | 
				
			||||||
            usage: 0,
 | 
					            usage: 0,
 | 
				
			||||||
 | 
					            usagePhotos: 0,
 | 
				
			||||||
 | 
					            usageVideos: 0,
 | 
				
			||||||
            userName: 'Immich Admin',
 | 
					            userName: 'Immich Admin',
 | 
				
			||||||
            userId: admin.userId,
 | 
					            userId: admin.userId,
 | 
				
			||||||
            videos: 0,
 | 
					            videos: 0,
 | 
				
			||||||
@ -176,6 +180,8 @@ describe('/server', () => {
 | 
				
			|||||||
            quotaSizeInBytes: null,
 | 
					            quotaSizeInBytes: null,
 | 
				
			||||||
            photos: 0,
 | 
					            photos: 0,
 | 
				
			||||||
            usage: 0,
 | 
					            usage: 0,
 | 
				
			||||||
 | 
					            usagePhotos: 0,
 | 
				
			||||||
 | 
					            usageVideos: 0,
 | 
				
			||||||
            userName: 'User 1',
 | 
					            userName: 'User 1',
 | 
				
			||||||
            userId: nonAdmin.userId,
 | 
					            userId: nonAdmin.userId,
 | 
				
			||||||
            videos: 0,
 | 
					            videos: 0,
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,8 @@ class ServerStatsResponseDto {
 | 
				
			|||||||
    this.photos = 0,
 | 
					    this.photos = 0,
 | 
				
			||||||
    this.usage = 0,
 | 
					    this.usage = 0,
 | 
				
			||||||
    this.usageByUser = const [],
 | 
					    this.usageByUser = const [],
 | 
				
			||||||
 | 
					    this.usagePhotos = 0,
 | 
				
			||||||
 | 
					    this.usageVideos = 0,
 | 
				
			||||||
    this.videos = 0,
 | 
					    this.videos = 0,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -25,6 +27,10 @@ class ServerStatsResponseDto {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  List<UsageByUserDto> usageByUser;
 | 
					  List<UsageByUserDto> usageByUser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int usagePhotos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int usageVideos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  int videos;
 | 
					  int videos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@ -32,6 +38,8 @@ class ServerStatsResponseDto {
 | 
				
			|||||||
    other.photos == photos &&
 | 
					    other.photos == photos &&
 | 
				
			||||||
    other.usage == usage &&
 | 
					    other.usage == usage &&
 | 
				
			||||||
    _deepEquality.equals(other.usageByUser, usageByUser) &&
 | 
					    _deepEquality.equals(other.usageByUser, usageByUser) &&
 | 
				
			||||||
 | 
					    other.usagePhotos == usagePhotos &&
 | 
				
			||||||
 | 
					    other.usageVideos == usageVideos &&
 | 
				
			||||||
    other.videos == videos;
 | 
					    other.videos == videos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@ -40,16 +48,20 @@ class ServerStatsResponseDto {
 | 
				
			|||||||
    (photos.hashCode) +
 | 
					    (photos.hashCode) +
 | 
				
			||||||
    (usage.hashCode) +
 | 
					    (usage.hashCode) +
 | 
				
			||||||
    (usageByUser.hashCode) +
 | 
					    (usageByUser.hashCode) +
 | 
				
			||||||
 | 
					    (usagePhotos.hashCode) +
 | 
				
			||||||
 | 
					    (usageVideos.hashCode) +
 | 
				
			||||||
    (videos.hashCode);
 | 
					    (videos.hashCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  String toString() => 'ServerStatsResponseDto[photos=$photos, usage=$usage, usageByUser=$usageByUser, videos=$videos]';
 | 
					  String toString() => 'ServerStatsResponseDto[photos=$photos, usage=$usage, usageByUser=$usageByUser, usagePhotos=$usagePhotos, usageVideos=$usageVideos, videos=$videos]';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Map<String, dynamic> toJson() {
 | 
					  Map<String, dynamic> toJson() {
 | 
				
			||||||
    final json = <String, dynamic>{};
 | 
					    final json = <String, dynamic>{};
 | 
				
			||||||
      json[r'photos'] = this.photos;
 | 
					      json[r'photos'] = this.photos;
 | 
				
			||||||
      json[r'usage'] = this.usage;
 | 
					      json[r'usage'] = this.usage;
 | 
				
			||||||
      json[r'usageByUser'] = this.usageByUser;
 | 
					      json[r'usageByUser'] = this.usageByUser;
 | 
				
			||||||
 | 
					      json[r'usagePhotos'] = this.usagePhotos;
 | 
				
			||||||
 | 
					      json[r'usageVideos'] = this.usageVideos;
 | 
				
			||||||
      json[r'videos'] = this.videos;
 | 
					      json[r'videos'] = this.videos;
 | 
				
			||||||
    return json;
 | 
					    return json;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -66,6 +78,8 @@ class ServerStatsResponseDto {
 | 
				
			|||||||
        photos: mapValueOfType<int>(json, r'photos')!,
 | 
					        photos: mapValueOfType<int>(json, r'photos')!,
 | 
				
			||||||
        usage: mapValueOfType<int>(json, r'usage')!,
 | 
					        usage: mapValueOfType<int>(json, r'usage')!,
 | 
				
			||||||
        usageByUser: UsageByUserDto.listFromJson(json[r'usageByUser']),
 | 
					        usageByUser: UsageByUserDto.listFromJson(json[r'usageByUser']),
 | 
				
			||||||
 | 
					        usagePhotos: mapValueOfType<int>(json, r'usagePhotos')!,
 | 
				
			||||||
 | 
					        usageVideos: mapValueOfType<int>(json, r'usageVideos')!,
 | 
				
			||||||
        videos: mapValueOfType<int>(json, r'videos')!,
 | 
					        videos: mapValueOfType<int>(json, r'videos')!,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -117,6 +131,8 @@ class ServerStatsResponseDto {
 | 
				
			|||||||
    'photos',
 | 
					    'photos',
 | 
				
			||||||
    'usage',
 | 
					    'usage',
 | 
				
			||||||
    'usageByUser',
 | 
					    'usageByUser',
 | 
				
			||||||
 | 
					    'usagePhotos',
 | 
				
			||||||
 | 
					    'usageVideos',
 | 
				
			||||||
    'videos',
 | 
					    'videos',
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										18
									
								
								mobile/openapi/lib/model/usage_by_user_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										18
									
								
								mobile/openapi/lib/model/usage_by_user_dto.dart
									
									
									
										generated
									
									
									
								
							@ -16,6 +16,8 @@ class UsageByUserDto {
 | 
				
			|||||||
    required this.photos,
 | 
					    required this.photos,
 | 
				
			||||||
    required this.quotaSizeInBytes,
 | 
					    required this.quotaSizeInBytes,
 | 
				
			||||||
    required this.usage,
 | 
					    required this.usage,
 | 
				
			||||||
 | 
					    required this.usagePhotos,
 | 
				
			||||||
 | 
					    required this.usageVideos,
 | 
				
			||||||
    required this.userId,
 | 
					    required this.userId,
 | 
				
			||||||
    required this.userName,
 | 
					    required this.userName,
 | 
				
			||||||
    required this.videos,
 | 
					    required this.videos,
 | 
				
			||||||
@ -27,6 +29,10 @@ class UsageByUserDto {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  int usage;
 | 
					  int usage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int usagePhotos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int usageVideos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  String userId;
 | 
					  String userId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  String userName;
 | 
					  String userName;
 | 
				
			||||||
@ -38,6 +44,8 @@ class UsageByUserDto {
 | 
				
			|||||||
    other.photos == photos &&
 | 
					    other.photos == photos &&
 | 
				
			||||||
    other.quotaSizeInBytes == quotaSizeInBytes &&
 | 
					    other.quotaSizeInBytes == quotaSizeInBytes &&
 | 
				
			||||||
    other.usage == usage &&
 | 
					    other.usage == usage &&
 | 
				
			||||||
 | 
					    other.usagePhotos == usagePhotos &&
 | 
				
			||||||
 | 
					    other.usageVideos == usageVideos &&
 | 
				
			||||||
    other.userId == userId &&
 | 
					    other.userId == userId &&
 | 
				
			||||||
    other.userName == userName &&
 | 
					    other.userName == userName &&
 | 
				
			||||||
    other.videos == videos;
 | 
					    other.videos == videos;
 | 
				
			||||||
@ -48,12 +56,14 @@ class UsageByUserDto {
 | 
				
			|||||||
    (photos.hashCode) +
 | 
					    (photos.hashCode) +
 | 
				
			||||||
    (quotaSizeInBytes == null ? 0 : quotaSizeInBytes!.hashCode) +
 | 
					    (quotaSizeInBytes == null ? 0 : quotaSizeInBytes!.hashCode) +
 | 
				
			||||||
    (usage.hashCode) +
 | 
					    (usage.hashCode) +
 | 
				
			||||||
 | 
					    (usagePhotos.hashCode) +
 | 
				
			||||||
 | 
					    (usageVideos.hashCode) +
 | 
				
			||||||
    (userId.hashCode) +
 | 
					    (userId.hashCode) +
 | 
				
			||||||
    (userName.hashCode) +
 | 
					    (userName.hashCode) +
 | 
				
			||||||
    (videos.hashCode);
 | 
					    (videos.hashCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  String toString() => 'UsageByUserDto[photos=$photos, quotaSizeInBytes=$quotaSizeInBytes, usage=$usage, userId=$userId, userName=$userName, videos=$videos]';
 | 
					  String toString() => 'UsageByUserDto[photos=$photos, quotaSizeInBytes=$quotaSizeInBytes, usage=$usage, usagePhotos=$usagePhotos, usageVideos=$usageVideos, userId=$userId, userName=$userName, videos=$videos]';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Map<String, dynamic> toJson() {
 | 
					  Map<String, dynamic> toJson() {
 | 
				
			||||||
    final json = <String, dynamic>{};
 | 
					    final json = <String, dynamic>{};
 | 
				
			||||||
@ -64,6 +74,8 @@ class UsageByUserDto {
 | 
				
			|||||||
    //  json[r'quotaSizeInBytes'] = null;
 | 
					    //  json[r'quotaSizeInBytes'] = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
      json[r'usage'] = this.usage;
 | 
					      json[r'usage'] = this.usage;
 | 
				
			||||||
 | 
					      json[r'usagePhotos'] = this.usagePhotos;
 | 
				
			||||||
 | 
					      json[r'usageVideos'] = this.usageVideos;
 | 
				
			||||||
      json[r'userId'] = this.userId;
 | 
					      json[r'userId'] = this.userId;
 | 
				
			||||||
      json[r'userName'] = this.userName;
 | 
					      json[r'userName'] = this.userName;
 | 
				
			||||||
      json[r'videos'] = this.videos;
 | 
					      json[r'videos'] = this.videos;
 | 
				
			||||||
@ -82,6 +94,8 @@ class UsageByUserDto {
 | 
				
			|||||||
        photos: mapValueOfType<int>(json, r'photos')!,
 | 
					        photos: mapValueOfType<int>(json, r'photos')!,
 | 
				
			||||||
        quotaSizeInBytes: mapValueOfType<int>(json, r'quotaSizeInBytes'),
 | 
					        quotaSizeInBytes: mapValueOfType<int>(json, r'quotaSizeInBytes'),
 | 
				
			||||||
        usage: mapValueOfType<int>(json, r'usage')!,
 | 
					        usage: mapValueOfType<int>(json, r'usage')!,
 | 
				
			||||||
 | 
					        usagePhotos: mapValueOfType<int>(json, r'usagePhotos')!,
 | 
				
			||||||
 | 
					        usageVideos: mapValueOfType<int>(json, r'usageVideos')!,
 | 
				
			||||||
        userId: mapValueOfType<String>(json, r'userId')!,
 | 
					        userId: mapValueOfType<String>(json, r'userId')!,
 | 
				
			||||||
        userName: mapValueOfType<String>(json, r'userName')!,
 | 
					        userName: mapValueOfType<String>(json, r'userName')!,
 | 
				
			||||||
        videos: mapValueOfType<int>(json, r'videos')!,
 | 
					        videos: mapValueOfType<int>(json, r'videos')!,
 | 
				
			||||||
@ -135,6 +149,8 @@ class UsageByUserDto {
 | 
				
			|||||||
    'photos',
 | 
					    'photos',
 | 
				
			||||||
    'quotaSizeInBytes',
 | 
					    'quotaSizeInBytes',
 | 
				
			||||||
    'usage',
 | 
					    'usage',
 | 
				
			||||||
 | 
					    'usagePhotos',
 | 
				
			||||||
 | 
					    'usageVideos',
 | 
				
			||||||
    'userId',
 | 
					    'userId',
 | 
				
			||||||
    'userName',
 | 
					    'userName',
 | 
				
			||||||
    'videos',
 | 
					    'videos',
 | 
				
			||||||
 | 
				
			|||||||
@ -10966,7 +10966,9 @@
 | 
				
			|||||||
              {
 | 
					              {
 | 
				
			||||||
                "photos": 1,
 | 
					                "photos": 1,
 | 
				
			||||||
                "videos": 1,
 | 
					                "videos": 1,
 | 
				
			||||||
                "diskUsageRaw": 1
 | 
					                "diskUsageRaw": 2,
 | 
				
			||||||
 | 
					                "usagePhotos": 1,
 | 
				
			||||||
 | 
					                "usageVideos": 1
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "items": {
 | 
					            "items": {
 | 
				
			||||||
@ -10975,6 +10977,16 @@
 | 
				
			|||||||
            "title": "Array of usage for each user",
 | 
					            "title": "Array of usage for each user",
 | 
				
			||||||
            "type": "array"
 | 
					            "type": "array"
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
 | 
					          "usagePhotos": {
 | 
				
			||||||
 | 
					            "default": 0,
 | 
				
			||||||
 | 
					            "format": "int64",
 | 
				
			||||||
 | 
					            "type": "integer"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "usageVideos": {
 | 
				
			||||||
 | 
					            "default": 0,
 | 
				
			||||||
 | 
					            "format": "int64",
 | 
				
			||||||
 | 
					            "type": "integer"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
          "videos": {
 | 
					          "videos": {
 | 
				
			||||||
            "default": 0,
 | 
					            "default": 0,
 | 
				
			||||||
            "type": "integer"
 | 
					            "type": "integer"
 | 
				
			||||||
@ -10984,6 +10996,8 @@
 | 
				
			|||||||
          "photos",
 | 
					          "photos",
 | 
				
			||||||
          "usage",
 | 
					          "usage",
 | 
				
			||||||
          "usageByUser",
 | 
					          "usageByUser",
 | 
				
			||||||
 | 
					          "usagePhotos",
 | 
				
			||||||
 | 
					          "usageVideos",
 | 
				
			||||||
          "videos"
 | 
					          "videos"
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        "type": "object"
 | 
					        "type": "object"
 | 
				
			||||||
@ -12503,6 +12517,14 @@
 | 
				
			|||||||
            "format": "int64",
 | 
					            "format": "int64",
 | 
				
			||||||
            "type": "integer"
 | 
					            "type": "integer"
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
 | 
					          "usagePhotos": {
 | 
				
			||||||
 | 
					            "format": "int64",
 | 
				
			||||||
 | 
					            "type": "integer"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "usageVideos": {
 | 
				
			||||||
 | 
					            "format": "int64",
 | 
				
			||||||
 | 
					            "type": "integer"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
          "userId": {
 | 
					          "userId": {
 | 
				
			||||||
            "type": "string"
 | 
					            "type": "string"
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
@ -12517,6 +12539,8 @@
 | 
				
			|||||||
          "photos",
 | 
					          "photos",
 | 
				
			||||||
          "quotaSizeInBytes",
 | 
					          "quotaSizeInBytes",
 | 
				
			||||||
          "usage",
 | 
					          "usage",
 | 
				
			||||||
 | 
					          "usagePhotos",
 | 
				
			||||||
 | 
					          "usageVideos",
 | 
				
			||||||
          "userId",
 | 
					          "userId",
 | 
				
			||||||
          "userName",
 | 
					          "userName",
 | 
				
			||||||
          "videos"
 | 
					          "videos"
 | 
				
			||||||
 | 
				
			|||||||
@ -969,6 +969,8 @@ export type UsageByUserDto = {
 | 
				
			|||||||
    photos: number;
 | 
					    photos: number;
 | 
				
			||||||
    quotaSizeInBytes: number | null;
 | 
					    quotaSizeInBytes: number | null;
 | 
				
			||||||
    usage: number;
 | 
					    usage: number;
 | 
				
			||||||
 | 
					    usagePhotos: number;
 | 
				
			||||||
 | 
					    usageVideos: number;
 | 
				
			||||||
    userId: string;
 | 
					    userId: string;
 | 
				
			||||||
    userName: string;
 | 
					    userName: string;
 | 
				
			||||||
    videos: number;
 | 
					    videos: number;
 | 
				
			||||||
@ -977,6 +979,8 @@ export type ServerStatsResponseDto = {
 | 
				
			|||||||
    photos: number;
 | 
					    photos: number;
 | 
				
			||||||
    usage: number;
 | 
					    usage: number;
 | 
				
			||||||
    usageByUser: UsageByUserDto[];
 | 
					    usageByUser: UsageByUserDto[];
 | 
				
			||||||
 | 
					    usagePhotos: number;
 | 
				
			||||||
 | 
					    usageVideos: number;
 | 
				
			||||||
    videos: number;
 | 
					    videos: number;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
export type ServerStorageResponseDto = {
 | 
					export type ServerStorageResponseDto = {
 | 
				
			||||||
 | 
				
			|||||||
@ -86,6 +86,10 @@ export class UsageByUserDto {
 | 
				
			|||||||
  @ApiProperty({ type: 'integer', format: 'int64' })
 | 
					  @ApiProperty({ type: 'integer', format: 'int64' })
 | 
				
			||||||
  usage!: number;
 | 
					  usage!: number;
 | 
				
			||||||
  @ApiProperty({ type: 'integer', format: 'int64' })
 | 
					  @ApiProperty({ type: 'integer', format: 'int64' })
 | 
				
			||||||
 | 
					  usagePhotos!: number;
 | 
				
			||||||
 | 
					  @ApiProperty({ type: 'integer', format: 'int64' })
 | 
				
			||||||
 | 
					  usageVideos!: number;
 | 
				
			||||||
 | 
					  @ApiProperty({ type: 'integer', format: 'int64' })
 | 
				
			||||||
  quotaSizeInBytes!: number | null;
 | 
					  quotaSizeInBytes!: number | null;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -99,6 +103,12 @@ export class ServerStatsResponseDto {
 | 
				
			|||||||
  @ApiProperty({ type: 'integer', format: 'int64' })
 | 
					  @ApiProperty({ type: 'integer', format: 'int64' })
 | 
				
			||||||
  usage = 0;
 | 
					  usage = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @ApiProperty({ type: 'integer', format: 'int64' })
 | 
				
			||||||
 | 
					  usagePhotos = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @ApiProperty({ type: 'integer', format: 'int64' })
 | 
				
			||||||
 | 
					  usageVideos = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @ApiProperty({
 | 
					  @ApiProperty({
 | 
				
			||||||
    isArray: true,
 | 
					    isArray: true,
 | 
				
			||||||
    type: UsageByUserDto,
 | 
					    type: UsageByUserDto,
 | 
				
			||||||
@ -107,7 +117,9 @@ export class ServerStatsResponseDto {
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        photos: 1,
 | 
					        photos: 1,
 | 
				
			||||||
        videos: 1,
 | 
					        videos: 1,
 | 
				
			||||||
        diskUsageRaw: 1,
 | 
					        diskUsageRaw: 2,
 | 
				
			||||||
 | 
					        usagePhotos: 1,
 | 
				
			||||||
 | 
					        usageVideos: 1,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,8 @@ export interface UserStatsQueryResponse {
 | 
				
			|||||||
  photos: number;
 | 
					  photos: number;
 | 
				
			||||||
  videos: number;
 | 
					  videos: number;
 | 
				
			||||||
  usage: number;
 | 
					  usage: number;
 | 
				
			||||||
 | 
					  usagePhotos: number;
 | 
				
			||||||
 | 
					  usageVideos: number;
 | 
				
			||||||
  quotaSizeInBytes: number | null;
 | 
					  quotaSizeInBytes: number | null;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -140,7 +140,23 @@ SELECT
 | 
				
			|||||||
        "assets"."libraryId" IS NULL
 | 
					        "assets"."libraryId" IS NULL
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    0
 | 
					    0
 | 
				
			||||||
  ) AS "usage"
 | 
					  ) AS "usage",
 | 
				
			||||||
 | 
					  COALESCE(
 | 
				
			||||||
 | 
					    SUM("exif"."fileSizeInByte") FILTER (
 | 
				
			||||||
 | 
					      WHERE
 | 
				
			||||||
 | 
					        "assets"."libraryId" IS NULL
 | 
				
			||||||
 | 
					        AND "assets"."type" = 'IMAGE'
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    0
 | 
				
			||||||
 | 
					  ) AS "usagePhotos",
 | 
				
			||||||
 | 
					  COALESCE(
 | 
				
			||||||
 | 
					    SUM("exif"."fileSizeInByte") FILTER (
 | 
				
			||||||
 | 
					      WHERE
 | 
				
			||||||
 | 
					        "assets"."libraryId" IS NULL
 | 
				
			||||||
 | 
					        AND "assets"."type" = 'VIDEO'
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    0
 | 
				
			||||||
 | 
					  ) AS "usageVideos"
 | 
				
			||||||
FROM
 | 
					FROM
 | 
				
			||||||
  "users" "users"
 | 
					  "users" "users"
 | 
				
			||||||
  LEFT JOIN "assets" "assets" ON "assets"."ownerId" = "users"."id"
 | 
					  LEFT JOIN "assets" "assets" ON "assets"."ownerId" = "users"."id"
 | 
				
			||||||
 | 
				
			|||||||
@ -108,6 +108,14 @@ export class UserRepository implements IUserRepository {
 | 
				
			|||||||
      .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos')
 | 
					      .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos')
 | 
				
			||||||
      .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos')
 | 
					      .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos')
 | 
				
			||||||
      .addSelect('COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL), 0)', 'usage')
 | 
					      .addSelect('COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL), 0)', 'usage')
 | 
				
			||||||
 | 
					      .addSelect(
 | 
				
			||||||
 | 
					        `COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL AND assets.type = 'IMAGE'), 0)`,
 | 
				
			||||||
 | 
					        'usagePhotos',
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      .addSelect(
 | 
				
			||||||
 | 
					        `COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL AND assets.type = 'VIDEO'), 0)`,
 | 
				
			||||||
 | 
					        'usageVideos',
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
      .addSelect('users.quotaSizeInBytes', 'quotaSizeInBytes')
 | 
					      .addSelect('users.quotaSizeInBytes', 'quotaSizeInBytes')
 | 
				
			||||||
      .leftJoin('users.assets', 'assets')
 | 
					      .leftJoin('users.assets', 'assets')
 | 
				
			||||||
      .leftJoin('assets.exifInfo', 'exif')
 | 
					      .leftJoin('assets.exifInfo', 'exif')
 | 
				
			||||||
@ -119,6 +127,8 @@ export class UserRepository implements IUserRepository {
 | 
				
			|||||||
      stat.photos = Number(stat.photos);
 | 
					      stat.photos = Number(stat.photos);
 | 
				
			||||||
      stat.videos = Number(stat.videos);
 | 
					      stat.videos = Number(stat.videos);
 | 
				
			||||||
      stat.usage = Number(stat.usage);
 | 
					      stat.usage = Number(stat.usage);
 | 
				
			||||||
 | 
					      stat.usagePhotos = Number(stat.usagePhotos);
 | 
				
			||||||
 | 
					      stat.usageVideos = Number(stat.usageVideos);
 | 
				
			||||||
      stat.quotaSizeInBytes = stat.quotaSizeInBytes;
 | 
					      stat.quotaSizeInBytes = stat.quotaSizeInBytes;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -185,6 +185,8 @@ describe(ServerService.name, () => {
 | 
				
			|||||||
          photos: 10,
 | 
					          photos: 10,
 | 
				
			||||||
          videos: 11,
 | 
					          videos: 11,
 | 
				
			||||||
          usage: 12_345,
 | 
					          usage: 12_345,
 | 
				
			||||||
 | 
					          usagePhotos: 1,
 | 
				
			||||||
 | 
					          usageVideos: 11_345,
 | 
				
			||||||
          quotaSizeInBytes: 0,
 | 
					          quotaSizeInBytes: 0,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -193,6 +195,8 @@ describe(ServerService.name, () => {
 | 
				
			|||||||
          photos: 10,
 | 
					          photos: 10,
 | 
				
			||||||
          videos: 20,
 | 
					          videos: 20,
 | 
				
			||||||
          usage: 123_456,
 | 
					          usage: 123_456,
 | 
				
			||||||
 | 
					          usagePhotos: 100,
 | 
				
			||||||
 | 
					          usageVideos: 23_456,
 | 
				
			||||||
          quotaSizeInBytes: 0,
 | 
					          quotaSizeInBytes: 0,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -201,6 +205,8 @@ describe(ServerService.name, () => {
 | 
				
			|||||||
          photos: 100,
 | 
					          photos: 100,
 | 
				
			||||||
          videos: 0,
 | 
					          videos: 0,
 | 
				
			||||||
          usage: 987_654,
 | 
					          usage: 987_654,
 | 
				
			||||||
 | 
					          usagePhotos: 900,
 | 
				
			||||||
 | 
					          usageVideos: 87_654,
 | 
				
			||||||
          quotaSizeInBytes: 0,
 | 
					          quotaSizeInBytes: 0,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      ]);
 | 
					      ]);
 | 
				
			||||||
@ -209,11 +215,15 @@ describe(ServerService.name, () => {
 | 
				
			|||||||
        photos: 120,
 | 
					        photos: 120,
 | 
				
			||||||
        videos: 31,
 | 
					        videos: 31,
 | 
				
			||||||
        usage: 1_123_455,
 | 
					        usage: 1_123_455,
 | 
				
			||||||
 | 
					        usagePhotos: 1001,
 | 
				
			||||||
 | 
					        usageVideos: 122_455,
 | 
				
			||||||
        usageByUser: [
 | 
					        usageByUser: [
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            photos: 10,
 | 
					            photos: 10,
 | 
				
			||||||
            quotaSizeInBytes: 0,
 | 
					            quotaSizeInBytes: 0,
 | 
				
			||||||
            usage: 12_345,
 | 
					            usage: 12_345,
 | 
				
			||||||
 | 
					            usagePhotos: 1,
 | 
				
			||||||
 | 
					            usageVideos: 11_345,
 | 
				
			||||||
            userName: '1 User',
 | 
					            userName: '1 User',
 | 
				
			||||||
            userId: 'user1',
 | 
					            userId: 'user1',
 | 
				
			||||||
            videos: 11,
 | 
					            videos: 11,
 | 
				
			||||||
@ -222,6 +232,8 @@ describe(ServerService.name, () => {
 | 
				
			|||||||
            photos: 10,
 | 
					            photos: 10,
 | 
				
			||||||
            quotaSizeInBytes: 0,
 | 
					            quotaSizeInBytes: 0,
 | 
				
			||||||
            usage: 123_456,
 | 
					            usage: 123_456,
 | 
				
			||||||
 | 
					            usagePhotos: 100,
 | 
				
			||||||
 | 
					            usageVideos: 23_456,
 | 
				
			||||||
            userName: '2 User',
 | 
					            userName: '2 User',
 | 
				
			||||||
            userId: 'user2',
 | 
					            userId: 'user2',
 | 
				
			||||||
            videos: 20,
 | 
					            videos: 20,
 | 
				
			||||||
@ -230,6 +242,8 @@ describe(ServerService.name, () => {
 | 
				
			|||||||
            photos: 100,
 | 
					            photos: 100,
 | 
				
			||||||
            quotaSizeInBytes: 0,
 | 
					            quotaSizeInBytes: 0,
 | 
				
			||||||
            usage: 987_654,
 | 
					            usage: 987_654,
 | 
				
			||||||
 | 
					            usagePhotos: 900,
 | 
				
			||||||
 | 
					            usageVideos: 87_654,
 | 
				
			||||||
            userName: '3 User',
 | 
					            userName: '3 User',
 | 
				
			||||||
            userId: 'user3',
 | 
					            userId: 'user3',
 | 
				
			||||||
            videos: 0,
 | 
					            videos: 0,
 | 
				
			||||||
 | 
				
			|||||||
@ -126,11 +126,16 @@ export class ServerService extends BaseService {
 | 
				
			|||||||
      usage.photos = user.photos;
 | 
					      usage.photos = user.photos;
 | 
				
			||||||
      usage.videos = user.videos;
 | 
					      usage.videos = user.videos;
 | 
				
			||||||
      usage.usage = user.usage;
 | 
					      usage.usage = user.usage;
 | 
				
			||||||
 | 
					      usage.usagePhotos = user.usagePhotos;
 | 
				
			||||||
 | 
					      usage.usageVideos = user.usageVideos;
 | 
				
			||||||
      usage.quotaSizeInBytes = user.quotaSizeInBytes;
 | 
					      usage.quotaSizeInBytes = user.quotaSizeInBytes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      serverStats.photos += usage.photos;
 | 
					      serverStats.photos += usage.photos;
 | 
				
			||||||
      serverStats.videos += usage.videos;
 | 
					      serverStats.videos += usage.videos;
 | 
				
			||||||
      serverStats.usage += usage.usage;
 | 
					      serverStats.usage += usage.usage;
 | 
				
			||||||
 | 
					      serverStats.usagePhotos += usage.usagePhotos;
 | 
				
			||||||
 | 
					      serverStats.usageVideos += usage.usageVideos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      serverStats.usageByUser.push(usage);
 | 
					      serverStats.usageByUser.push(usage);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,8 @@
 | 
				
			|||||||
      photos: 0,
 | 
					      photos: 0,
 | 
				
			||||||
      videos: 0,
 | 
					      videos: 0,
 | 
				
			||||||
      usage: 0,
 | 
					      usage: 0,
 | 
				
			||||||
 | 
					      usagePhotos: 0,
 | 
				
			||||||
 | 
					      usageVideos: 0,
 | 
				
			||||||
      usageByUser: [],
 | 
					      usageByUser: [],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  }: Props = $props();
 | 
					  }: Props = $props();
 | 
				
			||||||
@ -105,8 +107,12 @@
 | 
				
			|||||||
            class="flex h-[50px] w-full place-items-center text-center odd:bg-immich-gray even:bg-immich-bg odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50"
 | 
					            class="flex h-[50px] w-full place-items-center text-center odd:bg-immich-gray even:bg-immich-bg odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50"
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <td class="w-1/4 text-ellipsis px-2 text-sm">{user.userName}</td>
 | 
					            <td class="w-1/4 text-ellipsis px-2 text-sm">{user.userName}</td>
 | 
				
			||||||
            <td class="w-1/4 text-ellipsis px-2 text-sm">{user.photos.toLocaleString($locale)}</td>
 | 
					            <td class="w-1/4 text-ellipsis px-2 text-sm"
 | 
				
			||||||
            <td class="w-1/4 text-ellipsis px-2 text-sm">{user.videos.toLocaleString($locale)}</td>
 | 
					              >{user.photos.toLocaleString($locale)} ({getByteUnitString(user.usagePhotos, $locale, 0)})</td
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					            <td class="w-1/4 text-ellipsis px-2 text-sm"
 | 
				
			||||||
 | 
					              >{user.videos.toLocaleString($locale)} ({getByteUnitString(user.usageVideos, $locale, 0)})</td
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
            <td class="w-1/4 text-ellipsis px-2 text-sm">
 | 
					            <td class="w-1/4 text-ellipsis px-2 text-sm">
 | 
				
			||||||
              {getByteUnitString(user.usage, $locale, 0)}
 | 
					              {getByteUnitString(user.usage, $locale, 0)}
 | 
				
			||||||
              {#if user.quotaSizeInBytes}
 | 
					              {#if user.quotaSizeInBytes}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user