Browse Source

fix(web+server): showing assets without thumbnail (#2652)

* fix(web+server): showing assets without thumbnail

* missed change
Michel Heusschen 2 năm trước cách đây
mục cha
commit
cab5477656

+ 4 - 2
mobile/openapi/doc/AssetApi.md

@@ -553,7 +553,7 @@ Name | Type | Description  | Notes
 [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
 [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
 
 
 # **getAllAssets**
 # **getAllAssets**
-> List<AssetResponseDto> getAllAssets(userId, isFavorite, isArchived, skip, ifNoneMatch)
+> List<AssetResponseDto> getAllAssets(userId, isFavorite, isArchived, withoutThumbs, skip, ifNoneMatch)
 
 
 
 
 
 
@@ -581,11 +581,12 @@ final api_instance = AssetApi();
 final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | 
 final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | 
 final isFavorite = true; // bool | 
 final isFavorite = true; // bool | 
 final isArchived = true; // bool | 
 final isArchived = true; // bool | 
+final withoutThumbs = true; // bool | Include assets without thumbnails
 final skip = 8.14; // num | 
 final skip = 8.14; // num | 
 final ifNoneMatch = ifNoneMatch_example; // String | ETag of data already cached on the client
 final ifNoneMatch = ifNoneMatch_example; // String | ETag of data already cached on the client
 
 
 try {
 try {
-    final result = api_instance.getAllAssets(userId, isFavorite, isArchived, skip, ifNoneMatch);
+    final result = api_instance.getAllAssets(userId, isFavorite, isArchived, withoutThumbs, skip, ifNoneMatch);
     print(result);
     print(result);
 } catch (e) {
 } catch (e) {
     print('Exception when calling AssetApi->getAllAssets: $e\n');
     print('Exception when calling AssetApi->getAllAssets: $e\n');
@@ -599,6 +600,7 @@ Name | Type | Description  | Notes
  **userId** | **String**|  | [optional] 
  **userId** | **String**|  | [optional] 
  **isFavorite** | **bool**|  | [optional] 
  **isFavorite** | **bool**|  | [optional] 
  **isArchived** | **bool**|  | [optional] 
  **isArchived** | **bool**|  | [optional] 
+ **withoutThumbs** | **bool**| Include assets without thumbnails | [optional] 
  **skip** | **num**|  | [optional] 
  **skip** | **num**|  | [optional] 
  **ifNoneMatch** | **String**| ETag of data already cached on the client | [optional] 
  **ifNoneMatch** | **String**| ETag of data already cached on the client | [optional] 
 
 

+ 1 - 0
mobile/openapi/doc/GetAssetCountByTimeBucketDto.md

@@ -10,6 +10,7 @@ Name | Type | Description | Notes
 ------------ | ------------- | ------------- | -------------
 ------------ | ------------- | ------------- | -------------
 **timeGroup** | [**TimeGroupEnum**](TimeGroupEnum.md) |  | 
 **timeGroup** | [**TimeGroupEnum**](TimeGroupEnum.md) |  | 
 **userId** | **String** |  | [optional] 
 **userId** | **String** |  | [optional] 
+**withoutThumbs** | **bool** | Include assets without thumbnails | [optional] 
 
 
 [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
 [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
 
 

+ 12 - 3
mobile/openapi/lib/api/asset_api.dart

@@ -525,11 +525,14 @@ class AssetApi {
   ///
   ///
   /// * [bool] isArchived:
   /// * [bool] isArchived:
   ///
   ///
+  /// * [bool] withoutThumbs:
+  ///   Include assets without thumbnails
+  ///
   /// * [num] skip:
   /// * [num] skip:
   ///
   ///
   /// * [String] ifNoneMatch:
   /// * [String] ifNoneMatch:
   ///   ETag of data already cached on the client
   ///   ETag of data already cached on the client
-  Future<Response> getAllAssetsWithHttpInfo({ String? userId, bool? isFavorite, bool? isArchived, num? skip, String? ifNoneMatch, }) async {
+  Future<Response> getAllAssetsWithHttpInfo({ String? userId, bool? isFavorite, bool? isArchived, bool? withoutThumbs, num? skip, String? ifNoneMatch, }) async {
     // ignore: prefer_const_declarations
     // ignore: prefer_const_declarations
     final path = r'/asset';
     final path = r'/asset';
 
 
@@ -549,6 +552,9 @@ class AssetApi {
     if (isArchived != null) {
     if (isArchived != null) {
       queryParams.addAll(_queryParams('', 'isArchived', isArchived));
       queryParams.addAll(_queryParams('', 'isArchived', isArchived));
     }
     }
+    if (withoutThumbs != null) {
+      queryParams.addAll(_queryParams('', 'withoutThumbs', withoutThumbs));
+    }
     if (skip != null) {
     if (skip != null) {
       queryParams.addAll(_queryParams('', 'skip', skip));
       queryParams.addAll(_queryParams('', 'skip', skip));
     }
     }
@@ -581,12 +587,15 @@ class AssetApi {
   ///
   ///
   /// * [bool] isArchived:
   /// * [bool] isArchived:
   ///
   ///
+  /// * [bool] withoutThumbs:
+  ///   Include assets without thumbnails
+  ///
   /// * [num] skip:
   /// * [num] skip:
   ///
   ///
   /// * [String] ifNoneMatch:
   /// * [String] ifNoneMatch:
   ///   ETag of data already cached on the client
   ///   ETag of data already cached on the client
-  Future<List<AssetResponseDto>?> getAllAssets({ String? userId, bool? isFavorite, bool? isArchived, num? skip, String? ifNoneMatch, }) async {
-    final response = await getAllAssetsWithHttpInfo( userId: userId, isFavorite: isFavorite, isArchived: isArchived, skip: skip, ifNoneMatch: ifNoneMatch, );
+  Future<List<AssetResponseDto>?> getAllAssets({ String? userId, bool? isFavorite, bool? isArchived, bool? withoutThumbs, num? skip, String? ifNoneMatch, }) async {
+    final response = await getAllAssetsWithHttpInfo( userId: userId, isFavorite: isFavorite, isArchived: isArchived, withoutThumbs: withoutThumbs, skip: skip, ifNoneMatch: ifNoneMatch, );
     if (response.statusCode >= HttpStatus.badRequest) {
     if (response.statusCode >= HttpStatus.badRequest) {
       throw ApiException(response.statusCode, await _decodeBodyBytes(response));
       throw ApiException(response.statusCode, await _decodeBodyBytes(response));
     }
     }

+ 21 - 3
mobile/openapi/lib/model/get_asset_count_by_time_bucket_dto.dart

@@ -15,6 +15,7 @@ class GetAssetCountByTimeBucketDto {
   GetAssetCountByTimeBucketDto({
   GetAssetCountByTimeBucketDto({
     required this.timeGroup,
     required this.timeGroup,
     this.userId,
     this.userId,
+    this.withoutThumbs,
   });
   });
 
 
   TimeGroupEnum timeGroup;
   TimeGroupEnum timeGroup;
@@ -27,19 +28,30 @@ class GetAssetCountByTimeBucketDto {
   ///
   ///
   String? userId;
   String? userId;
 
 
+  /// Include assets without thumbnails
+  ///
+  /// Please note: This property should have been non-nullable! Since the specification file
+  /// does not include a default value (using the "default:" property), however, the generated
+  /// source code must fall back to having a nullable type.
+  /// Consider adding a "default:" property in the specification file to hide this note.
+  ///
+  bool? withoutThumbs;
+
   @override
   @override
   bool operator ==(Object other) => identical(this, other) || other is GetAssetCountByTimeBucketDto &&
   bool operator ==(Object other) => identical(this, other) || other is GetAssetCountByTimeBucketDto &&
      other.timeGroup == timeGroup &&
      other.timeGroup == timeGroup &&
-     other.userId == userId;
+     other.userId == userId &&
+     other.withoutThumbs == withoutThumbs;
 
 
   @override
   @override
   int get hashCode =>
   int get hashCode =>
     // ignore: unnecessary_parenthesis
     // ignore: unnecessary_parenthesis
     (timeGroup.hashCode) +
     (timeGroup.hashCode) +
-    (userId == null ? 0 : userId!.hashCode);
+    (userId == null ? 0 : userId!.hashCode) +
+    (withoutThumbs == null ? 0 : withoutThumbs!.hashCode);
 
 
   @override
   @override
-  String toString() => 'GetAssetCountByTimeBucketDto[timeGroup=$timeGroup, userId=$userId]';
+  String toString() => 'GetAssetCountByTimeBucketDto[timeGroup=$timeGroup, userId=$userId, withoutThumbs=$withoutThumbs]';
 
 
   Map<String, dynamic> toJson() {
   Map<String, dynamic> toJson() {
     final json = <String, dynamic>{};
     final json = <String, dynamic>{};
@@ -49,6 +61,11 @@ class GetAssetCountByTimeBucketDto {
     } else {
     } else {
       // json[r'userId'] = null;
       // json[r'userId'] = null;
     }
     }
+    if (this.withoutThumbs != null) {
+      json[r'withoutThumbs'] = this.withoutThumbs;
+    } else {
+      // json[r'withoutThumbs'] = null;
+    }
     return json;
     return json;
   }
   }
 
 
@@ -73,6 +90,7 @@ class GetAssetCountByTimeBucketDto {
       return GetAssetCountByTimeBucketDto(
       return GetAssetCountByTimeBucketDto(
         timeGroup: TimeGroupEnum.fromJson(json[r'timeGroup'])!,
         timeGroup: TimeGroupEnum.fromJson(json[r'timeGroup'])!,
         userId: mapValueOfType<String>(json, r'userId'),
         userId: mapValueOfType<String>(json, r'userId'),
+        withoutThumbs: mapValueOfType<bool>(json, r'withoutThumbs'),
       );
       );
     }
     }
     return null;
     return null;

+ 1 - 1
mobile/openapi/test/asset_api_test.dart

@@ -72,7 +72,7 @@ void main() {
 
 
     // Get all AssetEntity belong to the user
     // Get all AssetEntity belong to the user
     //
     //
-    //Future<List<AssetResponseDto>> getAllAssets({ String userId, bool isFavorite, bool isArchived, num skip, String ifNoneMatch }) async
+    //Future<List<AssetResponseDto>> getAllAssets({ String userId, bool isFavorite, bool isArchived, bool withoutThumbs, num skip, String ifNoneMatch }) async
     test('test getAllAssets', () async {
     test('test getAllAssets', () async {
       // TODO
       // TODO
     });
     });

+ 6 - 0
mobile/openapi/test/get_asset_count_by_time_bucket_dto_test.dart

@@ -26,6 +26,12 @@ void main() {
       // TODO
       // TODO
     });
     });
 
 
+    // Include assets without thumbnails
+    // bool withoutThumbs
+    test('to test the property `withoutThumbs`', () async {
+      // TODO
+    });
+
 
 
   });
   });
 
 

+ 25 - 26
server/apps/immich/src/api-v1/asset/asset-repository.ts

@@ -6,7 +6,7 @@ import { InjectRepository } from '@nestjs/typeorm';
 import { Repository } from 'typeorm/repository/Repository';
 import { Repository } from 'typeorm/repository/Repository';
 import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
 import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
 import { AssetCountByTimeBucket } from './response-dto/asset-count-by-time-group-response.dto';
 import { AssetCountByTimeBucket } from './response-dto/asset-count-by-time-group-response.dto';
-import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto';
+import { GetAssetCountByTimeBucketDto, TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto';
 import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
 import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
 import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto';
 import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto';
 import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto';
 import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto';
@@ -37,7 +37,7 @@ export interface IAssetRepository {
   getLocationsByUserId(userId: string): Promise<CuratedLocationsResponseDto[]>;
   getLocationsByUserId(userId: string): Promise<CuratedLocationsResponseDto[]>;
   getDetectedObjectsByUserId(userId: string): Promise<CuratedObjectsResponseDto[]>;
   getDetectedObjectsByUserId(userId: string): Promise<CuratedObjectsResponseDto[]>;
   getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]>;
   getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]>;
-  getAssetCountByTimeBucket(userId: string, timeBucket: TimeGroupEnum): Promise<AssetCountByTimeBucket[]>;
+  getAssetCountByTimeBucket(userId: string, dto: GetAssetCountByTimeBucketDto): Promise<AssetCountByTimeBucket[]>;
   getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto>;
   getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto>;
   getArchivedAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto>;
   getArchivedAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto>;
   getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]>;
   getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]>;
@@ -119,36 +119,35 @@ export class AssetRepository implements IAssetRepository {
     return builder.getMany();
     return builder.getMany();
   }
   }
 
 
-  async getAssetCountByTimeBucket(userId: string, timeBucket: TimeGroupEnum) {
-    let result: AssetCountByTimeBucket[] = [];
+  async getAssetCountByTimeBucket(
+    userId: string,
+    dto: GetAssetCountByTimeBucketDto,
+  ): Promise<AssetCountByTimeBucket[]> {
+    const builder = this.assetRepository
+      .createQueryBuilder('asset')
+      .select(`COUNT(asset.id)::int`, 'count')
+      .where('"ownerId" = :userId', { userId: userId })
+      .andWhere('asset.isVisible = true')
+      .andWhere('asset.isArchived = false');
 
 
-    if (timeBucket === TimeGroupEnum.Month) {
-      result = await this.assetRepository
-        .createQueryBuilder('asset')
-        .select(`COUNT(asset.id)::int`, 'count')
+    // Using a parameter for this doesn't work https://github.com/typeorm/typeorm/issues/7308
+    if (dto.timeGroup === TimeGroupEnum.Month) {
+      builder
         .addSelect(`date_trunc('month', "fileCreatedAt")`, 'timeBucket')
         .addSelect(`date_trunc('month', "fileCreatedAt")`, 'timeBucket')
-        .where('"ownerId" = :userId', { userId: userId })
-        .andWhere('asset.resizePath is not NULL')
-        .andWhere('asset.isVisible = true')
-        .andWhere('asset.isArchived = false')
         .groupBy(`date_trunc('month', "fileCreatedAt")`)
         .groupBy(`date_trunc('month', "fileCreatedAt")`)
-        .orderBy(`date_trunc('month', "fileCreatedAt")`, 'DESC')
-        .getRawMany();
-    } else if (timeBucket === TimeGroupEnum.Day) {
-      result = await this.assetRepository
-        .createQueryBuilder('asset')
-        .select(`COUNT(asset.id)::int`, 'count')
+        .orderBy(`date_trunc('month', "fileCreatedAt")`, 'DESC');
+    } else if (dto.timeGroup === TimeGroupEnum.Day) {
+      builder
         .addSelect(`date_trunc('day', "fileCreatedAt")`, 'timeBucket')
         .addSelect(`date_trunc('day', "fileCreatedAt")`, 'timeBucket')
-        .where('"ownerId" = :userId', { userId: userId })
-        .andWhere('asset.resizePath is not NULL')
-        .andWhere('asset.isVisible = true')
-        .andWhere('asset.isArchived = false')
         .groupBy(`date_trunc('day', "fileCreatedAt")`)
         .groupBy(`date_trunc('day', "fileCreatedAt")`)
-        .orderBy(`date_trunc('day', "fileCreatedAt")`, 'DESC')
-        .getRawMany();
+        .orderBy(`date_trunc('day', "fileCreatedAt")`, 'DESC');
+    }
+
+    if (!dto.withoutThumbs) {
+      builder.andWhere('asset.resizePath is not NULL');
     }
     }
 
 
-    return result;
+    return builder.getRawMany();
   }
   }
 
 
   async getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]> {
   async getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]> {
@@ -231,7 +230,7 @@ export class AssetRepository implements IAssetRepository {
     return this.assetRepository.find({
     return this.assetRepository.find({
       where: {
       where: {
         ownerId,
         ownerId,
-        resizePath: Not(IsNull()),
+        resizePath: dto.withoutThumbs ? undefined : Not(IsNull()),
         isVisible: true,
         isVisible: true,
         isFavorite: dto.isFavorite,
         isFavorite: dto.isFavorite,
         isArchived: dto.isArchived,
         isArchived: dto.isArchived,

+ 1 - 1
server/apps/immich/src/api-v1/asset/asset.service.ts

@@ -533,7 +533,7 @@ export class AssetService {
 
 
     const result = await this._assetRepository.getAssetCountByTimeBucket(
     const result = await this._assetRepository.getAssetCountByTimeBucket(
       getAssetCountByTimeBucketDto.userId || authUser.id,
       getAssetCountByTimeBucketDto.userId || authUser.id,
-      getAssetCountByTimeBucketDto.timeGroup,
+      getAssetCountByTimeBucketDto,
     );
     );
 
 
     return mapAssetCountByTimeBucket(result);
     return mapAssetCountByTimeBucket(result);

+ 8 - 0
server/apps/immich/src/api-v1/asset/dto/asset-search.dto.ts

@@ -16,6 +16,14 @@ export class AssetSearchDto {
   @Transform(toBoolean)
   @Transform(toBoolean)
   isArchived?: boolean;
   isArchived?: boolean;
 
 
+  /**
+   * Include assets without thumbnails
+   */
+  @IsOptional()
+  @IsBoolean()
+  @Transform(toBoolean)
+  withoutThumbs?: boolean;
+
   @IsOptional()
   @IsOptional()
   @IsNumber()
   @IsNumber()
   skip?: number;
   skip?: number;

+ 11 - 1
server/apps/immich/src/api-v1/asset/dto/get-asset-count-by-time-bucket.dto.ts

@@ -1,5 +1,7 @@
 import { ApiProperty } from '@nestjs/swagger';
 import { ApiProperty } from '@nestjs/swagger';
-import { IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
+import { Transform } from 'class-transformer';
+import { IsBoolean, IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
+import { toBoolean } from '../../../utils/transform.util';
 
 
 export enum TimeGroupEnum {
 export enum TimeGroupEnum {
   Day = 'day',
   Day = 'day',
@@ -19,4 +21,12 @@ export class GetAssetCountByTimeBucketDto {
   @IsUUID('4')
   @IsUUID('4')
   @ApiProperty({ format: 'uuid' })
   @ApiProperty({ format: 'uuid' })
   userId?: string;
   userId?: string;
+
+  /**
+   * Include assets without thumbnails
+   */
+  @IsOptional()
+  @IsBoolean()
+  @Transform(toBoolean)
+  withoutThumbs?: boolean;
 }
 }

+ 13 - 0
server/immich-openapi-specs.json

@@ -3380,6 +3380,15 @@
               "type": "boolean"
               "type": "boolean"
             }
             }
           },
           },
+          {
+            "name": "withoutThumbs",
+            "required": false,
+            "in": "query",
+            "description": "Include assets without thumbnails",
+            "schema": {
+              "type": "boolean"
+            }
+          },
           {
           {
             "name": "skip",
             "name": "skip",
             "required": false,
             "required": false,
@@ -6221,6 +6230,10 @@
           "userId": {
           "userId": {
             "type": "string",
             "type": "string",
             "format": "uuid"
             "format": "uuid"
+          },
+          "withoutThumbs": {
+            "type": "boolean",
+            "description": "Include assets without thumbnails"
           }
           }
         },
         },
         "required": [
         "required": [

+ 26 - 6
web/src/api/open-api/api.ts

@@ -1396,6 +1396,12 @@ export interface GetAssetCountByTimeBucketDto {
      * @memberof GetAssetCountByTimeBucketDto
      * @memberof GetAssetCountByTimeBucketDto
      */
      */
     'userId'?: string;
     'userId'?: string;
+    /**
+     * Include assets without thumbnails
+     * @type {boolean}
+     * @memberof GetAssetCountByTimeBucketDto
+     */
+    'withoutThumbs'?: boolean;
 }
 }
 
 
 
 
@@ -4999,12 +5005,13 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
          * @param {string} [userId] 
          * @param {string} [userId] 
          * @param {boolean} [isFavorite] 
          * @param {boolean} [isFavorite] 
          * @param {boolean} [isArchived] 
          * @param {boolean} [isArchived] 
+         * @param {boolean} [withoutThumbs] Include assets without thumbnails
          * @param {number} [skip] 
          * @param {number} [skip] 
          * @param {string} [ifNoneMatch] ETag of data already cached on the client
          * @param {string} [ifNoneMatch] ETag of data already cached on the client
          * @param {*} [options] Override http request option.
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          * @throws {RequiredError}
          */
          */
-        getAllAssets: async (userId?: string, isFavorite?: boolean, isArchived?: boolean, skip?: number, ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+        getAllAssets: async (userId?: string, isFavorite?: boolean, isArchived?: boolean, withoutThumbs?: boolean, skip?: number, ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
             const localVarPath = `/asset`;
             const localVarPath = `/asset`;
             // use dummy base URL string because the URL constructor only accepts absolute URLs.
             // use dummy base URL string because the URL constructor only accepts absolute URLs.
             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@@ -5038,6 +5045,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
                 localVarQueryParameter['isArchived'] = isArchived;
                 localVarQueryParameter['isArchived'] = isArchived;
             }
             }
 
 
+            if (withoutThumbs !== undefined) {
+                localVarQueryParameter['withoutThumbs'] = withoutThumbs;
+            }
+
             if (skip !== undefined) {
             if (skip !== undefined) {
                 localVarQueryParameter['skip'] = skip;
                 localVarQueryParameter['skip'] = skip;
             }
             }
@@ -5970,13 +5981,14 @@ export const AssetApiFp = function(configuration?: Configuration) {
          * @param {string} [userId] 
          * @param {string} [userId] 
          * @param {boolean} [isFavorite] 
          * @param {boolean} [isFavorite] 
          * @param {boolean} [isArchived] 
          * @param {boolean} [isArchived] 
+         * @param {boolean} [withoutThumbs] Include assets without thumbnails
          * @param {number} [skip] 
          * @param {number} [skip] 
          * @param {string} [ifNoneMatch] ETag of data already cached on the client
          * @param {string} [ifNoneMatch] ETag of data already cached on the client
          * @param {*} [options] Override http request option.
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          * @throws {RequiredError}
          */
          */
-        async getAllAssets(userId?: string, isFavorite?: boolean, isArchived?: boolean, skip?: number, ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> {
-            const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(userId, isFavorite, isArchived, skip, ifNoneMatch, options);
+        async getAllAssets(userId?: string, isFavorite?: boolean, isArchived?: boolean, withoutThumbs?: boolean, skip?: number, ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(userId, isFavorite, isArchived, withoutThumbs, skip, ifNoneMatch, options);
             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
         },
         },
         /**
         /**
@@ -6259,13 +6271,14 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
          * @param {string} [userId] 
          * @param {string} [userId] 
          * @param {boolean} [isFavorite] 
          * @param {boolean} [isFavorite] 
          * @param {boolean} [isArchived] 
          * @param {boolean} [isArchived] 
+         * @param {boolean} [withoutThumbs] Include assets without thumbnails
          * @param {number} [skip] 
          * @param {number} [skip] 
          * @param {string} [ifNoneMatch] ETag of data already cached on the client
          * @param {string} [ifNoneMatch] ETag of data already cached on the client
          * @param {*} [options] Override http request option.
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          * @throws {RequiredError}
          */
          */
-        getAllAssets(userId?: string, isFavorite?: boolean, isArchived?: boolean, skip?: number, ifNoneMatch?: string, options?: any): AxiosPromise<Array<AssetResponseDto>> {
-            return localVarFp.getAllAssets(userId, isFavorite, isArchived, skip, ifNoneMatch, options).then((request) => request(axios, basePath));
+        getAllAssets(userId?: string, isFavorite?: boolean, isArchived?: boolean, withoutThumbs?: boolean, skip?: number, ifNoneMatch?: string, options?: any): AxiosPromise<Array<AssetResponseDto>> {
+            return localVarFp.getAllAssets(userId, isFavorite, isArchived, withoutThumbs, skip, ifNoneMatch, options).then((request) => request(axios, basePath));
         },
         },
         /**
         /**
          * 
          * 
@@ -6627,6 +6640,13 @@ export interface AssetApiGetAllAssetsRequest {
      */
      */
     readonly isArchived?: boolean
     readonly isArchived?: boolean
 
 
+    /**
+     * Include assets without thumbnails
+     * @type {boolean}
+     * @memberof AssetApiGetAllAssets
+     */
+    readonly withoutThumbs?: boolean
+
     /**
     /**
      * 
      * 
      * @type {number}
      * @type {number}
@@ -7071,7 +7091,7 @@ export class AssetApi extends BaseAPI {
      * @memberof AssetApi
      * @memberof AssetApi
      */
      */
     public getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig) {
     public getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig) {
-        return AssetApiFp(this.configuration).getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.skip, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath));
+        return AssetApiFp(this.configuration).getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.withoutThumbs, requestParameters.skip, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath));
     }
     }
 
 
     /**
     /**

+ 2 - 1
web/src/lib/components/photos-page/asset-grid.svelte

@@ -29,7 +29,8 @@
 		const { data: assetCountByTimebucket } = await api.assetApi.getAssetCountByTimeBucket({
 		const { data: assetCountByTimebucket } = await api.assetApi.getAssetCountByTimeBucket({
 			getAssetCountByTimeBucketDto: {
 			getAssetCountByTimeBucketDto: {
 				timeGroup: TimeGroupEnum.Month,
 				timeGroup: TimeGroupEnum.Month,
-				userId: user?.id
+				userId: user?.id,
+				withoutThumbs: true
 			}
 			}
 		});
 		});
 		bucketInfo = assetCountByTimebucket;
 		bucketInfo = assetCountByTimebucket;

+ 4 - 1
web/src/lib/components/shared-components/side-bar/side-bar.svelte

@@ -30,7 +30,10 @@
 
 
 	const getFavoriteCount = async () => {
 	const getFavoriteCount = async () => {
 		try {
 		try {
-			const { data: assets } = await api.assetApi.getAllAssets({ isFavorite: true });
+			const { data: assets } = await api.assetApi.getAllAssets({
+				isFavorite: true,
+				withoutThumbs: true
+			});
 
 
 			return {
 			return {
 				favorites: assets.length
 				favorites: assets.length

+ 4 - 1
web/src/routes/(user)/archive/+page.svelte

@@ -26,7 +26,10 @@
 
 
 	onMount(async () => {
 	onMount(async () => {
 		try {
 		try {
-			const { data: assets } = await api.assetApi.getAllAssets({ isArchived: true });
+			const { data: assets } = await api.assetApi.getAllAssets({
+				isArchived: true,
+				withoutThumbs: true
+			});
 			$archivedAsset = assets;
 			$archivedAsset = assets;
 		} catch {
 		} catch {
 			handleError(Error, 'Unable to load archived assets');
 			handleError(Error, 'Unable to load archived assets');

+ 4 - 1
web/src/routes/(user)/favorites/+page.svelte

@@ -28,7 +28,10 @@
 
 
 	onMount(async () => {
 	onMount(async () => {
 		try {
 		try {
-			const { data: assets } = await api.assetApi.getAllAssets({ isFavorite: true });
+			const { data: assets } = await api.assetApi.getAllAssets({
+				isFavorite: true,
+				withoutThumbs: true
+			});
 			favorites = assets;
 			favorites = assets;
 		} catch {
 		} catch {
 			handleError(Error, 'Unable to load favorites');
 			handleError(Error, 'Unable to load favorites');