mirror of
https://github.com/immich-app/immich.git
synced 2025-03-01 15:11:21 +01: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:
parent
fc64be6603
commit
014d164d99
14 changed files with 429 additions and 3 deletions
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);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
@ -7043,6 +7086,16 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getMemoryLane(timestamp, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getMemoryLane(timestamp, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
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
|
* @param {TimeBucketSize} size
|
||||||
|
@ -7318,6 +7371,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
||||||
getMemoryLane(requestParameters: AssetApiGetMemoryLaneRequest, options?: AxiosRequestConfig): AxiosPromise<Array<MemoryLaneResponseDto>> {
|
getMemoryLane(requestParameters: AssetApiGetMemoryLaneRequest, options?: AxiosRequestConfig): AxiosPromise<Array<MemoryLaneResponseDto>> {
|
||||||
return localVarFp.getMemoryLane(requestParameters.timestamp, options).then((request) => request(axios, basePath));
|
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.
|
* @param {AssetApiGetTimeBucketsRequest} requestParameters Request parameters.
|
||||||
|
@ -7752,6 +7814,20 @@ export interface AssetApiGetMemoryLaneRequest {
|
||||||
readonly timestamp: string
|
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.
|
* Request parameters for getTimeBuckets operation in AssetApi.
|
||||||
* @export
|
* @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));
|
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.
|
* @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* | [**getDownloadInfo**](doc//AssetApi.md#getdownloadinfo) | **POST** /asset/download/info |
|
||||||
*AssetApi* | [**getMapMarkers**](doc//AssetApi.md#getmapmarkers) | **GET** /asset/map-marker |
|
*AssetApi* | [**getMapMarkers**](doc//AssetApi.md#getmapmarkers) | **GET** /asset/map-marker |
|
||||||
*AssetApi* | [**getMemoryLane**](doc//AssetApi.md#getmemorylane) | **GET** /asset/memory-lane |
|
*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* | [**getTimeBuckets**](doc//AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets |
|
||||||
*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
||||||
*AssetApi* | [**importFile**](doc//AssetApi.md#importfile) | **POST** /asset/import |
|
*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 |
|
[**getDownloadInfo**](AssetApi.md#getdownloadinfo) | **POST** /asset/download/info |
|
||||||
[**getMapMarkers**](AssetApi.md#getmapmarkers) | **GET** /asset/map-marker |
|
[**getMapMarkers**](AssetApi.md#getmapmarkers) | **GET** /asset/map-marker |
|
||||||
[**getMemoryLane**](AssetApi.md#getmemorylane) | **GET** /asset/memory-lane |
|
[**getMemoryLane**](AssetApi.md#getmemorylane) | **GET** /asset/memory-lane |
|
||||||
|
[**getRandom**](AssetApi.md#getrandom) | **GET** /asset/random |
|
||||||
[**getTimeBuckets**](AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets |
|
[**getTimeBuckets**](AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets |
|
||||||
[**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
[**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
||||||
[**importFile**](AssetApi.md#importfile) | **POST** /asset/import |
|
[**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)
|
[[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**
|
# **getTimeBuckets**
|
||||||
> List<TimeBucketResponseDto> getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, key)
|
> 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;
|
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].
|
/// Performs an HTTP 'GET /asset/time-buckets' operation and returns the [Response].
|
||||||
/// Parameters:
|
/// 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
|
// 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
|
//Future<List<TimeBucketResponseDto>> getTimeBuckets(TimeBucketSize size, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, String key }) async
|
||||||
test('test getTimeBuckets', () async {
|
test('test getTimeBuckets', () async {
|
||||||
// TODO
|
// 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": {
|
"/asset/search": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "searchAsset",
|
"operationId": "searchAsset",
|
||||||
|
|
|
@ -78,6 +78,7 @@ export interface IAssetRepository {
|
||||||
getByUserId(pagination: PaginationOptions, userId: string): Paginated<AssetEntity>;
|
getByUserId(pagination: PaginationOptions, userId: string): Paginated<AssetEntity>;
|
||||||
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity>;
|
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity>;
|
||||||
getWith(pagination: PaginationOptions, property: WithProperty, libraryId?: string): Paginated<AssetEntity>;
|
getWith(pagination: PaginationOptions, property: WithProperty, libraryId?: string): Paginated<AssetEntity>;
|
||||||
|
getRandom(userId: string, count: number): Promise<AssetEntity[]>;
|
||||||
getFirstAssetForAlbumId(albumId: string): Promise<AssetEntity | null>;
|
getFirstAssetForAlbumId(albumId: string): Promise<AssetEntity | null>;
|
||||||
getLastUpdatedAssetForAlbumId(albumId: string): Promise<AssetEntity | null>;
|
getLastUpdatedAssetForAlbumId(albumId: string): Promise<AssetEntity | null>;
|
||||||
getByLibraryId(libraryIds: string[]): Promise<AssetEntity[]>;
|
getByLibraryId(libraryIds: string[]): Promise<AssetEntity[]>;
|
||||||
|
|
|
@ -284,6 +284,11 @@ export class AssetService {
|
||||||
return mapStats(stats);
|
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> {
|
async update(authUser: AuthUserDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
||||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, id);
|
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 { Optional } from '../../domain.util';
|
||||||
import { BulkIdsDto } from '../response-dto';
|
import { BulkIdsDto } from '../response-dto';
|
||||||
|
|
||||||
|
@ -25,3 +26,11 @@ export class UpdateAssetDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class RandomAssetsDto {
|
||||||
|
@Optional()
|
||||||
|
@IsInt()
|
||||||
|
@IsPositive()
|
||||||
|
@Type(() => Number)
|
||||||
|
count?: number;
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
MapMarkerResponseDto,
|
MapMarkerResponseDto,
|
||||||
MemoryLaneDto,
|
MemoryLaneDto,
|
||||||
MemoryLaneResponseDto,
|
MemoryLaneResponseDto,
|
||||||
|
RandomAssetsDto,
|
||||||
TimeBucketAssetDto,
|
TimeBucketAssetDto,
|
||||||
TimeBucketDto,
|
TimeBucketDto,
|
||||||
TimeBucketResponseDto,
|
TimeBucketResponseDto,
|
||||||
|
@ -41,6 +42,11 @@ export class AssetController {
|
||||||
return this.service.getMemoryLane(authUser, dto);
|
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()
|
@SharedLinkRoute()
|
||||||
@Post('download/info')
|
@Post('download/info')
|
||||||
getDownloadInfo(@AuthUser() authUser: AuthUserDto, @Body() dto: DownloadInfoDto): Promise<DownloadResponseDto> {
|
getDownloadInfo(@AuthUser() authUser: AuthUserDto, @Body() dto: DownloadInfoDto): Promise<DownloadResponseDto> {
|
||||||
|
|
|
@ -429,6 +429,17 @@ export class AssetRepository implements IAssetRepository {
|
||||||
return result;
|
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[]> {
|
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]> {
|
||||||
const truncateValue = truncateMap[options.size];
|
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 { AppModule, AssetController } from '@app/immich';
|
||||||
import { AssetEntity, AssetType } from '@app/infra/entities';
|
import { AssetEntity, AssetType } from '@app/infra/entities';
|
||||||
import { INestApplication } from '@nestjs/common';
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
@ -322,7 +329,7 @@ describe(`${AssetController.name} (e2e)`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require authentication', async () => {
|
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(status).toBe(401);
|
||||||
expect(body).toEqual(errorStub.unauthorized);
|
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', () => {
|
describe('GET /asset/time-buckets', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server).get('/asset/time-buckets').query({ size: TimeBucketSize.MONTH });
|
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(),
|
getWithout: jest.fn(),
|
||||||
getByChecksum: jest.fn(),
|
getByChecksum: jest.fn(),
|
||||||
getWith: jest.fn(),
|
getWith: jest.fn(),
|
||||||
|
getRandom: jest.fn(),
|
||||||
getFirstAssetForAlbumId: jest.fn(),
|
getFirstAssetForAlbumId: jest.fn(),
|
||||||
getLastUpdatedAssetForAlbumId: jest.fn(),
|
getLastUpdatedAssetForAlbumId: jest.fn(),
|
||||||
getAll: jest.fn().mockResolvedValue({ items: [], hasNextPage: false }),
|
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);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
@ -7043,6 +7086,16 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getMemoryLane(timestamp, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getMemoryLane(timestamp, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
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
|
* @param {TimeBucketSize} size
|
||||||
|
@ -7318,6 +7371,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
||||||
getMemoryLane(requestParameters: AssetApiGetMemoryLaneRequest, options?: AxiosRequestConfig): AxiosPromise<Array<MemoryLaneResponseDto>> {
|
getMemoryLane(requestParameters: AssetApiGetMemoryLaneRequest, options?: AxiosRequestConfig): AxiosPromise<Array<MemoryLaneResponseDto>> {
|
||||||
return localVarFp.getMemoryLane(requestParameters.timestamp, options).then((request) => request(axios, basePath));
|
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.
|
* @param {AssetApiGetTimeBucketsRequest} requestParameters Request parameters.
|
||||||
|
@ -7752,6 +7814,20 @@ export interface AssetApiGetMemoryLaneRequest {
|
||||||
readonly timestamp: string
|
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.
|
* Request parameters for getTimeBuckets operation in AssetApi.
|
||||||
* @export
|
* @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));
|
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.
|
* @param {AssetApiGetTimeBucketsRequest} requestParameters Request parameters.
|
||||||
|
|
Loading…
Add table
Reference in a new issue