diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 850aeae7f..c4d4707f3 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -525,6 +525,42 @@ export const AssetIdsResponseDtoErrorEnum = { export type AssetIdsResponseDtoErrorEnum = typeof AssetIdsResponseDtoErrorEnum[keyof typeof AssetIdsResponseDtoErrorEnum]; +/** + * + * @export + * @enum {string} + */ + +export const AssetJobName = { + RegenerateThumbnail: 'regenerate-thumbnail', + RefreshMetadata: 'refresh-metadata', + TranscodeVideo: 'transcode-video' +} as const; + +export type AssetJobName = typeof AssetJobName[keyof typeof AssetJobName]; + + +/** + * + * @export + * @interface AssetJobsDto + */ +export interface AssetJobsDto { + /** + * + * @type {Array} + * @memberof AssetJobsDto + */ + 'assetIds': Array; + /** + * + * @type {AssetJobName} + * @memberof AssetJobsDto + */ + 'name': AssetJobName; +} + + /** * * @export @@ -5784,6 +5820,50 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, + /** + * + * @param {AssetJobsDto} assetJobsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + runAssetJobs: async (assetJobsDto: AssetJobsDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'assetJobsDto' is not null or undefined + assertParamExists('runAssetJobs', 'assetJobsDto', assetJobsDto) + const localVarPath = `/asset/jobs`; + // 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: 'POST', ...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) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(assetJobsDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {SearchAssetDto} searchAssetDto @@ -6331,6 +6411,16 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.importFile(importAssetDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {AssetJobsDto} assetJobsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async runAssetJobs(assetJobsDto: AssetJobsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.runAssetJobs(assetJobsDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {SearchAssetDto} searchAssetDto @@ -6584,6 +6674,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath importFile(requestParameters: AssetApiImportFileRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.importFile(requestParameters.importAssetDto, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {AssetApiRunAssetJobsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + runAssetJobs(requestParameters: AssetApiRunAssetJobsRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.runAssetJobs(requestParameters.assetJobsDto, options).then((request) => request(axios, basePath)); + }, /** * * @param {AssetApiSearchAssetRequest} requestParameters Request parameters. @@ -7066,6 +7165,20 @@ export interface AssetApiImportFileRequest { readonly importAssetDto: ImportAssetDto } +/** + * Request parameters for runAssetJobs operation in AssetApi. + * @export + * @interface AssetApiRunAssetJobsRequest + */ +export interface AssetApiRunAssetJobsRequest { + /** + * + * @type {AssetJobsDto} + * @memberof AssetApiRunAssetJobs + */ + readonly assetJobsDto: AssetJobsDto +} + /** * Request parameters for searchAsset operation in AssetApi. * @export @@ -7472,6 +7585,17 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).importFile(requestParameters.importAssetDto, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {AssetApiRunAssetJobsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public runAssetJobs(requestParameters: AssetApiRunAssetJobsRequest, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).runAssetJobs(requestParameters.assetJobsDto, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {AssetApiSearchAssetRequest} requestParameters Request parameters. diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index c51b8a3e0..93b704976 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -23,6 +23,8 @@ doc/AssetBulkUploadCheckResult.md doc/AssetFileUploadResponseDto.md doc/AssetIdsDto.md doc/AssetIdsResponseDto.md +doc/AssetJobName.md +doc/AssetJobsDto.md doc/AssetResponseDto.md doc/AssetStatsResponseDto.md doc/AssetTypeEnum.md @@ -168,6 +170,8 @@ lib/model/asset_bulk_upload_check_result.dart lib/model/asset_file_upload_response_dto.dart lib/model/asset_ids_dto.dart lib/model/asset_ids_response_dto.dart +lib/model/asset_job_name.dart +lib/model/asset_jobs_dto.dart lib/model/asset_response_dto.dart lib/model/asset_stats_response_dto.dart lib/model/asset_type_enum.dart @@ -282,6 +286,8 @@ test/asset_bulk_upload_check_result_test.dart test/asset_file_upload_response_dto_test.dart test/asset_ids_dto_test.dart test/asset_ids_response_dto_test.dart +test/asset_job_name_test.dart +test/asset_jobs_dto_test.dart test/asset_response_dto_test.dart test/asset_stats_response_dto_test.dart test/asset_type_enum_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 6c6108a9f..976c1aa09 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -107,6 +107,7 @@ Class | Method | HTTP request | Description *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 | +*AssetApi* | [**runAssetJobs**](doc//AssetApi.md#runassetjobs) | **POST** /asset/jobs | *AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search | *AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{id} | *AssetApi* | [**updateAsset**](doc//AssetApi.md#updateasset) | **PUT** /asset/{id} | @@ -197,6 +198,8 @@ Class | Method | HTTP request | Description - [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md) - [AssetIdsDto](doc//AssetIdsDto.md) - [AssetIdsResponseDto](doc//AssetIdsResponseDto.md) + - [AssetJobName](doc//AssetJobName.md) + - [AssetJobsDto](doc//AssetJobsDto.md) - [AssetResponseDto](doc//AssetResponseDto.md) - [AssetStatsResponseDto](doc//AssetStatsResponseDto.md) - [AssetTypeEnum](doc//AssetTypeEnum.md) diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index 6981d9ec3..2d76e5f9a 100644 --- a/mobile/openapi/doc/AssetApi.md +++ b/mobile/openapi/doc/AssetApi.md @@ -29,6 +29,7 @@ Method | HTTP request | Description [**getTimeBuckets**](AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets | [**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} | [**importFile**](AssetApi.md#importfile) | **POST** /asset/import | +[**runAssetJobs**](AssetApi.md#runassetjobs) | **POST** /asset/jobs | [**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search | [**serveFile**](AssetApi.md#servefile) | **GET** /asset/file/{id} | [**updateAsset**](AssetApi.md#updateasset) | **PUT** /asset/{id} | @@ -1192,6 +1193,60 @@ 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) +# **runAssetJobs** +> runAssetJobs(assetJobsDto) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = AssetApi(); +final assetJobsDto = AssetJobsDto(); // AssetJobsDto | + +try { + api_instance.runAssetJobs(assetJobsDto); +} catch (e) { + print('Exception when calling AssetApi->runAssetJobs: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **assetJobsDto** | [**AssetJobsDto**](AssetJobsDto.md)| | + +### Return type + +void (empty response body) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: Not defined + +[[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) + # **searchAsset** > List searchAsset(searchAssetDto) diff --git a/mobile/openapi/doc/AssetJobName.md b/mobile/openapi/doc/AssetJobName.md new file mode 100644 index 000000000..d9612705a --- /dev/null +++ b/mobile/openapi/doc/AssetJobName.md @@ -0,0 +1,14 @@ +# openapi.model.AssetJobName + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/doc/AssetJobsDto.md b/mobile/openapi/doc/AssetJobsDto.md new file mode 100644 index 000000000..e1d04388a --- /dev/null +++ b/mobile/openapi/doc/AssetJobsDto.md @@ -0,0 +1,16 @@ +# openapi.model.AssetJobsDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**assetIds** | **List** | | [default to const []] +**name** | [**AssetJobName**](AssetJobName.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) + + diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 064a26517..7797555bf 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -60,6 +60,8 @@ part 'model/asset_bulk_upload_check_result.dart'; part 'model/asset_file_upload_response_dto.dart'; part 'model/asset_ids_dto.dart'; part 'model/asset_ids_response_dto.dart'; +part 'model/asset_job_name.dart'; +part 'model/asset_jobs_dto.dart'; part 'model/asset_response_dto.dart'; part 'model/asset_stats_response_dto.dart'; part 'model/asset_type_enum.dart'; diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index 2c73a4ba8..0f8b69a1b 100644 --- a/mobile/openapi/lib/api/asset_api.dart +++ b/mobile/openapi/lib/api/asset_api.dart @@ -1227,6 +1227,45 @@ class AssetApi { return null; } + /// Performs an HTTP 'POST /asset/jobs' operation and returns the [Response]. + /// Parameters: + /// + /// * [AssetJobsDto] assetJobsDto (required): + Future runAssetJobsWithHttpInfo(AssetJobsDto assetJobsDto,) async { + // ignore: prefer_const_declarations + final path = r'/asset/jobs'; + + // ignore: prefer_final_locals + Object? postBody = assetJobsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + path, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [AssetJobsDto] assetJobsDto (required): + Future runAssetJobs(AssetJobsDto assetJobsDto,) async { + final response = await runAssetJobsWithHttpInfo(assetJobsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + /// Performs an HTTP 'POST /asset/search' operation and returns the [Response]. /// Parameters: /// diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 830aab936..2a3ebeb64 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -215,6 +215,10 @@ class ApiClient { return AssetIdsDto.fromJson(value); case 'AssetIdsResponseDto': return AssetIdsResponseDto.fromJson(value); + case 'AssetJobName': + return AssetJobNameTypeTransformer().decode(value); + case 'AssetJobsDto': + return AssetJobsDto.fromJson(value); case 'AssetResponseDto': return AssetResponseDto.fromJson(value); case 'AssetStatsResponseDto': diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index bc1dfd7bc..a80daaf76 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -55,6 +55,9 @@ String parameterToString(dynamic value) { if (value is DateTime) { return value.toUtc().toIso8601String(); } + if (value is AssetJobName) { + return AssetJobNameTypeTransformer().encode(value).toString(); + } if (value is AssetTypeEnum) { return AssetTypeEnumTypeTransformer().encode(value).toString(); } diff --git a/mobile/openapi/lib/model/asset_job_name.dart b/mobile/openapi/lib/model/asset_job_name.dart new file mode 100644 index 000000000..61334df08 --- /dev/null +++ b/mobile/openapi/lib/model/asset_job_name.dart @@ -0,0 +1,88 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class AssetJobName { + /// Instantiate a new enum with the provided [value]. + const AssetJobName._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const regenerateThumbnail = AssetJobName._(r'regenerate-thumbnail'); + static const refreshMetadata = AssetJobName._(r'refresh-metadata'); + static const transcodeVideo = AssetJobName._(r'transcode-video'); + + /// List of all possible values in this [enum][AssetJobName]. + static const values = [ + regenerateThumbnail, + refreshMetadata, + transcodeVideo, + ]; + + static AssetJobName? fromJson(dynamic value) => AssetJobNameTypeTransformer().decode(value); + + static List? listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetJobName.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetJobName] to String, +/// and [decode] dynamic data back to [AssetJobName]. +class AssetJobNameTypeTransformer { + factory AssetJobNameTypeTransformer() => _instance ??= const AssetJobNameTypeTransformer._(); + + const AssetJobNameTypeTransformer._(); + + String encode(AssetJobName data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetJobName. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AssetJobName? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'regenerate-thumbnail': return AssetJobName.regenerateThumbnail; + case r'refresh-metadata': return AssetJobName.refreshMetadata; + case r'transcode-video': return AssetJobName.transcodeVideo; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetJobNameTypeTransformer] instance. + static AssetJobNameTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/asset_jobs_dto.dart b/mobile/openapi/lib/model/asset_jobs_dto.dart new file mode 100644 index 000000000..3c88438b9 --- /dev/null +++ b/mobile/openapi/lib/model/asset_jobs_dto.dart @@ -0,0 +1,108 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetJobsDto { + /// Returns a new [AssetJobsDto] instance. + AssetJobsDto({ + this.assetIds = const [], + required this.name, + }); + + List assetIds; + + AssetJobName name; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetJobsDto && + other.assetIds == assetIds && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetIds.hashCode) + + (name.hashCode); + + @override + String toString() => 'AssetJobsDto[assetIds=$assetIds, name=$name]'; + + Map toJson() { + final json = {}; + json[r'assetIds'] = this.assetIds; + json[r'name'] = this.name; + return json; + } + + /// Returns a new [AssetJobsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetJobsDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return AssetJobsDto( + assetIds: json[r'assetIds'] is List + ? (json[r'assetIds'] as List).cast() + : const [], + name: AssetJobName.fromJson(json[r'name'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetJobsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetJobsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetJobsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetJobsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetIds', + 'name', + }; +} + diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index 2a4847e02..ef8ae195a 100644 --- a/mobile/openapi/test/asset_api_test.dart +++ b/mobile/openapi/test/asset_api_test.dart @@ -129,6 +129,11 @@ void main() { // TODO }); + //Future runAssetJobs(AssetJobsDto assetJobsDto) async + test('test runAssetJobs', () async { + // TODO + }); + //Future> searchAsset(SearchAssetDto searchAssetDto) async test('test searchAsset', () async { // TODO diff --git a/mobile/openapi/test/asset_job_name_test.dart b/mobile/openapi/test/asset_job_name_test.dart new file mode 100644 index 000000000..dc6313c92 --- /dev/null +++ b/mobile/openapi/test/asset_job_name_test.dart @@ -0,0 +1,21 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for AssetJobName +void main() { + + group('test AssetJobName', () { + + }); + +} diff --git a/mobile/openapi/test/asset_jobs_dto_test.dart b/mobile/openapi/test/asset_jobs_dto_test.dart new file mode 100644 index 000000000..e114d9fb2 --- /dev/null +++ b/mobile/openapi/test/asset_jobs_dto_test.dart @@ -0,0 +1,32 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for AssetJobsDto +void main() { + // final instance = AssetJobsDto(); + + group('test AssetJobsDto', () { + // List assetIds (default value: const []) + test('to test the property `assetIds`', () async { + // TODO + }); + + // AssetJobName name + test('to test the property `name`', () async { + // TODO + }); + + + }); + +} diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 91f72c170..ba6c5cb7a 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1367,6 +1367,41 @@ ] } }, + "/asset/jobs": { + "post": { + "operationId": "runAssetJobs", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetJobsDto" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Asset" + ] + } + }, "/asset/map-marker": { "get": { "operationId": "getMapMarkers", @@ -5042,6 +5077,33 @@ ], "type": "object" }, + "AssetJobName": { + "enum": [ + "regenerate-thumbnail", + "refresh-metadata", + "transcode-video" + ], + "type": "string" + }, + "AssetJobsDto": { + "properties": { + "assetIds": { + "items": { + "format": "uuid", + "type": "string" + }, + "type": "array" + }, + "name": { + "$ref": "#/components/schemas/AssetJobName" + } + }, + "required": [ + "assetIds", + "name" + ], + "type": "object" + }, "AssetResponseDto": { "properties": { "checksum": { diff --git a/server/src/domain/asset/asset.service.spec.ts b/server/src/domain/asset/asset.service.spec.ts index 88c82994f..59c161f67 100644 --- a/server/src/domain/asset/asset.service.spec.ts +++ b/server/src/domain/asset/asset.service.spec.ts @@ -7,15 +7,17 @@ import { newAccessRepositoryMock, newAssetRepositoryMock, newCryptoRepositoryMock, + newJobRepositoryMock, newStorageRepositoryMock, } from '@test'; import { when } from 'jest-when'; import { Readable } from 'stream'; import { ICryptoRepository } from '../crypto'; +import { IJobRepository, JobName } from '../index'; import { IStorageRepository } from '../storage'; import { AssetStats, IAssetRepository } from './asset.repository'; import { AssetService, UploadFieldName } from './asset.service'; -import { AssetStatsResponseDto, DownloadResponseDto } from './dto'; +import { AssetJobName, AssetStatsResponseDto, DownloadResponseDto } from './dto'; import { mapAsset } from './response-dto'; const downloadResponse: DownloadResponseDto = { @@ -145,6 +147,7 @@ describe(AssetService.name, () => { let accessMock: IAccessRepositoryMock; let assetMock: jest.Mocked; let cryptoMock: jest.Mocked; + let jobMock: jest.Mocked; let storageMock: jest.Mocked; it('should work', () => { @@ -155,8 +158,9 @@ describe(AssetService.name, () => { accessMock = newAccessRepositoryMock(); assetMock = newAssetRepositoryMock(); cryptoMock = newCryptoRepositoryMock(); + jobMock = newJobRepositoryMock(); storageMock = newStorageRepositoryMock(); - sut = new AssetService(accessMock, assetMock, cryptoMock, storageMock); + sut = new AssetService(accessMock, assetMock, cryptoMock, jobMock, storageMock); }); describe('canUpload', () => { @@ -532,4 +536,24 @@ describe(AssetService.name, () => { expect(assetMock.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true }); }); }); + + describe('run', () => { + it('should run the refresh metadata job', async () => { + accessMock.asset.hasOwnerAccess.mockResolvedValue(true); + await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_METADATA }), + expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.METADATA_EXTRACTION, data: { id: 'asset-1' } }); + }); + + it('should run the refresh thumbnails job', async () => { + accessMock.asset.hasOwnerAccess.mockResolvedValue(true); + await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REGENERATE_THUMBNAIL }), + expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-1' } }); + }); + + it('should run the transcode video', async () => { + accessMock.asset.hasOwnerAccess.mockResolvedValue(true); + await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.TRANSCODE_VIDEO }), + expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.VIDEO_CONVERSION, data: { id: 'asset-1' } }); + }); + }); }); diff --git a/server/src/domain/asset/asset.service.ts b/server/src/domain/asset/asset.service.ts index 7d00aa6b0..c7dc79046 100644 --- a/server/src/domain/asset/asset.service.ts +++ b/server/src/domain/asset/asset.service.ts @@ -8,11 +8,14 @@ import { AuthUserDto } from '../auth'; import { ICryptoRepository } from '../crypto'; import { mimeTypes } from '../domain.constant'; import { HumanReadableSize, usePagination } from '../domain.util'; +import { IJobRepository, JobName } from '../job'; import { ImmichReadStream, IStorageRepository, StorageCore, StorageFolder } from '../storage'; import { IAssetRepository } from './asset.repository'; import { AssetBulkUpdateDto, AssetIdsDto, + AssetJobName, + AssetJobsDto, DownloadArchiveInfo, DownloadInfoDto, DownloadResponseDto, @@ -54,6 +57,7 @@ export class AssetService { @Inject(IAccessRepository) accessRepository: IAccessRepository, @Inject(IAssetRepository) private assetRepository: IAssetRepository, @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, + @Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, ) { this.access = new AccessCore(accessRepository); @@ -275,4 +279,24 @@ export class AssetService { await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, ids); await this.assetRepository.updateAll(ids, options); } + + async run(authUser: AuthUserDto, dto: AssetJobsDto) { + await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, dto.assetIds); + + for (const id of dto.assetIds) { + switch (dto.name) { + case AssetJobName.REFRESH_METADATA: + await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id } }); + break; + + case AssetJobName.REGENERATE_THUMBNAIL: + await this.jobRepository.queue({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id } }); + break; + + case AssetJobName.TRANSCODE_VIDEO: + await this.jobRepository.queue({ name: JobName.VIDEO_CONVERSION, data: { id } }); + break; + } + } + } } diff --git a/server/src/domain/asset/dto/asset-ids.dto.ts b/server/src/domain/asset/dto/asset-ids.dto.ts index 6d2c58528..5ee988bb4 100644 --- a/server/src/domain/asset/dto/asset-ids.dto.ts +++ b/server/src/domain/asset/dto/asset-ids.dto.ts @@ -1,6 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum } from 'class-validator'; import { ValidateUUID } from '../../domain.util'; export class AssetIdsDto { @ValidateUUID({ each: true }) assetIds!: string[]; } + +export enum AssetJobName { + REGENERATE_THUMBNAIL = 'regenerate-thumbnail', + REFRESH_METADATA = 'refresh-metadata', + TRANSCODE_VIDEO = 'transcode-video', +} + +export class AssetJobsDto extends AssetIdsDto { + @ApiProperty({ enumName: 'AssetJobName', enum: AssetJobName }) + @IsEnum(AssetJobName) + name!: AssetJobName; +} diff --git a/server/src/immich/controllers/asset.controller.ts b/server/src/immich/controllers/asset.controller.ts index 70168cb81..652238a1c 100644 --- a/server/src/immich/controllers/asset.controller.ts +++ b/server/src/immich/controllers/asset.controller.ts @@ -1,6 +1,7 @@ import { AssetBulkUpdateDto, AssetIdsDto, + AssetJobsDto, AssetResponseDto, AssetService, AssetStatsDto, @@ -78,6 +79,12 @@ export class AssetController { return this.service.getByTimeBucket(authUser, dto); } + @Post('jobs') + @HttpCode(HttpStatus.NO_CONTENT) + runAssetJobs(@AuthUser() authUser: AuthUserDto, @Body() dto: AssetJobsDto): Promise { + return this.service.run(authUser, dto); + } + @Put() @HttpCode(HttpStatus.NO_CONTENT) updateAssets(@AuthUser() authUser: AuthUserDto, @Body() dto: AssetBulkUpdateDto): Promise { diff --git a/web/src/api/api.ts b/web/src/api/api.ts index 22f998972..3478daf2a 100644 --- a/web/src/api/api.ts +++ b/web/src/api/api.ts @@ -3,6 +3,7 @@ import { APIKeyApi, AssetApi, AssetApiFp, + AssetJobName, AuthenticationApi, Configuration, ConfigurationParameters, @@ -120,6 +121,26 @@ export class ImmichApi { return names[jobName]; } + + public getAssetJobName(job: AssetJobName) { + const names: Record = { + [AssetJobName.RefreshMetadata]: 'Refresh metadata', + [AssetJobName.RegenerateThumbnail]: 'Refresh thumbnails', + [AssetJobName.TranscodeVideo]: 'Refresh encoded videos', + }; + + return names[job]; + } + + public getAssetJobMessage(job: AssetJobName) { + const messages: Record = { + [AssetJobName.RefreshMetadata]: 'Refreshing metadata', + [AssetJobName.RegenerateThumbnail]: `Regenerating thumbnails`, + [AssetJobName.TranscodeVideo]: `Refreshing encoded video`, + }; + + return messages[job]; + } } export const api = new ImmichApi({ basePath: '/api' }); diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 850aeae7f..c4d4707f3 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -525,6 +525,42 @@ export const AssetIdsResponseDtoErrorEnum = { export type AssetIdsResponseDtoErrorEnum = typeof AssetIdsResponseDtoErrorEnum[keyof typeof AssetIdsResponseDtoErrorEnum]; +/** + * + * @export + * @enum {string} + */ + +export const AssetJobName = { + RegenerateThumbnail: 'regenerate-thumbnail', + RefreshMetadata: 'refresh-metadata', + TranscodeVideo: 'transcode-video' +} as const; + +export type AssetJobName = typeof AssetJobName[keyof typeof AssetJobName]; + + +/** + * + * @export + * @interface AssetJobsDto + */ +export interface AssetJobsDto { + /** + * + * @type {Array} + * @memberof AssetJobsDto + */ + 'assetIds': Array; + /** + * + * @type {AssetJobName} + * @memberof AssetJobsDto + */ + 'name': AssetJobName; +} + + /** * * @export @@ -5784,6 +5820,50 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, + /** + * + * @param {AssetJobsDto} assetJobsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + runAssetJobs: async (assetJobsDto: AssetJobsDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'assetJobsDto' is not null or undefined + assertParamExists('runAssetJobs', 'assetJobsDto', assetJobsDto) + const localVarPath = `/asset/jobs`; + // 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: 'POST', ...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) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(assetJobsDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {SearchAssetDto} searchAssetDto @@ -6331,6 +6411,16 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.importFile(importAssetDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {AssetJobsDto} assetJobsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async runAssetJobs(assetJobsDto: AssetJobsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.runAssetJobs(assetJobsDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {SearchAssetDto} searchAssetDto @@ -6584,6 +6674,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath importFile(requestParameters: AssetApiImportFileRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.importFile(requestParameters.importAssetDto, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {AssetApiRunAssetJobsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + runAssetJobs(requestParameters: AssetApiRunAssetJobsRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.runAssetJobs(requestParameters.assetJobsDto, options).then((request) => request(axios, basePath)); + }, /** * * @param {AssetApiSearchAssetRequest} requestParameters Request parameters. @@ -7066,6 +7165,20 @@ export interface AssetApiImportFileRequest { readonly importAssetDto: ImportAssetDto } +/** + * Request parameters for runAssetJobs operation in AssetApi. + * @export + * @interface AssetApiRunAssetJobsRequest + */ +export interface AssetApiRunAssetJobsRequest { + /** + * + * @type {AssetJobsDto} + * @memberof AssetApiRunAssetJobs + */ + readonly assetJobsDto: AssetJobsDto +} + /** * Request parameters for searchAsset operation in AssetApi. * @export @@ -7472,6 +7585,17 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).importFile(requestParameters.importAssetDto, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {AssetApiRunAssetJobsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public runAssetJobs(requestParameters: AssetApiRunAssetJobsRequest, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).runAssetJobs(requestParameters.assetJobsDto, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {AssetApiSearchAssetRequest} requestParameters Request parameters. diff --git a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte index 2dbbf9ca6..3f31459ab 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte @@ -1,7 +1,7 @@
(shouldPlayMotionPhoto = false)} on:toggleArchive={toggleArchive} on:asProfileImage={() => (isShowProfileImageCrop = true)} + on:runJob={({ detail: job }) => handleRunJob(job)} /> diff --git a/web/src/lib/components/photos-page/actions/asset-job-actions.svelte b/web/src/lib/components/photos-page/actions/asset-job-actions.svelte new file mode 100644 index 000000000..3e678cdf0 --- /dev/null +++ b/web/src/lib/components/photos-page/actions/asset-job-actions.svelte @@ -0,0 +1,37 @@ + + +{#each jobs as job} + {#if isAllVideos || job !== AssetJobName.TranscodeVideo} + handleRunJob(job)} /> + {/if} +{/each} diff --git a/web/src/routes/(user)/photos/+page.svelte b/web/src/routes/(user)/photos/+page.svelte index 66dbc1d92..63a933984 100644 --- a/web/src/routes/(user)/photos/+page.svelte +++ b/web/src/routes/(user)/photos/+page.svelte @@ -2,6 +2,7 @@ import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte'; import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte'; + import AssetJobActions from '$lib/components/photos-page/actions/asset-job-actions.svelte'; import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte'; import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte'; import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte'; @@ -52,6 +53,7 @@ assetStore.removeAssets(ids)} /> + {/if}