You've already forked immich
							
							
				mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 00:18:28 +02:00 
			
		
		
		
	feat(server): random assets API (#4184)
* feat(server): get random assets API * Fix tests * Use correct validation annotation * Fix offset use in query * Update API specs * Fix typo * Random assets e2e tests * Improve e2e tests
This commit is contained in:
		
							
								
								
									
										87
									
								
								cli/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										87
									
								
								cli/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -6303,6 +6303,49 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration | ||||
| 
 | ||||
| 
 | ||||
|      | ||||
|             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||
|             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||
|             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||
| 
 | ||||
|             return { | ||||
|                 url: toPathString(localVarUrlObj), | ||||
|                 options: localVarRequestOptions, | ||||
|             }; | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {number} [count]  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         getRandom: async (count?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|             const localVarPath = `/asset/random`; | ||||
|             // use dummy base URL string because the URL constructor only accepts absolute URLs.
 | ||||
|             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); | ||||
|             let baseOptions; | ||||
|             if (configuration) { | ||||
|                 baseOptions = configuration.baseOptions; | ||||
|             } | ||||
| 
 | ||||
|             const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; | ||||
|             const localVarHeaderParameter = {} as any; | ||||
|             const localVarQueryParameter = {} as any; | ||||
| 
 | ||||
|             // authentication cookie required
 | ||||
| 
 | ||||
|             // authentication api_key required
 | ||||
|             await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) | ||||
| 
 | ||||
|             // authentication bearer required
 | ||||
|             // http bearer authentication required
 | ||||
|             await setBearerAuthToObject(localVarHeaderParameter, configuration) | ||||
| 
 | ||||
|             if (count !== undefined) { | ||||
|                 localVarQueryParameter['count'] = count; | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|      | ||||
|             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||
|             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||
|             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||
| @@ -7043,6 +7086,16 @@ export const AssetApiFp = function(configuration?: Configuration) { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.getMemoryLane(timestamp, options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {number} [count]  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         async getRandom(count?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.getRandom(count, options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {TimeBucketSize} size  | ||||
| @@ -7318,6 +7371,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath | ||||
|         getMemoryLane(requestParameters: AssetApiGetMemoryLaneRequest, options?: AxiosRequestConfig): AxiosPromise<Array<MemoryLaneResponseDto>> { | ||||
|             return localVarFp.getMemoryLane(requestParameters.timestamp, options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {AssetApiGetRandomRequest} requestParameters Request parameters. | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         getRandom(requestParameters: AssetApiGetRandomRequest = {}, options?: AxiosRequestConfig): AxiosPromise<Array<AssetResponseDto>> { | ||||
|             return localVarFp.getRandom(requestParameters.count, options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {AssetApiGetTimeBucketsRequest} requestParameters Request parameters. | ||||
| @@ -7752,6 +7814,20 @@ export interface AssetApiGetMemoryLaneRequest { | ||||
|     readonly timestamp: string | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Request parameters for getRandom operation in AssetApi. | ||||
|  * @export | ||||
|  * @interface AssetApiGetRandomRequest | ||||
|  */ | ||||
| export interface AssetApiGetRandomRequest { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {number} | ||||
|      * @memberof AssetApiGetRandom | ||||
|      */ | ||||
|     readonly count?: number | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Request parameters for getTimeBuckets operation in AssetApi. | ||||
|  * @export | ||||
| @@ -8244,6 +8320,17 @@ export class AssetApi extends BaseAPI { | ||||
|         return AssetApiFp(this.configuration).getMemoryLane(requestParameters.timestamp, options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      * @param {AssetApiGetRandomRequest} requestParameters Request parameters. | ||||
|      * @param {*} [options] Override http request option. | ||||
|      * @throws {RequiredError} | ||||
|      * @memberof AssetApi | ||||
|      */ | ||||
|     public getRandom(requestParameters: AssetApiGetRandomRequest = {}, options?: AxiosRequestConfig) { | ||||
|         return AssetApiFp(this.configuration).getRandom(requestParameters.count, options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      * @param {AssetApiGetTimeBucketsRequest} requestParameters Request parameters. | ||||
|   | ||||
							
								
								
									
										1
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							| @@ -104,6 +104,7 @@ Class | Method | HTTP request | Description | ||||
| *AssetApi* | [**getDownloadInfo**](doc//AssetApi.md#getdownloadinfo) | **POST** /asset/download/info |  | ||||
| *AssetApi* | [**getMapMarkers**](doc//AssetApi.md#getmapmarkers) | **GET** /asset/map-marker |  | ||||
| *AssetApi* | [**getMemoryLane**](doc//AssetApi.md#getmemorylane) | **GET** /asset/memory-lane |  | ||||
| *AssetApi* | [**getRandom**](doc//AssetApi.md#getrandom) | **GET** /asset/random |  | ||||
| *AssetApi* | [**getTimeBuckets**](doc//AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets |  | ||||
| *AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |  | ||||
| *AssetApi* | [**importFile**](doc//AssetApi.md#importfile) | **POST** /asset/import |  | ||||
|   | ||||
							
								
								
									
										56
									
								
								mobile/openapi/doc/AssetApi.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										56
									
								
								mobile/openapi/doc/AssetApi.md
									
									
									
										generated
									
									
									
								
							| @@ -26,6 +26,7 @@ Method | HTTP request | Description | ||||
| [**getDownloadInfo**](AssetApi.md#getdownloadinfo) | **POST** /asset/download/info |  | ||||
| [**getMapMarkers**](AssetApi.md#getmapmarkers) | **GET** /asset/map-marker |  | ||||
| [**getMemoryLane**](AssetApi.md#getmemorylane) | **GET** /asset/memory-lane |  | ||||
| [**getRandom**](AssetApi.md#getrandom) | **GET** /asset/random |  | ||||
| [**getTimeBuckets**](AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets |  | ||||
| [**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |  | ||||
| [**importFile**](AssetApi.md#importfile) | **POST** /asset/import |  | ||||
| @@ -1014,6 +1015,61 @@ 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) | ||||
| 
 | ||||
| # **getRandom** | ||||
| > List<AssetResponseDto> getRandom(count) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ### Example | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| // TODO Configure API key authorization: cookie | ||||
| //defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY'; | ||||
| // uncomment below to setup prefix (e.g. Bearer) for API key, if needed | ||||
| //defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer'; | ||||
| // TODO Configure API key authorization: api_key | ||||
| //defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY'; | ||||
| // uncomment below to setup prefix (e.g. Bearer) for API key, if needed | ||||
| //defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer'; | ||||
| // TODO Configure HTTP Bearer authorization: bearer | ||||
| // Case 1. Use String Token | ||||
| //defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); | ||||
| // Case 2. Use Function which generate token. | ||||
| // String yourTokenGeneratorFunction() { ... } | ||||
| //defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction); | ||||
| 
 | ||||
| final api_instance = AssetApi(); | ||||
| final count = 8.14; // num |  | ||||
| 
 | ||||
| try { | ||||
|     final result = api_instance.getRandom(count); | ||||
|     print(result); | ||||
| } catch (e) { | ||||
|     print('Exception when calling AssetApi->getRandom: $e\n'); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### Parameters | ||||
| 
 | ||||
| Name | Type | Description  | Notes | ||||
| ------------- | ------------- | ------------- | ------------- | ||||
|  **count** | **num**|  | [optional]  | ||||
| 
 | ||||
| ### Return type | ||||
| 
 | ||||
| [**List<AssetResponseDto>**](AssetResponseDto.md) | ||||
| 
 | ||||
| ### Authorization | ||||
| 
 | ||||
| [cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) | ||||
| 
 | ||||
| ### HTTP request headers | ||||
| 
 | ||||
|  - **Content-Type**: Not defined | ||||
|  - **Accept**: application/json | ||||
| 
 | ||||
| [[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) | ||||
| 
 | ||||
| # **getTimeBuckets** | ||||
| > List<TimeBucketResponseDto> getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, key) | ||||
| 
 | ||||
|   | ||||
							
								
								
									
										54
									
								
								mobile/openapi/lib/api/asset_api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										54
									
								
								mobile/openapi/lib/api/asset_api.dart
									
									
									
										generated
									
									
									
								
							| @@ -1028,6 +1028,60 @@ class AssetApi { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   /// Performs an HTTP 'GET /asset/random' operation and returns the [Response]. | ||||
|   /// Parameters: | ||||
|   /// | ||||
|   /// * [num] count: | ||||
|   Future<Response> getRandomWithHttpInfo({ num? count, }) async { | ||||
|     // ignore: prefer_const_declarations | ||||
|     final path = r'/asset/random'; | ||||
| 
 | ||||
|     // ignore: prefer_final_locals | ||||
|     Object? postBody; | ||||
| 
 | ||||
|     final queryParams = <QueryParam>[]; | ||||
|     final headerParams = <String, String>{}; | ||||
|     final formParams = <String, String>{}; | ||||
| 
 | ||||
|     if (count != null) { | ||||
|       queryParams.addAll(_queryParams('', 'count', count)); | ||||
|     } | ||||
| 
 | ||||
|     const contentTypes = <String>[]; | ||||
| 
 | ||||
| 
 | ||||
|     return apiClient.invokeAPI( | ||||
|       path, | ||||
|       'GET', | ||||
|       queryParams, | ||||
|       postBody, | ||||
|       headerParams, | ||||
|       formParams, | ||||
|       contentTypes.isEmpty ? null : contentTypes.first, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /// Parameters: | ||||
|   /// | ||||
|   /// * [num] count: | ||||
|   Future<List<AssetResponseDto>?> getRandom({ num? count, }) async { | ||||
|     final response = await getRandomWithHttpInfo( count: count, ); | ||||
|     if (response.statusCode >= HttpStatus.badRequest) { | ||||
|       throw ApiException(response.statusCode, await _decodeBodyBytes(response)); | ||||
|     } | ||||
|     // When a remote server returns no body with a status of 204, we shall not decode it. | ||||
|     // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" | ||||
|     // FormatException when trying to decode an empty string. | ||||
|     if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { | ||||
|       final responseBody = await _decodeBodyBytes(response); | ||||
|       return (await apiClient.deserializeAsync(responseBody, 'List<AssetResponseDto>') as List) | ||||
|         .cast<AssetResponseDto>() | ||||
|         .toList(); | ||||
| 
 | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   /// Performs an HTTP 'GET /asset/time-buckets' operation and returns the [Response]. | ||||
|   /// Parameters: | ||||
|   /// | ||||
|   | ||||
							
								
								
									
										5
									
								
								mobile/openapi/test/asset_api_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/asset_api_test.dart
									
									
									
										generated
									
									
									
								
							| @@ -112,6 +112,11 @@ void main() { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     //Future<List<AssetResponseDto>> getRandom({ num count }) async | ||||
|     test('test getRandom', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     //Future<List<TimeBucketResponseDto>> getTimeBuckets(TimeBucketSize size, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, String key }) async | ||||
|     test('test getTimeBuckets', () async { | ||||
|       // TODO | ||||
|   | ||||
| @@ -1510,6 +1510,50 @@ | ||||
|         ] | ||||
|       } | ||||
|     }, | ||||
|     "/asset/random": { | ||||
|       "get": { | ||||
|         "operationId": "getRandom", | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "name": "count", | ||||
|             "required": false, | ||||
|             "in": "query", | ||||
|             "schema": { | ||||
|               "type": "number" | ||||
|             } | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "200": { | ||||
|             "content": { | ||||
|               "application/json": { | ||||
|                 "schema": { | ||||
|                   "items": { | ||||
|                     "$ref": "#/components/schemas/AssetResponseDto" | ||||
|                   }, | ||||
|                   "type": "array" | ||||
|                 } | ||||
|               } | ||||
|             }, | ||||
|             "description": "" | ||||
|           } | ||||
|         }, | ||||
|         "security": [ | ||||
|           { | ||||
|             "bearer": [] | ||||
|           }, | ||||
|           { | ||||
|             "cookie": [] | ||||
|           }, | ||||
|           { | ||||
|             "api_key": [] | ||||
|           } | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "Asset" | ||||
|         ] | ||||
|       } | ||||
|     }, | ||||
|     "/asset/search": { | ||||
|       "post": { | ||||
|         "operationId": "searchAsset", | ||||
|   | ||||
| @@ -78,6 +78,7 @@ export interface IAssetRepository { | ||||
|   getByUserId(pagination: PaginationOptions, userId: string): Paginated<AssetEntity>; | ||||
|   getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity>; | ||||
|   getWith(pagination: PaginationOptions, property: WithProperty, libraryId?: string): Paginated<AssetEntity>; | ||||
|   getRandom(userId: string, count: number): Promise<AssetEntity[]>; | ||||
|   getFirstAssetForAlbumId(albumId: string): Promise<AssetEntity | null>; | ||||
|   getLastUpdatedAssetForAlbumId(albumId: string): Promise<AssetEntity | null>; | ||||
|   getByLibraryId(libraryIds: string[]): Promise<AssetEntity[]>; | ||||
|   | ||||
| @@ -284,6 +284,11 @@ export class AssetService { | ||||
|     return mapStats(stats); | ||||
|   } | ||||
|  | ||||
|   async getRandom(authUser: AuthUserDto, count: number): Promise<AssetResponseDto[]> { | ||||
|     const assets = await this.assetRepository.getRandom(authUser.id, count); | ||||
|     return assets.map((a) => mapAsset(a)); | ||||
|   } | ||||
|  | ||||
|   async update(authUser: AuthUserDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> { | ||||
|     await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, id); | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import { IsBoolean, IsString } from 'class-validator'; | ||||
| import { Type } from 'class-transformer'; | ||||
| import { IsBoolean, IsInt, IsPositive, IsString } from 'class-validator'; | ||||
| import { Optional } from '../../domain.util'; | ||||
| import { BulkIdsDto } from '../response-dto'; | ||||
|  | ||||
| @@ -25,3 +26,11 @@ export class UpdateAssetDto { | ||||
|   @IsString() | ||||
|   description?: string; | ||||
| } | ||||
|  | ||||
| export class RandomAssetsDto { | ||||
|   @Optional() | ||||
|   @IsInt() | ||||
|   @IsPositive() | ||||
|   @Type(() => Number) | ||||
|   count?: number; | ||||
| } | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import { | ||||
|   MapMarkerResponseDto, | ||||
|   MemoryLaneDto, | ||||
|   MemoryLaneResponseDto, | ||||
|   RandomAssetsDto, | ||||
|   TimeBucketAssetDto, | ||||
|   TimeBucketDto, | ||||
|   TimeBucketResponseDto, | ||||
| @@ -41,6 +42,11 @@ export class AssetController { | ||||
|     return this.service.getMemoryLane(authUser, dto); | ||||
|   } | ||||
|  | ||||
|   @Get('random') | ||||
|   getRandom(@AuthUser() authUser: AuthUserDto, @Query() dto: RandomAssetsDto): Promise<AssetResponseDto[]> { | ||||
|     return this.service.getRandom(authUser, dto.count ?? 1); | ||||
|   } | ||||
|  | ||||
|   @SharedLinkRoute() | ||||
|   @Post('download/info') | ||||
|   getDownloadInfo(@AuthUser() authUser: AuthUserDto, @Body() dto: DownloadInfoDto): Promise<DownloadResponseDto> { | ||||
|   | ||||
| @@ -429,6 +429,17 @@ export class AssetRepository implements IAssetRepository { | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
|   getRandom(ownerId: string, count: number): Promise<AssetEntity[]> { | ||||
|     // can't use queryBuilder because of custom OFFSET clause | ||||
|     return this.repository.query( | ||||
|       `SELECT * | ||||
|        FROM assets | ||||
|        WHERE "ownerId" = $1 | ||||
|        OFFSET FLOOR(RANDOM() * (SELECT GREATEST(COUNT(*) - 1, 0) FROM ASSETS)) LIMIT $2`, | ||||
|       [ownerId, count], | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]> { | ||||
|     const truncateValue = truncateMap[options.size]; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,11 @@ | ||||
| import { IAssetRepository, IFaceRepository, IPersonRepository, LoginResponseDto, TimeBucketSize } from '@app/domain'; | ||||
| import { | ||||
|   AssetResponseDto, | ||||
|   IAssetRepository, | ||||
|   IFaceRepository, | ||||
|   IPersonRepository, | ||||
|   LoginResponseDto, | ||||
|   TimeBucketSize, | ||||
| } from '@app/domain'; | ||||
| import { AppModule, AssetController } from '@app/immich'; | ||||
| import { AssetEntity, AssetType } from '@app/infra/entities'; | ||||
| import { INestApplication } from '@nestjs/common'; | ||||
| @@ -322,7 +329,7 @@ describe(`${AssetController.name} (e2e)`, () => { | ||||
|     }); | ||||
|  | ||||
|     it('should require authentication', async () => { | ||||
|       const { status, body } = await request(server).get('/album/statistics'); | ||||
|       const { status, body } = await request(server).get('/asset/statistics'); | ||||
|  | ||||
|       expect(status).toBe(401); | ||||
|       expect(body).toEqual(errorStub.unauthorized); | ||||
| @@ -378,6 +385,58 @@ describe(`${AssetController.name} (e2e)`, () => { | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   describe('GET /asset/random', () => { | ||||
|     it('should require authentication', async () => { | ||||
|       const { status, body } = await request(server).get('/asset/random'); | ||||
|  | ||||
|       expect(status).toBe(401); | ||||
|       expect(body).toEqual(errorStub.unauthorized); | ||||
|     }); | ||||
|  | ||||
|     it('should return 1 random assets', async () => { | ||||
|       const { status, body } = await request(server) | ||||
|         .get('/asset/random') | ||||
|         .set('Authorization', `Bearer ${user1.accessToken}`); | ||||
|  | ||||
|       expect(status).toBe(200); | ||||
|  | ||||
|       const assets: AssetResponseDto[] = body; | ||||
|       expect(assets.length).toBe(1); | ||||
|       expect(assets[0].ownerId).toBe(user1.userId); | ||||
|       // assets owned by user1 | ||||
|       expect([asset1.id, asset2.id, asset3.id]).toContain(assets[0].id); | ||||
|       // assets owned by user2 | ||||
|       expect(assets[0].id).not.toBe(asset4.id); | ||||
|     }); | ||||
|  | ||||
|     it('should return 2 random assets', async () => { | ||||
|       const { status, body } = await request(server) | ||||
|         .get('/asset/random?count=2') | ||||
|         .set('Authorization', `Bearer ${user1.accessToken}`); | ||||
|  | ||||
|       expect(status).toBe(200); | ||||
|  | ||||
|       const assets: AssetResponseDto[] = body; | ||||
|       expect(assets.length).toBe(2); | ||||
|  | ||||
|       for (const asset of assets) { | ||||
|         expect(asset.ownerId).toBe(user1.userId); | ||||
|         // assets owned by user1 | ||||
|         expect([asset1.id, asset2.id, asset3.id]).toContain(asset.id); | ||||
|         // assets owned by user2 | ||||
|         expect(asset.id).not.toBe(asset4.id); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     it('should return error', async () => { | ||||
|       const { status } = await request(server) | ||||
|         .get('/asset/random?count=ABC') | ||||
|         .set('Authorization', `Bearer ${user1.accessToken}`); | ||||
|  | ||||
|       expect(status).toBe(400); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   describe('GET /asset/time-buckets', () => { | ||||
|     it('should require authentication', async () => { | ||||
|       const { status, body } = await request(server).get('/asset/time-buckets').query({ size: TimeBucketSize.MONTH }); | ||||
|   | ||||
| @@ -11,6 +11,7 @@ export const newAssetRepositoryMock = (): jest.Mocked<IAssetRepository> => { | ||||
|     getWithout: jest.fn(), | ||||
|     getByChecksum: jest.fn(), | ||||
|     getWith: jest.fn(), | ||||
|     getRandom: jest.fn(), | ||||
|     getFirstAssetForAlbumId: jest.fn(), | ||||
|     getLastUpdatedAssetForAlbumId: jest.fn(), | ||||
|     getAll: jest.fn().mockResolvedValue({ items: [], hasNextPage: false }), | ||||
|   | ||||
							
								
								
									
										87
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										87
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -6303,6 +6303,49 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration | ||||
| 
 | ||||
| 
 | ||||
|      | ||||
|             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||
|             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||
|             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||
| 
 | ||||
|             return { | ||||
|                 url: toPathString(localVarUrlObj), | ||||
|                 options: localVarRequestOptions, | ||||
|             }; | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {number} [count]  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         getRandom: async (count?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|             const localVarPath = `/asset/random`; | ||||
|             // use dummy base URL string because the URL constructor only accepts absolute URLs.
 | ||||
|             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); | ||||
|             let baseOptions; | ||||
|             if (configuration) { | ||||
|                 baseOptions = configuration.baseOptions; | ||||
|             } | ||||
| 
 | ||||
|             const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; | ||||
|             const localVarHeaderParameter = {} as any; | ||||
|             const localVarQueryParameter = {} as any; | ||||
| 
 | ||||
|             // authentication cookie required
 | ||||
| 
 | ||||
|             // authentication api_key required
 | ||||
|             await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) | ||||
| 
 | ||||
|             // authentication bearer required
 | ||||
|             // http bearer authentication required
 | ||||
|             await setBearerAuthToObject(localVarHeaderParameter, configuration) | ||||
| 
 | ||||
|             if (count !== undefined) { | ||||
|                 localVarQueryParameter['count'] = count; | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|      | ||||
|             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||
|             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||
|             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||
| @@ -7043,6 +7086,16 @@ export const AssetApiFp = function(configuration?: Configuration) { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.getMemoryLane(timestamp, options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {number} [count]  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         async getRandom(count?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.getRandom(count, options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {TimeBucketSize} size  | ||||
| @@ -7318,6 +7371,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath | ||||
|         getMemoryLane(requestParameters: AssetApiGetMemoryLaneRequest, options?: AxiosRequestConfig): AxiosPromise<Array<MemoryLaneResponseDto>> { | ||||
|             return localVarFp.getMemoryLane(requestParameters.timestamp, options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {AssetApiGetRandomRequest} requestParameters Request parameters. | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         getRandom(requestParameters: AssetApiGetRandomRequest = {}, options?: AxiosRequestConfig): AxiosPromise<Array<AssetResponseDto>> { | ||||
|             return localVarFp.getRandom(requestParameters.count, options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {AssetApiGetTimeBucketsRequest} requestParameters Request parameters. | ||||
| @@ -7752,6 +7814,20 @@ export interface AssetApiGetMemoryLaneRequest { | ||||
|     readonly timestamp: string | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Request parameters for getRandom operation in AssetApi. | ||||
|  * @export | ||||
|  * @interface AssetApiGetRandomRequest | ||||
|  */ | ||||
| export interface AssetApiGetRandomRequest { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {number} | ||||
|      * @memberof AssetApiGetRandom | ||||
|      */ | ||||
|     readonly count?: number | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Request parameters for getTimeBuckets operation in AssetApi. | ||||
|  * @export | ||||
| @@ -8244,6 +8320,17 @@ export class AssetApi extends BaseAPI { | ||||
|         return AssetApiFp(this.configuration).getMemoryLane(requestParameters.timestamp, options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      * @param {AssetApiGetRandomRequest} requestParameters Request parameters. | ||||
|      * @param {*} [options] Override http request option. | ||||
|      * @throws {RequiredError} | ||||
|      * @memberof AssetApi | ||||
|      */ | ||||
|     public getRandom(requestParameters: AssetApiGetRandomRequest = {}, options?: AxiosRequestConfig) { | ||||
|         return AssetApiFp(this.configuration).getRandom(requestParameters.count, options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      * @param {AssetApiGetTimeBucketsRequest} requestParameters Request parameters. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user