mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat: bulk asset metadata endpoints (#25133)
This commit is contained in:
Generated
+7
-1
@@ -100,6 +100,7 @@ Class | Method | HTTP request | Description
|
|||||||
*AssetsApi* | [**copyAsset**](doc//AssetsApi.md#copyasset) | **PUT** /assets/copy | Copy asset
|
*AssetsApi* | [**copyAsset**](doc//AssetsApi.md#copyasset) | **PUT** /assets/copy | Copy asset
|
||||||
*AssetsApi* | [**deleteAssetMetadata**](doc//AssetsApi.md#deleteassetmetadata) | **DELETE** /assets/{id}/metadata/{key} | Delete asset metadata by key
|
*AssetsApi* | [**deleteAssetMetadata**](doc//AssetsApi.md#deleteassetmetadata) | **DELETE** /assets/{id}/metadata/{key} | Delete asset metadata by key
|
||||||
*AssetsApi* | [**deleteAssets**](doc//AssetsApi.md#deleteassets) | **DELETE** /assets | Delete assets
|
*AssetsApi* | [**deleteAssets**](doc//AssetsApi.md#deleteassets) | **DELETE** /assets | Delete assets
|
||||||
|
*AssetsApi* | [**deleteBulkAssetMetadata**](doc//AssetsApi.md#deletebulkassetmetadata) | **DELETE** /assets/metadata | Delete asset metadata
|
||||||
*AssetsApi* | [**downloadAsset**](doc//AssetsApi.md#downloadasset) | **GET** /assets/{id}/original | Download original asset
|
*AssetsApi* | [**downloadAsset**](doc//AssetsApi.md#downloadasset) | **GET** /assets/{id}/original | Download original asset
|
||||||
*AssetsApi* | [**getAllUserAssetsByDeviceId**](doc//AssetsApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Retrieve assets by device ID
|
*AssetsApi* | [**getAllUserAssetsByDeviceId**](doc//AssetsApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Retrieve assets by device ID
|
||||||
*AssetsApi* | [**getAssetInfo**](doc//AssetsApi.md#getassetinfo) | **GET** /assets/{id} | Retrieve an asset
|
*AssetsApi* | [**getAssetInfo**](doc//AssetsApi.md#getassetinfo) | **GET** /assets/{id} | Retrieve an asset
|
||||||
@@ -114,6 +115,7 @@ Class | Method | HTTP request | Description
|
|||||||
*AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} | Update an asset
|
*AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} | Update an asset
|
||||||
*AssetsApi* | [**updateAssetMetadata**](doc//AssetsApi.md#updateassetmetadata) | **PUT** /assets/{id}/metadata | Update asset metadata
|
*AssetsApi* | [**updateAssetMetadata**](doc//AssetsApi.md#updateassetmetadata) | **PUT** /assets/{id}/metadata | Update asset metadata
|
||||||
*AssetsApi* | [**updateAssets**](doc//AssetsApi.md#updateassets) | **PUT** /assets | Update assets
|
*AssetsApi* | [**updateAssets**](doc//AssetsApi.md#updateassets) | **PUT** /assets | Update assets
|
||||||
|
*AssetsApi* | [**updateBulkAssetMetadata**](doc//AssetsApi.md#updatebulkassetmetadata) | **PUT** /assets/metadata | Upsert asset metadata
|
||||||
*AssetsApi* | [**uploadAsset**](doc//AssetsApi.md#uploadasset) | **POST** /assets | Upload asset
|
*AssetsApi* | [**uploadAsset**](doc//AssetsApi.md#uploadasset) | **POST** /assets | Upload asset
|
||||||
*AssetsApi* | [**viewAsset**](doc//AssetsApi.md#viewasset) | **GET** /assets/{id}/thumbnail | View asset thumbnail
|
*AssetsApi* | [**viewAsset**](doc//AssetsApi.md#viewasset) | **GET** /assets/{id}/thumbnail | View asset thumbnail
|
||||||
*AuthenticationApi* | [**changePassword**](doc//AuthenticationApi.md#changepassword) | **POST** /auth/change-password | Change password
|
*AuthenticationApi* | [**changePassword**](doc//AuthenticationApi.md#changepassword) | **POST** /auth/change-password | Change password
|
||||||
@@ -358,7 +360,11 @@ Class | Method | HTTP request | Description
|
|||||||
- [AssetMediaResponseDto](doc//AssetMediaResponseDto.md)
|
- [AssetMediaResponseDto](doc//AssetMediaResponseDto.md)
|
||||||
- [AssetMediaSize](doc//AssetMediaSize.md)
|
- [AssetMediaSize](doc//AssetMediaSize.md)
|
||||||
- [AssetMediaStatus](doc//AssetMediaStatus.md)
|
- [AssetMediaStatus](doc//AssetMediaStatus.md)
|
||||||
- [AssetMetadataKey](doc//AssetMetadataKey.md)
|
- [AssetMetadataBulkDeleteDto](doc//AssetMetadataBulkDeleteDto.md)
|
||||||
|
- [AssetMetadataBulkDeleteItemDto](doc//AssetMetadataBulkDeleteItemDto.md)
|
||||||
|
- [AssetMetadataBulkResponseDto](doc//AssetMetadataBulkResponseDto.md)
|
||||||
|
- [AssetMetadataBulkUpsertDto](doc//AssetMetadataBulkUpsertDto.md)
|
||||||
|
- [AssetMetadataBulkUpsertItemDto](doc//AssetMetadataBulkUpsertItemDto.md)
|
||||||
- [AssetMetadataResponseDto](doc//AssetMetadataResponseDto.md)
|
- [AssetMetadataResponseDto](doc//AssetMetadataResponseDto.md)
|
||||||
- [AssetMetadataUpsertDto](doc//AssetMetadataUpsertDto.md)
|
- [AssetMetadataUpsertDto](doc//AssetMetadataUpsertDto.md)
|
||||||
- [AssetMetadataUpsertItemDto](doc//AssetMetadataUpsertItemDto.md)
|
- [AssetMetadataUpsertItemDto](doc//AssetMetadataUpsertItemDto.md)
|
||||||
|
|||||||
Generated
+5
-1
@@ -109,7 +109,11 @@ part 'model/asset_jobs_dto.dart';
|
|||||||
part 'model/asset_media_response_dto.dart';
|
part 'model/asset_media_response_dto.dart';
|
||||||
part 'model/asset_media_size.dart';
|
part 'model/asset_media_size.dart';
|
||||||
part 'model/asset_media_status.dart';
|
part 'model/asset_media_status.dart';
|
||||||
part 'model/asset_metadata_key.dart';
|
part 'model/asset_metadata_bulk_delete_dto.dart';
|
||||||
|
part 'model/asset_metadata_bulk_delete_item_dto.dart';
|
||||||
|
part 'model/asset_metadata_bulk_response_dto.dart';
|
||||||
|
part 'model/asset_metadata_bulk_upsert_dto.dart';
|
||||||
|
part 'model/asset_metadata_bulk_upsert_item_dto.dart';
|
||||||
part 'model/asset_metadata_response_dto.dart';
|
part 'model/asset_metadata_response_dto.dart';
|
||||||
part 'model/asset_metadata_upsert_dto.dart';
|
part 'model/asset_metadata_upsert_dto.dart';
|
||||||
part 'model/asset_metadata_upsert_item_dto.dart';
|
part 'model/asset_metadata_upsert_item_dto.dart';
|
||||||
|
|||||||
Generated
+117
-10
@@ -186,12 +186,12 @@ class AssetsApi {
|
|||||||
///
|
///
|
||||||
/// * [String] id (required):
|
/// * [String] id (required):
|
||||||
///
|
///
|
||||||
/// * [AssetMetadataKey] key (required):
|
/// * [String] key (required):
|
||||||
Future<Response> deleteAssetMetadataWithHttpInfo(String id, AssetMetadataKey key,) async {
|
Future<Response> deleteAssetMetadataWithHttpInfo(String id, String key,) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/assets/{id}/metadata/{key}'
|
final apiPath = r'/assets/{id}/metadata/{key}'
|
||||||
.replaceAll('{id}', id)
|
.replaceAll('{id}', id)
|
||||||
.replaceAll('{key}', key.toString());
|
.replaceAll('{key}', key);
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
// ignore: prefer_final_locals
|
||||||
Object? postBody;
|
Object? postBody;
|
||||||
@@ -222,8 +222,8 @@ class AssetsApi {
|
|||||||
///
|
///
|
||||||
/// * [String] id (required):
|
/// * [String] id (required):
|
||||||
///
|
///
|
||||||
/// * [AssetMetadataKey] key (required):
|
/// * [String] key (required):
|
||||||
Future<void> deleteAssetMetadata(String id, AssetMetadataKey key,) async {
|
Future<void> deleteAssetMetadata(String id, String key,) async {
|
||||||
final response = await deleteAssetMetadataWithHttpInfo(id, key,);
|
final response = await deleteAssetMetadataWithHttpInfo(id, key,);
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
@@ -278,6 +278,54 @@ class AssetsApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete asset metadata
|
||||||
|
///
|
||||||
|
/// Delete metadata key-value pairs for multiple assets.
|
||||||
|
///
|
||||||
|
/// Note: This method returns the HTTP [Response].
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [AssetMetadataBulkDeleteDto] assetMetadataBulkDeleteDto (required):
|
||||||
|
Future<Response> deleteBulkAssetMetadataWithHttpInfo(AssetMetadataBulkDeleteDto assetMetadataBulkDeleteDto,) async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final apiPath = r'/assets/metadata';
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody = assetMetadataBulkDeleteDto;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>['application/json'];
|
||||||
|
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
apiPath,
|
||||||
|
'DELETE',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete asset metadata
|
||||||
|
///
|
||||||
|
/// Delete metadata key-value pairs for multiple assets.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [AssetMetadataBulkDeleteDto] assetMetadataBulkDeleteDto (required):
|
||||||
|
Future<void> deleteBulkAssetMetadata(AssetMetadataBulkDeleteDto assetMetadataBulkDeleteDto,) async {
|
||||||
|
final response = await deleteBulkAssetMetadataWithHttpInfo(assetMetadataBulkDeleteDto,);
|
||||||
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Download original asset
|
/// Download original asset
|
||||||
///
|
///
|
||||||
/// Downloads the original file of the specified asset.
|
/// Downloads the original file of the specified asset.
|
||||||
@@ -552,12 +600,12 @@ class AssetsApi {
|
|||||||
///
|
///
|
||||||
/// * [String] id (required):
|
/// * [String] id (required):
|
||||||
///
|
///
|
||||||
/// * [AssetMetadataKey] key (required):
|
/// * [String] key (required):
|
||||||
Future<Response> getAssetMetadataByKeyWithHttpInfo(String id, AssetMetadataKey key,) async {
|
Future<Response> getAssetMetadataByKeyWithHttpInfo(String id, String key,) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/assets/{id}/metadata/{key}'
|
final apiPath = r'/assets/{id}/metadata/{key}'
|
||||||
.replaceAll('{id}', id)
|
.replaceAll('{id}', id)
|
||||||
.replaceAll('{key}', key.toString());
|
.replaceAll('{key}', key);
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
// ignore: prefer_final_locals
|
||||||
Object? postBody;
|
Object? postBody;
|
||||||
@@ -588,8 +636,8 @@ class AssetsApi {
|
|||||||
///
|
///
|
||||||
/// * [String] id (required):
|
/// * [String] id (required):
|
||||||
///
|
///
|
||||||
/// * [AssetMetadataKey] key (required):
|
/// * [String] key (required):
|
||||||
Future<AssetMetadataResponseDto?> getAssetMetadataByKey(String id, AssetMetadataKey key,) async {
|
Future<AssetMetadataResponseDto?> getAssetMetadataByKey(String id, String key,) async {
|
||||||
final response = await getAssetMetadataByKeyWithHttpInfo(id, key,);
|
final response = await getAssetMetadataByKeyWithHttpInfo(id, key,);
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
@@ -1228,6 +1276,65 @@ class AssetsApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Upsert asset metadata
|
||||||
|
///
|
||||||
|
/// Upsert metadata key-value pairs for multiple assets.
|
||||||
|
///
|
||||||
|
/// Note: This method returns the HTTP [Response].
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [AssetMetadataBulkUpsertDto] assetMetadataBulkUpsertDto (required):
|
||||||
|
Future<Response> updateBulkAssetMetadataWithHttpInfo(AssetMetadataBulkUpsertDto assetMetadataBulkUpsertDto,) async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final apiPath = r'/assets/metadata';
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody = assetMetadataBulkUpsertDto;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>['application/json'];
|
||||||
|
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
apiPath,
|
||||||
|
'PUT',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upsert asset metadata
|
||||||
|
///
|
||||||
|
/// Upsert metadata key-value pairs for multiple assets.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [AssetMetadataBulkUpsertDto] assetMetadataBulkUpsertDto (required):
|
||||||
|
Future<List<AssetMetadataBulkResponseDto>?> updateBulkAssetMetadata(AssetMetadataBulkUpsertDto assetMetadataBulkUpsertDto,) async {
|
||||||
|
final response = await updateBulkAssetMetadataWithHttpInfo(assetMetadataBulkUpsertDto,);
|
||||||
|
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<AssetMetadataBulkResponseDto>') as List)
|
||||||
|
.cast<AssetMetadataBulkResponseDto>()
|
||||||
|
.toList(growable: false);
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Upload asset
|
/// Upload asset
|
||||||
///
|
///
|
||||||
/// Uploads a new asset to the server.
|
/// Uploads a new asset to the server.
|
||||||
|
|||||||
Generated
+10
-2
@@ -266,8 +266,16 @@ class ApiClient {
|
|||||||
return AssetMediaSizeTypeTransformer().decode(value);
|
return AssetMediaSizeTypeTransformer().decode(value);
|
||||||
case 'AssetMediaStatus':
|
case 'AssetMediaStatus':
|
||||||
return AssetMediaStatusTypeTransformer().decode(value);
|
return AssetMediaStatusTypeTransformer().decode(value);
|
||||||
case 'AssetMetadataKey':
|
case 'AssetMetadataBulkDeleteDto':
|
||||||
return AssetMetadataKeyTypeTransformer().decode(value);
|
return AssetMetadataBulkDeleteDto.fromJson(value);
|
||||||
|
case 'AssetMetadataBulkDeleteItemDto':
|
||||||
|
return AssetMetadataBulkDeleteItemDto.fromJson(value);
|
||||||
|
case 'AssetMetadataBulkResponseDto':
|
||||||
|
return AssetMetadataBulkResponseDto.fromJson(value);
|
||||||
|
case 'AssetMetadataBulkUpsertDto':
|
||||||
|
return AssetMetadataBulkUpsertDto.fromJson(value);
|
||||||
|
case 'AssetMetadataBulkUpsertItemDto':
|
||||||
|
return AssetMetadataBulkUpsertItemDto.fromJson(value);
|
||||||
case 'AssetMetadataResponseDto':
|
case 'AssetMetadataResponseDto':
|
||||||
return AssetMetadataResponseDto.fromJson(value);
|
return AssetMetadataResponseDto.fromJson(value);
|
||||||
case 'AssetMetadataUpsertDto':
|
case 'AssetMetadataUpsertDto':
|
||||||
|
|||||||
Generated
-3
@@ -67,9 +67,6 @@ String parameterToString(dynamic value) {
|
|||||||
if (value is AssetMediaStatus) {
|
if (value is AssetMediaStatus) {
|
||||||
return AssetMediaStatusTypeTransformer().encode(value).toString();
|
return AssetMediaStatusTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
if (value is AssetMetadataKey) {
|
|
||||||
return AssetMetadataKeyTypeTransformer().encode(value).toString();
|
|
||||||
}
|
|
||||||
if (value is AssetOrder) {
|
if (value is AssetOrder) {
|
||||||
return AssetOrderTypeTransformer().encode(value).toString();
|
return AssetOrderTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// 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 AssetMetadataBulkDeleteDto {
|
||||||
|
/// Returns a new [AssetMetadataBulkDeleteDto] instance.
|
||||||
|
AssetMetadataBulkDeleteDto({
|
||||||
|
this.items = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
List<AssetMetadataBulkDeleteItemDto> items;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkDeleteDto &&
|
||||||
|
_deepEquality.equals(other.items, items);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(items.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AssetMetadataBulkDeleteDto[items=$items]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'items'] = this.items;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [AssetMetadataBulkDeleteDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static AssetMetadataBulkDeleteDto? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "AssetMetadataBulkDeleteDto");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return AssetMetadataBulkDeleteDto(
|
||||||
|
items: AssetMetadataBulkDeleteItemDto.listFromJson(json[r'items']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<AssetMetadataBulkDeleteDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <AssetMetadataBulkDeleteDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = AssetMetadataBulkDeleteDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, AssetMetadataBulkDeleteDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, AssetMetadataBulkDeleteDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetMetadataBulkDeleteDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of AssetMetadataBulkDeleteDto-objects as value to a dart map
|
||||||
|
static Map<String, List<AssetMetadataBulkDeleteDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<AssetMetadataBulkDeleteDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = AssetMetadataBulkDeleteDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'items',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// 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 AssetMetadataBulkDeleteItemDto {
|
||||||
|
/// Returns a new [AssetMetadataBulkDeleteItemDto] instance.
|
||||||
|
AssetMetadataBulkDeleteItemDto({
|
||||||
|
required this.assetId,
|
||||||
|
required this.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
String assetId;
|
||||||
|
|
||||||
|
String key;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkDeleteItemDto &&
|
||||||
|
other.assetId == assetId &&
|
||||||
|
other.key == key;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(assetId.hashCode) +
|
||||||
|
(key.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AssetMetadataBulkDeleteItemDto[assetId=$assetId, key=$key]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'assetId'] = this.assetId;
|
||||||
|
json[r'key'] = this.key;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [AssetMetadataBulkDeleteItemDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static AssetMetadataBulkDeleteItemDto? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "AssetMetadataBulkDeleteItemDto");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return AssetMetadataBulkDeleteItemDto(
|
||||||
|
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||||
|
key: mapValueOfType<String>(json, r'key')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<AssetMetadataBulkDeleteItemDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <AssetMetadataBulkDeleteItemDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = AssetMetadataBulkDeleteItemDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, AssetMetadataBulkDeleteItemDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, AssetMetadataBulkDeleteItemDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetMetadataBulkDeleteItemDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of AssetMetadataBulkDeleteItemDto-objects as value to a dart map
|
||||||
|
static Map<String, List<AssetMetadataBulkDeleteItemDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<AssetMetadataBulkDeleteItemDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = AssetMetadataBulkDeleteItemDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'assetId',
|
||||||
|
'key',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// 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 AssetMetadataBulkResponseDto {
|
||||||
|
/// Returns a new [AssetMetadataBulkResponseDto] instance.
|
||||||
|
AssetMetadataBulkResponseDto({
|
||||||
|
required this.assetId,
|
||||||
|
required this.key,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
String assetId;
|
||||||
|
|
||||||
|
String key;
|
||||||
|
|
||||||
|
DateTime updatedAt;
|
||||||
|
|
||||||
|
Object value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkResponseDto &&
|
||||||
|
other.assetId == assetId &&
|
||||||
|
other.key == key &&
|
||||||
|
other.updatedAt == updatedAt &&
|
||||||
|
other.value == value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(assetId.hashCode) +
|
||||||
|
(key.hashCode) +
|
||||||
|
(updatedAt.hashCode) +
|
||||||
|
(value.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AssetMetadataBulkResponseDto[assetId=$assetId, key=$key, updatedAt=$updatedAt, value=$value]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'assetId'] = this.assetId;
|
||||||
|
json[r'key'] = this.key;
|
||||||
|
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||||
|
json[r'value'] = this.value;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [AssetMetadataBulkResponseDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static AssetMetadataBulkResponseDto? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "AssetMetadataBulkResponseDto");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return AssetMetadataBulkResponseDto(
|
||||||
|
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||||
|
key: mapValueOfType<String>(json, r'key')!,
|
||||||
|
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||||
|
value: mapValueOfType<Object>(json, r'value')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<AssetMetadataBulkResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <AssetMetadataBulkResponseDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = AssetMetadataBulkResponseDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, AssetMetadataBulkResponseDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, AssetMetadataBulkResponseDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetMetadataBulkResponseDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of AssetMetadataBulkResponseDto-objects as value to a dart map
|
||||||
|
static Map<String, List<AssetMetadataBulkResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<AssetMetadataBulkResponseDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = AssetMetadataBulkResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'assetId',
|
||||||
|
'key',
|
||||||
|
'updatedAt',
|
||||||
|
'value',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// 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 AssetMetadataBulkUpsertDto {
|
||||||
|
/// Returns a new [AssetMetadataBulkUpsertDto] instance.
|
||||||
|
AssetMetadataBulkUpsertDto({
|
||||||
|
this.items = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
List<AssetMetadataBulkUpsertItemDto> items;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkUpsertDto &&
|
||||||
|
_deepEquality.equals(other.items, items);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(items.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AssetMetadataBulkUpsertDto[items=$items]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'items'] = this.items;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [AssetMetadataBulkUpsertDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static AssetMetadataBulkUpsertDto? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "AssetMetadataBulkUpsertDto");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return AssetMetadataBulkUpsertDto(
|
||||||
|
items: AssetMetadataBulkUpsertItemDto.listFromJson(json[r'items']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<AssetMetadataBulkUpsertDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <AssetMetadataBulkUpsertDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = AssetMetadataBulkUpsertDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, AssetMetadataBulkUpsertDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, AssetMetadataBulkUpsertDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetMetadataBulkUpsertDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of AssetMetadataBulkUpsertDto-objects as value to a dart map
|
||||||
|
static Map<String, List<AssetMetadataBulkUpsertDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<AssetMetadataBulkUpsertDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = AssetMetadataBulkUpsertDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'items',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// 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 AssetMetadataBulkUpsertItemDto {
|
||||||
|
/// Returns a new [AssetMetadataBulkUpsertItemDto] instance.
|
||||||
|
AssetMetadataBulkUpsertItemDto({
|
||||||
|
required this.assetId,
|
||||||
|
required this.key,
|
||||||
|
required this.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
String assetId;
|
||||||
|
|
||||||
|
String key;
|
||||||
|
|
||||||
|
Object value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkUpsertItemDto &&
|
||||||
|
other.assetId == assetId &&
|
||||||
|
other.key == key &&
|
||||||
|
other.value == value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(assetId.hashCode) +
|
||||||
|
(key.hashCode) +
|
||||||
|
(value.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AssetMetadataBulkUpsertItemDto[assetId=$assetId, key=$key, value=$value]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'assetId'] = this.assetId;
|
||||||
|
json[r'key'] = this.key;
|
||||||
|
json[r'value'] = this.value;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [AssetMetadataBulkUpsertItemDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static AssetMetadataBulkUpsertItemDto? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "AssetMetadataBulkUpsertItemDto");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return AssetMetadataBulkUpsertItemDto(
|
||||||
|
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||||
|
key: mapValueOfType<String>(json, r'key')!,
|
||||||
|
value: mapValueOfType<Object>(json, r'value')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<AssetMetadataBulkUpsertItemDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <AssetMetadataBulkUpsertItemDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = AssetMetadataBulkUpsertItemDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, AssetMetadataBulkUpsertItemDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, AssetMetadataBulkUpsertItemDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetMetadataBulkUpsertItemDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of AssetMetadataBulkUpsertItemDto-objects as value to a dart map
|
||||||
|
static Map<String, List<AssetMetadataBulkUpsertItemDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<AssetMetadataBulkUpsertItemDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = AssetMetadataBulkUpsertItemDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'assetId',
|
||||||
|
'key',
|
||||||
|
'value',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
-82
@@ -1,82 +0,0 @@
|
|||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.18
|
|
||||||
|
|
||||||
// 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 AssetMetadataKey {
|
|
||||||
/// Instantiate a new enum with the provided [value].
|
|
||||||
const AssetMetadataKey._(this.value);
|
|
||||||
|
|
||||||
/// The underlying value of this enum member.
|
|
||||||
final String value;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => value;
|
|
||||||
|
|
||||||
String toJson() => value;
|
|
||||||
|
|
||||||
static const mobileApp = AssetMetadataKey._(r'mobile-app');
|
|
||||||
|
|
||||||
/// List of all possible values in this [enum][AssetMetadataKey].
|
|
||||||
static const values = <AssetMetadataKey>[
|
|
||||||
mobileApp,
|
|
||||||
];
|
|
||||||
|
|
||||||
static AssetMetadataKey? fromJson(dynamic value) => AssetMetadataKeyTypeTransformer().decode(value);
|
|
||||||
|
|
||||||
static List<AssetMetadataKey> listFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final result = <AssetMetadataKey>[];
|
|
||||||
if (json is List && json.isNotEmpty) {
|
|
||||||
for (final row in json) {
|
|
||||||
final value = AssetMetadataKey.fromJson(row);
|
|
||||||
if (value != null) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toList(growable: growable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transformation class that can [encode] an instance of [AssetMetadataKey] to String,
|
|
||||||
/// and [decode] dynamic data back to [AssetMetadataKey].
|
|
||||||
class AssetMetadataKeyTypeTransformer {
|
|
||||||
factory AssetMetadataKeyTypeTransformer() => _instance ??= const AssetMetadataKeyTypeTransformer._();
|
|
||||||
|
|
||||||
const AssetMetadataKeyTypeTransformer._();
|
|
||||||
|
|
||||||
String encode(AssetMetadataKey data) => data.value;
|
|
||||||
|
|
||||||
/// Decodes a [dynamic value][data] to a AssetMetadataKey.
|
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
AssetMetadataKey? decode(dynamic data, {bool allowNull = true}) {
|
|
||||||
if (data != null) {
|
|
||||||
switch (data) {
|
|
||||||
case r'mobile-app': return AssetMetadataKey.mobileApp;
|
|
||||||
default:
|
|
||||||
if (!allowNull) {
|
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Singleton [AssetMetadataKeyTypeTransformer] instance.
|
|
||||||
static AssetMetadataKeyTypeTransformer? _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
+2
-2
@@ -18,7 +18,7 @@ class AssetMetadataResponseDto {
|
|||||||
required this.value,
|
required this.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
AssetMetadataKey key;
|
String key;
|
||||||
|
|
||||||
DateTime updatedAt;
|
DateTime updatedAt;
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ class AssetMetadataResponseDto {
|
|||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
return AssetMetadataResponseDto(
|
return AssetMetadataResponseDto(
|
||||||
key: AssetMetadataKey.fromJson(json[r'key'])!,
|
key: mapValueOfType<String>(json, r'key')!,
|
||||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||||
value: mapValueOfType<Object>(json, r'value')!,
|
value: mapValueOfType<Object>(json, r'value')!,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class AssetMetadataUpsertItemDto {
|
|||||||
required this.value,
|
required this.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
AssetMetadataKey key;
|
String key;
|
||||||
|
|
||||||
Object value;
|
Object value;
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ class AssetMetadataUpsertItemDto {
|
|||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
return AssetMetadataUpsertItemDto(
|
return AssetMetadataUpsertItemDto(
|
||||||
key: AssetMetadataKey.fromJson(json[r'key'])!,
|
key: mapValueOfType<String>(json, r'key')!,
|
||||||
value: mapValueOfType<Object>(json, r'value')!,
|
value: mapValueOfType<Object>(json, r'value')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -19,7 +19,7 @@ class SyncAssetMetadataDeleteV1 {
|
|||||||
|
|
||||||
String assetId;
|
String assetId;
|
||||||
|
|
||||||
AssetMetadataKey key;
|
String key;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is SyncAssetMetadataDeleteV1 &&
|
bool operator ==(Object other) => identical(this, other) || other is SyncAssetMetadataDeleteV1 &&
|
||||||
@@ -52,7 +52,7 @@ class SyncAssetMetadataDeleteV1 {
|
|||||||
|
|
||||||
return SyncAssetMetadataDeleteV1(
|
return SyncAssetMetadataDeleteV1(
|
||||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||||
key: AssetMetadataKey.fromJson(json[r'key'])!,
|
key: mapValueOfType<String>(json, r'key')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
+2
-2
@@ -20,7 +20,7 @@ class SyncAssetMetadataV1 {
|
|||||||
|
|
||||||
String assetId;
|
String assetId;
|
||||||
|
|
||||||
AssetMetadataKey key;
|
String key;
|
||||||
|
|
||||||
Object value;
|
Object value;
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ class SyncAssetMetadataV1 {
|
|||||||
|
|
||||||
return SyncAssetMetadataV1(
|
return SyncAssetMetadataV1(
|
||||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||||
key: AssetMetadataKey.fromJson(json[r'key'])!,
|
key: mapValueOfType<String>(json, r'key')!,
|
||||||
value: mapValueOfType<Object>(json, r'value')!,
|
value: mapValueOfType<Object>(json, r'value')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2906,6 +2906,112 @@
|
|||||||
"x-immich-state": "Stable"
|
"x-immich-state": "Stable"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/assets/metadata": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Delete metadata key-value pairs for multiple assets.",
|
||||||
|
"operationId": "deleteBulkAssetMetadata",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/AssetMetadataBulkDeleteDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cookie": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"api_key": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": "Delete asset metadata",
|
||||||
|
"tags": [
|
||||||
|
"Assets"
|
||||||
|
],
|
||||||
|
"x-immich-history": [
|
||||||
|
{
|
||||||
|
"version": "v1",
|
||||||
|
"state": "Added"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "v2.5.0",
|
||||||
|
"state": "Beta"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x-immich-permission": "asset.update",
|
||||||
|
"x-immich-state": "Beta"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"description": "Upsert metadata key-value pairs for multiple assets.",
|
||||||
|
"operationId": "updateBulkAssetMetadata",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/AssetMetadataBulkUpsertDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/AssetMetadataBulkResponseDto"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cookie": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"api_key": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": "Upsert asset metadata",
|
||||||
|
"tags": [
|
||||||
|
"Assets"
|
||||||
|
],
|
||||||
|
"x-immich-history": [
|
||||||
|
{
|
||||||
|
"version": "v1",
|
||||||
|
"state": "Added"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "v2.5.0",
|
||||||
|
"state": "Beta"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x-immich-permission": "asset.update",
|
||||||
|
"x-immich-state": "Beta"
|
||||||
|
}
|
||||||
|
},
|
||||||
"/assets/random": {
|
"/assets/random": {
|
||||||
"get": {
|
"get": {
|
||||||
"deprecated": true,
|
"deprecated": true,
|
||||||
@@ -3340,7 +3446,7 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/AssetMetadataKey"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -3399,7 +3505,7 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/AssetMetadataKey"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -15575,20 +15681,98 @@
|
|||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"AssetMetadataKey": {
|
"AssetMetadataBulkDeleteDto": {
|
||||||
"enum": [
|
"properties": {
|
||||||
"mobile-app"
|
"items": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/AssetMetadataBulkDeleteItemDto"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"items"
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "object"
|
||||||
|
},
|
||||||
|
"AssetMetadataBulkDeleteItemDto": {
|
||||||
|
"properties": {
|
||||||
|
"assetId": {
|
||||||
|
"format": "uuid",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"assetId",
|
||||||
|
"key"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"AssetMetadataBulkResponseDto": {
|
||||||
|
"properties": {
|
||||||
|
"assetId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"assetId",
|
||||||
|
"key",
|
||||||
|
"updatedAt",
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"AssetMetadataBulkUpsertDto": {
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/AssetMetadataBulkUpsertItemDto"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"items"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"AssetMetadataBulkUpsertItemDto": {
|
||||||
|
"properties": {
|
||||||
|
"assetId": {
|
||||||
|
"format": "uuid",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"assetId",
|
||||||
|
"key",
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
},
|
},
|
||||||
"AssetMetadataResponseDto": {
|
"AssetMetadataResponseDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"key": {
|
"key": {
|
||||||
"allOf": [
|
"type": "string"
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/AssetMetadataKey"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
@@ -15622,11 +15806,7 @@
|
|||||||
"AssetMetadataUpsertItemDto": {
|
"AssetMetadataUpsertItemDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"key": {
|
"key": {
|
||||||
"allOf": [
|
"type": "string"
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/AssetMetadataKey"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -20651,11 +20831,7 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"key": {
|
"key": {
|
||||||
"allOf": [
|
"type": "string"
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/AssetMetadataKey"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -20670,11 +20846,7 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"key": {
|
"key": {
|
||||||
"allOf": [
|
"type": "string"
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/AssetMetadataKey"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|||||||
@@ -471,7 +471,7 @@ export type AssetBulkDeleteDto = {
|
|||||||
ids: string[];
|
ids: string[];
|
||||||
};
|
};
|
||||||
export type AssetMetadataUpsertItemDto = {
|
export type AssetMetadataUpsertItemDto = {
|
||||||
key: AssetMetadataKey;
|
key: string;
|
||||||
value: object;
|
value: object;
|
||||||
};
|
};
|
||||||
export type AssetMediaCreateDto = {
|
export type AssetMediaCreateDto = {
|
||||||
@@ -543,6 +543,27 @@ export type AssetJobsDto = {
|
|||||||
assetIds: string[];
|
assetIds: string[];
|
||||||
name: AssetJobName;
|
name: AssetJobName;
|
||||||
};
|
};
|
||||||
|
export type AssetMetadataBulkDeleteItemDto = {
|
||||||
|
assetId: string;
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
export type AssetMetadataBulkDeleteDto = {
|
||||||
|
items: AssetMetadataBulkDeleteItemDto[];
|
||||||
|
};
|
||||||
|
export type AssetMetadataBulkUpsertItemDto = {
|
||||||
|
assetId: string;
|
||||||
|
key: string;
|
||||||
|
value: object;
|
||||||
|
};
|
||||||
|
export type AssetMetadataBulkUpsertDto = {
|
||||||
|
items: AssetMetadataBulkUpsertItemDto[];
|
||||||
|
};
|
||||||
|
export type AssetMetadataBulkResponseDto = {
|
||||||
|
assetId: string;
|
||||||
|
key: string;
|
||||||
|
updatedAt: string;
|
||||||
|
value: object;
|
||||||
|
};
|
||||||
export type UpdateAssetDto = {
|
export type UpdateAssetDto = {
|
||||||
dateTimeOriginal?: string;
|
dateTimeOriginal?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@@ -554,7 +575,7 @@ export type UpdateAssetDto = {
|
|||||||
visibility?: AssetVisibility;
|
visibility?: AssetVisibility;
|
||||||
};
|
};
|
||||||
export type AssetMetadataResponseDto = {
|
export type AssetMetadataResponseDto = {
|
||||||
key: AssetMetadataKey;
|
key: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
value: object;
|
value: object;
|
||||||
};
|
};
|
||||||
@@ -2462,6 +2483,33 @@ export function runAssetJobs({ assetJobsDto }: {
|
|||||||
body: assetJobsDto
|
body: assetJobsDto
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Delete asset metadata
|
||||||
|
*/
|
||||||
|
export function deleteBulkAssetMetadata({ assetMetadataBulkDeleteDto }: {
|
||||||
|
assetMetadataBulkDeleteDto: AssetMetadataBulkDeleteDto;
|
||||||
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
|
return oazapfts.ok(oazapfts.fetchText("/assets/metadata", oazapfts.json({
|
||||||
|
...opts,
|
||||||
|
method: "DELETE",
|
||||||
|
body: assetMetadataBulkDeleteDto
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Upsert asset metadata
|
||||||
|
*/
|
||||||
|
export function updateBulkAssetMetadata({ assetMetadataBulkUpsertDto }: {
|
||||||
|
assetMetadataBulkUpsertDto: AssetMetadataBulkUpsertDto;
|
||||||
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
|
status: 200;
|
||||||
|
data: AssetMetadataBulkResponseDto[];
|
||||||
|
}>("/assets/metadata", oazapfts.json({
|
||||||
|
...opts,
|
||||||
|
method: "PUT",
|
||||||
|
body: assetMetadataBulkUpsertDto
|
||||||
|
})));
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Get random assets
|
* Get random assets
|
||||||
*/
|
*/
|
||||||
@@ -2564,7 +2612,7 @@ export function updateAssetMetadata({ id, assetMetadataUpsertDto }: {
|
|||||||
*/
|
*/
|
||||||
export function deleteAssetMetadata({ id, key }: {
|
export function deleteAssetMetadata({ id, key }: {
|
||||||
id: string;
|
id: string;
|
||||||
key: AssetMetadataKey;
|
key: string;
|
||||||
}, opts?: Oazapfts.RequestOpts) {
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
return oazapfts.ok(oazapfts.fetchText(`/assets/${encodeURIComponent(id)}/metadata/${encodeURIComponent(key)}`, {
|
return oazapfts.ok(oazapfts.fetchText(`/assets/${encodeURIComponent(id)}/metadata/${encodeURIComponent(key)}`, {
|
||||||
...opts,
|
...opts,
|
||||||
@@ -2576,7 +2624,7 @@ export function deleteAssetMetadata({ id, key }: {
|
|||||||
*/
|
*/
|
||||||
export function getAssetMetadataByKey({ id, key }: {
|
export function getAssetMetadataByKey({ id, key }: {
|
||||||
id: string;
|
id: string;
|
||||||
key: AssetMetadataKey;
|
key: string;
|
||||||
}, opts?: Oazapfts.RequestOpts) {
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
return oazapfts.ok(oazapfts.fetchJson<{
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
status: 200;
|
status: 200;
|
||||||
@@ -5363,9 +5411,6 @@ export enum Permission {
|
|||||||
AdminSessionRead = "adminSession.read",
|
AdminSessionRead = "adminSession.read",
|
||||||
AdminAuthUnlinkAll = "adminAuth.unlinkAll"
|
AdminAuthUnlinkAll = "adminAuth.unlinkAll"
|
||||||
}
|
}
|
||||||
export enum AssetMetadataKey {
|
|
||||||
MobileApp = "mobile-app"
|
|
||||||
}
|
|
||||||
export enum AssetMediaStatus {
|
export enum AssetMediaStatus {
|
||||||
Created = "created",
|
Created = "created",
|
||||||
Replaced = "replaced",
|
Replaced = "replaced",
|
||||||
|
|||||||
@@ -79,6 +79,74 @@ describe(AssetController.name, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('PUT /assets/metadata', () => {
|
||||||
|
it('should be an authenticated route', async () => {
|
||||||
|
await request(ctx.getHttpServer()).put(`/assets/metadata`);
|
||||||
|
expect(ctx.authenticate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require a valid assetId', async () => {
|
||||||
|
const { status, body } = await request(ctx.getHttpServer())
|
||||||
|
.put('/assets/metadata')
|
||||||
|
.send({ items: [{ assetId: '123', key: 'test', value: {} }] });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(factory.responses.badRequest(expect.arrayContaining(['items.0.assetId must be a UUID'])));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require a key', async () => {
|
||||||
|
const { status, body } = await request(ctx.getHttpServer())
|
||||||
|
.put('/assets/metadata')
|
||||||
|
.send({ items: [{ assetId: factory.uuid(), value: {} }] });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(
|
||||||
|
factory.responses.badRequest(
|
||||||
|
expect.arrayContaining(['items.0.key must be a string', 'items.0.key should not be empty']),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work', async () => {
|
||||||
|
const { status } = await request(ctx.getHttpServer())
|
||||||
|
.put('/assets/metadata')
|
||||||
|
.send({ items: [{ assetId: factory.uuid(), key: AssetMetadataKey.MobileApp, value: { iCloudId: '123' } }] });
|
||||||
|
expect(status).toBe(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DELETE /assets/metadata', () => {
|
||||||
|
it('should be an authenticated route', async () => {
|
||||||
|
await request(ctx.getHttpServer()).delete(`/assets/metadata`);
|
||||||
|
expect(ctx.authenticate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require a valid assetId', async () => {
|
||||||
|
const { status, body } = await request(ctx.getHttpServer())
|
||||||
|
.delete('/assets/metadata')
|
||||||
|
.send({ items: [{ assetId: '123', key: 'test' }] });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(factory.responses.badRequest(expect.arrayContaining(['items.0.assetId must be a UUID'])));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require a key', async () => {
|
||||||
|
const { status, body } = await request(ctx.getHttpServer())
|
||||||
|
.delete('/assets/metadata')
|
||||||
|
.send({ items: [{ assetId: factory.uuid() }] });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(
|
||||||
|
factory.responses.badRequest(
|
||||||
|
expect.arrayContaining(['items.0.key must be a string', 'items.0.key should not be empty']),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work', async () => {
|
||||||
|
const { status } = await request(ctx.getHttpServer())
|
||||||
|
.delete('/assets/metadata')
|
||||||
|
.send({ items: [{ assetId: factory.uuid(), key: AssetMetadataKey.MobileApp }] });
|
||||||
|
expect(status).toBe(204);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('PUT /assets/:id', () => {
|
describe('PUT /assets/:id', () => {
|
||||||
it('should be an authenticated route', async () => {
|
it('should be an authenticated route', async () => {
|
||||||
await request(ctx.getHttpServer()).get(`/assets/123`);
|
await request(ctx.getHttpServer()).get(`/assets/123`);
|
||||||
@@ -169,12 +237,10 @@ describe(AssetController.name, () => {
|
|||||||
it('should require each item to have a valid key', async () => {
|
it('should require each item to have a valid key', async () => {
|
||||||
const { status, body } = await request(ctx.getHttpServer())
|
const { status, body } = await request(ctx.getHttpServer())
|
||||||
.put(`/assets/${factory.uuid()}/metadata`)
|
.put(`/assets/${factory.uuid()}/metadata`)
|
||||||
.send({ items: [{ key: 'someKey' }] });
|
.send({ items: [{ value: { some: 'value' } }] });
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
factory.responses.badRequest(
|
factory.responses.badRequest(['items.0.key must be a string', 'items.0.key should not be empty']),
|
||||||
expect.arrayContaining([expect.stringContaining('items.0.key must be one of the following values')]),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -224,16 +290,6 @@ describe(AssetController.name, () => {
|
|||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(factory.responses.badRequest(expect.arrayContaining(['id must be a UUID'])));
|
expect(body).toEqual(factory.responses.badRequest(expect.arrayContaining(['id must be a UUID'])));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require a valid key', async () => {
|
|
||||||
const { status, body } = await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/metadata/invalid`);
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(
|
|
||||||
factory.responses.badRequest(
|
|
||||||
expect.arrayContaining([expect.stringContaining('key must be one of the following value')]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('DELETE /assets/:id/metadata/:key', () => {
|
describe('DELETE /assets/:id/metadata/:key', () => {
|
||||||
@@ -247,13 +303,5 @@ describe(AssetController.name, () => {
|
|||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(factory.responses.badRequest(['id must be a UUID']));
|
expect(body).toEqual(factory.responses.badRequest(['id must be a UUID']));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require a valid key', async () => {
|
|
||||||
const { status, body } = await request(ctx.getHttpServer()).delete(`/assets/${factory.uuid()}/metadata/invalid`);
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(
|
|
||||||
factory.responses.badRequest([expect.stringContaining('key must be one of the following values')]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import {
|
|||||||
AssetBulkUpdateDto,
|
AssetBulkUpdateDto,
|
||||||
AssetCopyDto,
|
AssetCopyDto,
|
||||||
AssetJobsDto,
|
AssetJobsDto,
|
||||||
|
AssetMetadataBulkDeleteDto,
|
||||||
|
AssetMetadataBulkResponseDto,
|
||||||
|
AssetMetadataBulkUpsertDto,
|
||||||
AssetMetadataResponseDto,
|
AssetMetadataResponseDto,
|
||||||
AssetMetadataRouteParams,
|
AssetMetadataRouteParams,
|
||||||
AssetMetadataUpsertDto,
|
AssetMetadataUpsertDto,
|
||||||
@@ -120,6 +123,32 @@ export class AssetController {
|
|||||||
return this.service.copy(auth, dto);
|
return this.service.copy(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Put('metadata')
|
||||||
|
@Authenticated({ permission: Permission.AssetUpdate })
|
||||||
|
@Endpoint({
|
||||||
|
summary: 'Upsert asset metadata',
|
||||||
|
description: 'Upsert metadata key-value pairs for multiple assets.',
|
||||||
|
history: new HistoryBuilder().added('v1').beta('v2.5.0'),
|
||||||
|
})
|
||||||
|
updateBulkAssetMetadata(
|
||||||
|
@Auth() auth: AuthDto,
|
||||||
|
@Body() dto: AssetMetadataBulkUpsertDto,
|
||||||
|
): Promise<AssetMetadataBulkResponseDto[]> {
|
||||||
|
return this.service.upsertBulkMetadata(auth, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('metadata')
|
||||||
|
@Authenticated({ permission: Permission.AssetUpdate })
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
@Endpoint({
|
||||||
|
summary: 'Delete asset metadata',
|
||||||
|
description: 'Delete metadata key-value pairs for multiple assets.',
|
||||||
|
history: new HistoryBuilder().added('v1').beta('v2.5.0'),
|
||||||
|
})
|
||||||
|
deleteBulkAssetMetadata(@Auth() auth: AuthDto, @Body() dto: AssetMetadataBulkDeleteDto): Promise<void> {
|
||||||
|
return this.service.deleteBulkMetadata(auth, dto);
|
||||||
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated({ permission: Permission.AssetUpdate })
|
@Authenticated({ permission: Permission.AssetUpdate })
|
||||||
@Endpoint({
|
@Endpoint({
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ import {
|
|||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { AssetMetadataKey, AssetType, AssetVisibility } from 'src/enum';
|
import { AssetType, AssetVisibility } from 'src/enum';
|
||||||
import { AssetStats } from 'src/repositories/asset.repository';
|
import { AssetStats } from 'src/repositories/asset.repository';
|
||||||
import { IsNotSiblingOf, Optional, ValidateBoolean, ValidateEnum, ValidateUUID } from 'src/validation';
|
import { IsNotSiblingOf, Optional, ValidateBoolean, ValidateEnum, ValidateString, ValidateUUID } from 'src/validation';
|
||||||
|
|
||||||
export class DeviceIdDto {
|
export class DeviceIdDto {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@@ -142,8 +142,8 @@ export class AssetMetadataRouteParams {
|
|||||||
@ValidateUUID()
|
@ValidateUUID()
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
@ValidateEnum({ enum: AssetMetadataKey, name: 'AssetMetadataKey' })
|
@ValidateString()
|
||||||
key!: AssetMetadataKey;
|
key!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AssetMetadataUpsertDto {
|
export class AssetMetadataUpsertDto {
|
||||||
@@ -154,26 +154,57 @@ export class AssetMetadataUpsertDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AssetMetadataUpsertItemDto {
|
export class AssetMetadataUpsertItemDto {
|
||||||
@ValidateEnum({ enum: AssetMetadataKey, name: 'AssetMetadataKey' })
|
@ValidateString()
|
||||||
key!: AssetMetadataKey;
|
key!: string;
|
||||||
|
|
||||||
@IsObject()
|
@IsObject()
|
||||||
value!: object;
|
value!: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AssetMetadataMobileAppDto {
|
export class AssetMetadataBulkUpsertDto {
|
||||||
@IsString()
|
@IsArray()
|
||||||
@Optional()
|
@ValidateNested({ each: true })
|
||||||
iCloudId?: string;
|
@Type(() => AssetMetadataBulkUpsertItemDto)
|
||||||
|
items!: AssetMetadataBulkUpsertItemDto[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AssetMetadataBulkUpsertItemDto {
|
||||||
|
@ValidateUUID()
|
||||||
|
assetId!: string;
|
||||||
|
|
||||||
|
@ValidateString()
|
||||||
|
key!: string;
|
||||||
|
|
||||||
|
@IsObject()
|
||||||
|
value!: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AssetMetadataBulkDeleteDto {
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => AssetMetadataBulkDeleteItemDto)
|
||||||
|
items!: AssetMetadataBulkDeleteItemDto[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AssetMetadataBulkDeleteItemDto {
|
||||||
|
@ValidateUUID()
|
||||||
|
assetId!: string;
|
||||||
|
|
||||||
|
@ValidateString()
|
||||||
|
key!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AssetMetadataResponseDto {
|
export class AssetMetadataResponseDto {
|
||||||
@ValidateEnum({ enum: AssetMetadataKey, name: 'AssetMetadataKey' })
|
@ValidateString()
|
||||||
key!: AssetMetadataKey;
|
key!: string;
|
||||||
value!: object;
|
value!: object;
|
||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AssetMetadataBulkResponseDto extends AssetMetadataResponseDto {
|
||||||
|
assetId!: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class AssetCopyDto {
|
export class AssetCopyDto {
|
||||||
@ValidateUUID()
|
@ValidateUUID()
|
||||||
sourceId!: string;
|
sourceId!: string;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { ArrayMaxSize, IsInt, IsPositive, IsString } from 'class-validator';
|
|||||||
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
||||||
import {
|
import {
|
||||||
AlbumUserRole,
|
AlbumUserRole,
|
||||||
AssetMetadataKey,
|
|
||||||
AssetOrder,
|
AssetOrder,
|
||||||
AssetType,
|
AssetType,
|
||||||
AssetVisibility,
|
AssetVisibility,
|
||||||
@@ -167,16 +166,14 @@ export class SyncAssetExifV1 {
|
|||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
export class SyncAssetMetadataV1 {
|
export class SyncAssetMetadataV1 {
|
||||||
assetId!: string;
|
assetId!: string;
|
||||||
@ValidateEnum({ enum: AssetMetadataKey, name: 'AssetMetadataKey' })
|
key!: string;
|
||||||
key!: AssetMetadataKey;
|
|
||||||
value!: object;
|
value!: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
export class SyncAssetMetadataDeleteV1 {
|
export class SyncAssetMetadataDeleteV1 {
|
||||||
assetId!: string;
|
assetId!: string;
|
||||||
@ValidateEnum({ enum: AssetMetadataKey, name: 'AssetMetadataKey' })
|
key!: string;
|
||||||
key!: AssetMetadataKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
|
|||||||
@@ -76,6 +76,14 @@ where
|
|||||||
"assetId" = $1
|
"assetId" = $1
|
||||||
and "key" = $2
|
and "key" = $2
|
||||||
|
|
||||||
|
-- AssetRepository.deleteBulkMetadata
|
||||||
|
begin
|
||||||
|
delete from "asset_metadata"
|
||||||
|
where
|
||||||
|
"assetId" = $1
|
||||||
|
and "key" = $2
|
||||||
|
commit
|
||||||
|
|
||||||
-- AssetRepository.getByDayOfYear
|
-- AssetRepository.getByDayOfYear
|
||||||
with
|
with
|
||||||
"res" as (
|
"res" as (
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import { InjectKysely } from 'nestjs-kysely';
|
|||||||
import { LockableProperty, Stack } from 'src/database';
|
import { LockableProperty, Stack } from 'src/database';
|
||||||
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { AssetFileType, AssetMetadataKey, AssetOrder, AssetStatus, AssetType, AssetVisibility } from 'src/enum';
|
import { AssetFileType, AssetOrder, AssetStatus, AssetType, AssetVisibility } from 'src/enum';
|
||||||
import { DB } from 'src/schema';
|
import { DB } from 'src/schema';
|
||||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||||
import { AssetFileTable } from 'src/schema/tables/asset-file.table';
|
import { AssetFileTable } from 'src/schema/tables/asset-file.table';
|
||||||
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
|
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
|
||||||
|
import { AssetMetadataTable } from 'src/schema/tables/asset-metadata.table';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import {
|
import {
|
||||||
anyUuid,
|
anyUuid,
|
||||||
@@ -256,7 +257,7 @@ export class AssetRepository {
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
upsertMetadata(id: string, items: Array<{ key: AssetMetadataKey; value: object }>) {
|
upsertMetadata(id: string, items: Array<{ key: string; value: object }>) {
|
||||||
return this.db
|
return this.db
|
||||||
.insertInto('asset_metadata')
|
.insertInto('asset_metadata')
|
||||||
.values(items.map((item) => ({ assetId: id, ...item })))
|
.values(items.map((item) => ({ assetId: id, ...item })))
|
||||||
@@ -269,8 +270,21 @@ export class AssetRepository {
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upsertBulkMetadata(items: Insertable<AssetMetadataTable>[]) {
|
||||||
|
return this.db
|
||||||
|
.insertInto('asset_metadata')
|
||||||
|
.values(items)
|
||||||
|
.onConflict((oc) =>
|
||||||
|
oc
|
||||||
|
.columns(['assetId', 'key'])
|
||||||
|
.doUpdateSet((eb) => ({ key: eb.ref('excluded.key'), value: eb.ref('excluded.value') })),
|
||||||
|
)
|
||||||
|
.returning(['assetId', 'key', 'value', 'updatedAt'])
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
|
||||||
getMetadataByKey(assetId: string, key: AssetMetadataKey) {
|
getMetadataByKey(assetId: string, key: string) {
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('asset_metadata')
|
.selectFrom('asset_metadata')
|
||||||
.select(['key', 'value', 'updatedAt'])
|
.select(['key', 'value', 'updatedAt'])
|
||||||
@@ -280,10 +294,23 @@ export class AssetRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
|
||||||
async deleteMetadataByKey(id: string, key: AssetMetadataKey) {
|
async deleteMetadataByKey(id: string, key: string) {
|
||||||
await this.db.deleteFrom('asset_metadata').where('assetId', '=', id).where('key', '=', key).execute();
|
await this.db.deleteFrom('asset_metadata').where('assetId', '=', id).where('key', '=', key).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [[{ assetId: DummyValue.UUID, key: DummyValue.STRING }]] })
|
||||||
|
async deleteBulkMetadata(items: Array<{ assetId: string; key: string }>) {
|
||||||
|
if (items.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.db.transaction().execute(async (tx) => {
|
||||||
|
for (const { assetId, key } of items) {
|
||||||
|
await tx.deleteFrom('asset_metadata').where('assetId', '=', assetId).where('key', '=', key).execute();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
create(asset: Insertable<AssetTable>) {
|
create(asset: Insertable<AssetTable>) {
|
||||||
return this.db.insertInto('asset').values(asset).returningAll().executeTakeFirstOrThrow();
|
return this.db.insertInto('asset').values(asset).returningAll().executeTakeFirstOrThrow();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { AssetMetadataKey } from 'src/enum';
|
|
||||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
||||||
|
|
||||||
@Table('asset_metadata_audit')
|
@Table('asset_metadata_audit')
|
||||||
@@ -11,7 +10,7 @@ export class AssetMetadataAuditTable {
|
|||||||
assetId!: string;
|
assetId!: string;
|
||||||
|
|
||||||
@Column({ index: true })
|
@Column({ index: true })
|
||||||
key!: AssetMetadataKey;
|
key!: string;
|
||||||
|
|
||||||
@CreateDateColumn({ default: () => 'clock_timestamp()', index: true })
|
@CreateDateColumn({ default: () => 'clock_timestamp()', index: true })
|
||||||
deletedAt!: Generated<Timestamp>;
|
deletedAt!: Generated<Timestamp>;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export class AssetMetadataTable {
|
|||||||
assetId!: string;
|
assetId!: string;
|
||||||
|
|
||||||
@PrimaryColumn({ type: 'character varying' })
|
@PrimaryColumn({ type: 'character varying' })
|
||||||
key!: AssetMetadataKey;
|
key!: AssetMetadataKey | string;
|
||||||
|
|
||||||
@Column({ type: 'jsonb' })
|
@Column({ type: 'jsonb' })
|
||||||
value!: object;
|
value!: object;
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import {
|
|||||||
AssetCopyDto,
|
AssetCopyDto,
|
||||||
AssetJobName,
|
AssetJobName,
|
||||||
AssetJobsDto,
|
AssetJobsDto,
|
||||||
|
AssetMetadataBulkDeleteDto,
|
||||||
|
AssetMetadataBulkResponseDto,
|
||||||
|
AssetMetadataBulkUpsertDto,
|
||||||
AssetMetadataResponseDto,
|
AssetMetadataResponseDto,
|
||||||
AssetMetadataUpsertDto,
|
AssetMetadataUpsertDto,
|
||||||
AssetStatsDto,
|
AssetStatsDto,
|
||||||
@@ -19,16 +22,7 @@ import {
|
|||||||
} from 'src/dtos/asset.dto';
|
} from 'src/dtos/asset.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { AssetOcrResponseDto } from 'src/dtos/ocr.dto';
|
import { AssetOcrResponseDto } from 'src/dtos/ocr.dto';
|
||||||
import {
|
import { AssetFileType, AssetStatus, AssetVisibility, JobName, JobStatus, Permission, QueueName } from 'src/enum';
|
||||||
AssetFileType,
|
|
||||||
AssetMetadataKey,
|
|
||||||
AssetStatus,
|
|
||||||
AssetVisibility,
|
|
||||||
JobName,
|
|
||||||
JobStatus,
|
|
||||||
Permission,
|
|
||||||
QueueName,
|
|
||||||
} from 'src/enum';
|
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { JobItem, JobOf } from 'src/types';
|
import { JobItem, JobOf } from 'src/types';
|
||||||
import { requireElevatedPermission } from 'src/utils/access';
|
import { requireElevatedPermission } from 'src/utils/access';
|
||||||
@@ -381,12 +375,17 @@ export class AssetService extends BaseService {
|
|||||||
return this.ocrRepository.getByAssetId(id);
|
return this.ocrRepository.getByAssetId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async upsertBulkMetadata(auth: AuthDto, dto: AssetMetadataBulkUpsertDto): Promise<AssetMetadataBulkResponseDto[]> {
|
||||||
|
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: dto.items.map((item) => item.assetId) });
|
||||||
|
return this.assetRepository.upsertBulkMetadata(dto.items);
|
||||||
|
}
|
||||||
|
|
||||||
async upsertMetadata(auth: AuthDto, id: string, dto: AssetMetadataUpsertDto): Promise<AssetMetadataResponseDto[]> {
|
async upsertMetadata(auth: AuthDto, id: string, dto: AssetMetadataUpsertDto): Promise<AssetMetadataResponseDto[]> {
|
||||||
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] });
|
||||||
return this.assetRepository.upsertMetadata(id, dto.items);
|
return this.assetRepository.upsertMetadata(id, dto.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMetadataByKey(auth: AuthDto, id: string, key: AssetMetadataKey): Promise<AssetMetadataResponseDto> {
|
async getMetadataByKey(auth: AuthDto, id: string, key: string): Promise<AssetMetadataResponseDto> {
|
||||||
await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] });
|
||||||
|
|
||||||
const item = await this.assetRepository.getMetadataByKey(id, key);
|
const item = await this.assetRepository.getMetadataByKey(id, key);
|
||||||
@@ -396,11 +395,16 @@ export class AssetService extends BaseService {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteMetadataByKey(auth: AuthDto, id: string, key: AssetMetadataKey): Promise<void> {
|
async deleteMetadataByKey(auth: AuthDto, id: string, key: string): Promise<void> {
|
||||||
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] });
|
||||||
return this.assetRepository.deleteMetadataByKey(id, key);
|
return this.assetRepository.deleteMetadataByKey(id, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteBulkMetadata(auth: AuthDto, dto: AssetMetadataBulkDeleteDto) {
|
||||||
|
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: dto.items.map((item) => item.assetId) });
|
||||||
|
await this.assetRepository.deleteBulkMetadata(dto.items);
|
||||||
|
}
|
||||||
|
|
||||||
async run(auth: AuthDto, dto: AssetJobsDto) {
|
async run(auth: AuthDto, dto: AssetJobsDto) {
|
||||||
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: dto.assetIds });
|
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: dto.assetIds });
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import { AlbumTable } from 'src/schema/tables/album.table';
|
|||||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||||
import { AssetFileTable } from 'src/schema/tables/asset-file.table';
|
import { AssetFileTable } from 'src/schema/tables/asset-file.table';
|
||||||
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
|
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
|
||||||
|
import { AssetMetadataTable } from 'src/schema/tables/asset-metadata.table';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||||
import { MemoryTable } from 'src/schema/tables/memory.table';
|
import { MemoryTable } from 'src/schema/tables/memory.table';
|
||||||
@@ -179,6 +180,12 @@ export class MediumTestContext<S extends BaseService = BaseService> {
|
|||||||
return { asset, result };
|
return { asset, result };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async newMetadata(dto: Insertable<AssetMetadataTable>) {
|
||||||
|
const { assetId, ...item } = dto;
|
||||||
|
const result = await this.get(AssetRepository).upsertMetadata(assetId, [item]);
|
||||||
|
return { metadata: dto, result };
|
||||||
|
}
|
||||||
|
|
||||||
async newAssetFile(dto: Insertable<AssetFileTable>) {
|
async newAssetFile(dto: Insertable<AssetFileTable>) {
|
||||||
const result = await this.get(AssetRepository).upsertFile(dto);
|
const result = await this.get(AssetRepository).upsertFile(dto);
|
||||||
return { result };
|
return { result };
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Kysely } from 'kysely';
|
import { Kysely } from 'kysely';
|
||||||
import { AssetFileType, JobName, SharedLinkType } from 'src/enum';
|
import { AssetFileType, AssetMetadataKey, JobName, SharedLinkType } from 'src/enum';
|
||||||
import { AccessRepository } from 'src/repositories/access.repository';
|
import { AccessRepository } from 'src/repositories/access.repository';
|
||||||
import { AlbumRepository } from 'src/repositories/album.repository';
|
import { AlbumRepository } from 'src/repositories/album.repository';
|
||||||
import { AssetJobRepository } from 'src/repositories/asset-job.repository';
|
import { AssetJobRepository } from 'src/repositories/asset-job.repository';
|
||||||
@@ -430,4 +430,177 @@ describe(AssetService.name, () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('upsertBulkMetadata', () => {
|
||||||
|
it('should work', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const auth = factory.auth({ user });
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
const items = [{ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'foo' } }];
|
||||||
|
|
||||||
|
await sut.upsertBulkMetadata(auth, { items });
|
||||||
|
|
||||||
|
const metadata = await ctx.get(AssetRepository).getMetadata(asset.id);
|
||||||
|
expect(metadata.length).toEqual(1);
|
||||||
|
expect(metadata[0]).toEqual(
|
||||||
|
expect.objectContaining({ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'foo' } }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work on conflict', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const auth = factory.auth({ user });
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ctx.newMetadata({ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'old-id' } });
|
||||||
|
|
||||||
|
// verify existing metadata
|
||||||
|
await expect(ctx.get(AssetRepository).getMetadata(asset.id)).resolves.toEqual([
|
||||||
|
expect.objectContaining({ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'old-id' } }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const items = [{ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'new-id' } }];
|
||||||
|
await sut.upsertBulkMetadata(auth, { items });
|
||||||
|
|
||||||
|
// verify updated metadata
|
||||||
|
await expect(ctx.get(AssetRepository).getMetadata(asset.id)).resolves.toEqual([
|
||||||
|
expect.objectContaining({ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'new-id' } }),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with multiple assets', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const auth = factory.auth({ user });
|
||||||
|
const { asset: asset1 } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
const { asset: asset2 } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ assetId: asset1.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } },
|
||||||
|
{ assetId: asset2.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id2' } },
|
||||||
|
];
|
||||||
|
|
||||||
|
await sut.upsertBulkMetadata(auth, { items });
|
||||||
|
|
||||||
|
const metadata1 = await ctx.get(AssetRepository).getMetadata(asset1.id);
|
||||||
|
expect(metadata1).toEqual([
|
||||||
|
expect.objectContaining({ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const metadata2 = await ctx.get(AssetRepository).getMetadata(asset2.id);
|
||||||
|
expect(metadata2).toEqual([
|
||||||
|
expect.objectContaining({ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id2' } }),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with multiple metadata for the same asset', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const auth = factory.auth({ user });
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } },
|
||||||
|
{ assetId: asset.id, key: 'some-other-key', value: { foo: 'bar' } },
|
||||||
|
];
|
||||||
|
|
||||||
|
await sut.upsertBulkMetadata(auth, { items });
|
||||||
|
|
||||||
|
const metadata = await ctx.get(AssetRepository).getMetadata(asset.id);
|
||||||
|
expect(metadata).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
key: AssetMetadataKey.MobileApp,
|
||||||
|
value: { iCloudId: 'id1' },
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
key: 'some-other-key',
|
||||||
|
value: { foo: 'bar' },
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteBulkMetadata', () => {
|
||||||
|
it('should work', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const auth = factory.auth({ user });
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ctx.newMetadata({ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'foo' } });
|
||||||
|
|
||||||
|
await sut.deleteBulkMetadata(auth, { items: [{ assetId: asset.id, key: AssetMetadataKey.MobileApp }] });
|
||||||
|
|
||||||
|
const metadata = await ctx.get(AssetRepository).getMetadata(asset.id);
|
||||||
|
expect(metadata.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work even if the item does not exist', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const auth = factory.auth({ user });
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
|
||||||
|
await sut.deleteBulkMetadata(auth, { items: [{ assetId: asset.id, key: AssetMetadataKey.MobileApp }] });
|
||||||
|
|
||||||
|
const metadata = await ctx.get(AssetRepository).getMetadata(asset.id);
|
||||||
|
expect(metadata.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with multiple assets', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const auth = factory.auth({ user });
|
||||||
|
const { asset: asset1 } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ctx.newMetadata({ assetId: asset1.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } });
|
||||||
|
const { asset: asset2 } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ctx.newMetadata({ assetId: asset2.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id2' } });
|
||||||
|
|
||||||
|
await sut.deleteBulkMetadata(auth, {
|
||||||
|
items: [
|
||||||
|
{ assetId: asset1.id, key: AssetMetadataKey.MobileApp },
|
||||||
|
{ assetId: asset2.id, key: AssetMetadataKey.MobileApp },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(ctx.get(AssetRepository).getMetadata(asset1.id)).resolves.toEqual([]);
|
||||||
|
await expect(ctx.get(AssetRepository).getMetadata(asset2.id)).resolves.toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with multiple metadata for the same asset', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const auth = factory.auth({ user });
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ctx.newMetadata({ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } });
|
||||||
|
await ctx.newMetadata({ assetId: asset.id, key: 'some-other-key', value: { foo: 'bar' } });
|
||||||
|
|
||||||
|
await sut.deleteBulkMetadata(auth, {
|
||||||
|
items: [
|
||||||
|
{ assetId: asset.id, key: AssetMetadataKey.MobileApp },
|
||||||
|
{ assetId: asset.id, key: 'some-other-key' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(ctx.get(AssetRepository).getMetadata(asset.id)).resolves.toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not delete unspecified keys', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const auth = factory.auth({ user });
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ctx.newMetadata({ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } });
|
||||||
|
await ctx.newMetadata({ assetId: asset.id, key: 'some-other-key', value: { foo: 'bar' } });
|
||||||
|
|
||||||
|
await sut.deleteBulkMetadata(auth, {
|
||||||
|
items: [{ assetId: asset.id, key: AssetMetadataKey.MobileApp }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const metadata = await ctx.get(AssetRepository).getMetadata(asset.id);
|
||||||
|
expect(metadata).toEqual([expect.objectContaining({ key: 'some-other-key', value: { foo: 'bar' } })]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -44,8 +44,10 @@ export const newAssetRepositoryMock = (): Mocked<RepositoryInterface<AssetReposi
|
|||||||
updateByLibraryId: vitest.fn(),
|
updateByLibraryId: vitest.fn(),
|
||||||
getFileSamples: vitest.fn(),
|
getFileSamples: vitest.fn(),
|
||||||
getMetadata: vitest.fn(),
|
getMetadata: vitest.fn(),
|
||||||
upsertMetadata: vitest.fn(),
|
|
||||||
getMetadataByKey: vitest.fn(),
|
getMetadataByKey: vitest.fn(),
|
||||||
|
upsertMetadata: vitest.fn(),
|
||||||
|
upsertBulkMetadata: vitest.fn(),
|
||||||
deleteMetadataByKey: vitest.fn(),
|
deleteMetadataByKey: vitest.fn(),
|
||||||
|
deleteBulkMetadata: vitest.fn(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user