mirror of https://github.com/immich-app/immich.git
feat: asset metadata (#20446)
parent
25a94bd117
commit
88072910da
@ -0,0 +1,82 @@
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -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 AssetMetadataResponseDto {
|
||||
/// Returns a new [AssetMetadataResponseDto] instance.
|
||||
AssetMetadataResponseDto({
|
||||
required this.key,
|
||||
required this.updatedAt,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
AssetMetadataKey key;
|
||||
|
||||
DateTime updatedAt;
|
||||
|
||||
Object value;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataResponseDto &&
|
||||
other.key == key &&
|
||||
other.updatedAt == updatedAt &&
|
||||
other.value == value;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(key.hashCode) +
|
||||
(updatedAt.hashCode) +
|
||||
(value.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetMetadataResponseDto[key=$key, updatedAt=$updatedAt, value=$value]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'key'] = this.key;
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
json[r'value'] = this.value;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetMetadataResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetMetadataResponseDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetMetadataResponseDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetMetadataResponseDto(
|
||||
key: AssetMetadataKey.fromJson(json[r'key'])!,
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||
value: mapValueOfType<Object>(json, r'value')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetMetadataResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetMetadataResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetMetadataResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetMetadataResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetMetadataResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetMetadataResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetMetadataResponseDto-objects as value to a dart map
|
||||
static Map<String, List<AssetMetadataResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetMetadataResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetMetadataResponseDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'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 AssetMetadataUpsertDto {
|
||||
/// Returns a new [AssetMetadataUpsertDto] instance.
|
||||
AssetMetadataUpsertDto({
|
||||
this.items = const [],
|
||||
});
|
||||
|
||||
List<AssetMetadataUpsertItemDto> items;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataUpsertDto &&
|
||||
_deepEquality.equals(other.items, items);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(items.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetMetadataUpsertDto[items=$items]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'items'] = this.items;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetMetadataUpsertDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetMetadataUpsertDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetMetadataUpsertDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetMetadataUpsertDto(
|
||||
items: AssetMetadataUpsertItemDto.listFromJson(json[r'items']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetMetadataUpsertDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetMetadataUpsertDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetMetadataUpsertDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetMetadataUpsertDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetMetadataUpsertDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetMetadataUpsertDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetMetadataUpsertDto-objects as value to a dart map
|
||||
static Map<String, List<AssetMetadataUpsertDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetMetadataUpsertDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetMetadataUpsertDto.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 AssetMetadataUpsertItemDto {
|
||||
/// Returns a new [AssetMetadataUpsertItemDto] instance.
|
||||
AssetMetadataUpsertItemDto({
|
||||
required this.key,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
AssetMetadataKey key;
|
||||
|
||||
Object value;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataUpsertItemDto &&
|
||||
other.key == key &&
|
||||
other.value == value;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(key.hashCode) +
|
||||
(value.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetMetadataUpsertItemDto[key=$key, value=$value]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'key'] = this.key;
|
||||
json[r'value'] = this.value;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetMetadataUpsertItemDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetMetadataUpsertItemDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetMetadataUpsertItemDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetMetadataUpsertItemDto(
|
||||
key: AssetMetadataKey.fromJson(json[r'key'])!,
|
||||
value: mapValueOfType<Object>(json, r'value')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetMetadataUpsertItemDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetMetadataUpsertItemDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetMetadataUpsertItemDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetMetadataUpsertItemDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetMetadataUpsertItemDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetMetadataUpsertItemDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetMetadataUpsertItemDto-objects as value to a dart map
|
||||
static Map<String, List<AssetMetadataUpsertItemDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetMetadataUpsertItemDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetMetadataUpsertItemDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'key',
|
||||
'value',
|
||||
};
|
||||
}
|
||||
|
||||
@ -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 SyncAssetMetadataDeleteV1 {
|
||||
/// Returns a new [SyncAssetMetadataDeleteV1] instance.
|
||||
SyncAssetMetadataDeleteV1({
|
||||
required this.assetId,
|
||||
required this.key,
|
||||
});
|
||||
|
||||
String assetId;
|
||||
|
||||
AssetMetadataKey key;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SyncAssetMetadataDeleteV1 &&
|
||||
other.assetId == assetId &&
|
||||
other.key == key;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(assetId.hashCode) +
|
||||
(key.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SyncAssetMetadataDeleteV1[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 [SyncAssetMetadataDeleteV1] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static SyncAssetMetadataDeleteV1? fromJson(dynamic value) {
|
||||
upgradeDto(value, "SyncAssetMetadataDeleteV1");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return SyncAssetMetadataDeleteV1(
|
||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||
key: AssetMetadataKey.fromJson(json[r'key'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<SyncAssetMetadataDeleteV1> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SyncAssetMetadataDeleteV1>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SyncAssetMetadataDeleteV1.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, SyncAssetMetadataDeleteV1> mapFromJson(dynamic json) {
|
||||
final map = <String, SyncAssetMetadataDeleteV1>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = SyncAssetMetadataDeleteV1.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of SyncAssetMetadataDeleteV1-objects as value to a dart map
|
||||
static Map<String, List<SyncAssetMetadataDeleteV1>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<SyncAssetMetadataDeleteV1>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = SyncAssetMetadataDeleteV1.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,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 SyncAssetMetadataV1 {
|
||||
/// Returns a new [SyncAssetMetadataV1] instance.
|
||||
SyncAssetMetadataV1({
|
||||
required this.assetId,
|
||||
required this.key,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
String assetId;
|
||||
|
||||
AssetMetadataKey key;
|
||||
|
||||
Object value;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SyncAssetMetadataV1 &&
|
||||
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() => 'SyncAssetMetadataV1[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 [SyncAssetMetadataV1] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static SyncAssetMetadataV1? fromJson(dynamic value) {
|
||||
upgradeDto(value, "SyncAssetMetadataV1");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return SyncAssetMetadataV1(
|
||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||
key: AssetMetadataKey.fromJson(json[r'key'])!,
|
||||
value: mapValueOfType<Object>(json, r'value')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<SyncAssetMetadataV1> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SyncAssetMetadataV1>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SyncAssetMetadataV1.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, SyncAssetMetadataV1> mapFromJson(dynamic json) {
|
||||
final map = <String, SyncAssetMetadataV1>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = SyncAssetMetadataV1.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of SyncAssetMetadataV1-objects as value to a dart map
|
||||
static Map<String, List<SyncAssetMetadataV1>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<SyncAssetMetadataV1>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = SyncAssetMetadataV1.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',
|
||||
};
|
||||
}
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`CREATE OR REPLACE FUNCTION asset_metadata_audit()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO asset_metadata_audit ("assetId", "key")
|
||||
SELECT "assetId", "key"
|
||||
FROM OLD;
|
||||
RETURN NULL;
|
||||
END
|
||||
$$;`.execute(db);
|
||||
await sql`CREATE TABLE "asset_metadata_audit" (
|
||||
"id" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||
"assetId" uuid NOT NULL,
|
||||
"key" character varying NOT NULL,
|
||||
"deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(),
|
||||
CONSTRAINT "asset_metadata_audit_pkey" PRIMARY KEY ("id")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "asset_metadata_audit_assetId_idx" ON "asset_metadata_audit" ("assetId");`.execute(db);
|
||||
await sql`CREATE INDEX "asset_metadata_audit_key_idx" ON "asset_metadata_audit" ("key");`.execute(db);
|
||||
await sql`CREATE INDEX "asset_metadata_audit_deletedAt_idx" ON "asset_metadata_audit" ("deletedAt");`.execute(db);
|
||||
await sql`CREATE TABLE "asset_metadata" (
|
||||
"assetId" uuid NOT NULL,
|
||||
"key" character varying NOT NULL,
|
||||
"value" jsonb NOT NULL,
|
||||
"updateId" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||
"updatedAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT "asset_metadata_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "asset" ("id") ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT "asset_metadata_pkey" PRIMARY KEY ("assetId", "key")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "asset_metadata_updateId_idx" ON "asset_metadata" ("updateId");`.execute(db);
|
||||
await sql`CREATE INDEX "asset_metadata_updatedAt_idx" ON "asset_metadata" ("updatedAt");`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "asset_metadata_audit"
|
||||
AFTER DELETE ON "asset_metadata"
|
||||
REFERENCING OLD TABLE AS "old"
|
||||
FOR EACH STATEMENT
|
||||
WHEN (pg_trigger_depth() = 0)
|
||||
EXECUTE FUNCTION asset_metadata_audit();`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "asset_metadata_updated_at"
|
||||
BEFORE UPDATE ON "asset_metadata"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION updated_at();`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_asset_metadata_audit', '{"type":"function","name":"asset_metadata_audit","sql":"CREATE OR REPLACE FUNCTION asset_metadata_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO asset_metadata_audit (\\"assetId\\", \\"key\\")\\n SELECT \\"assetId\\", \\"key\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_metadata_audit', '{"type":"trigger","name":"asset_metadata_audit","sql":"CREATE OR REPLACE TRIGGER \\"asset_metadata_audit\\"\\n AFTER DELETE ON \\"asset_metadata\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION asset_metadata_audit();"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_metadata_updated_at', '{"type":"trigger","name":"asset_metadata_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"asset_metadata_updated_at\\"\\n BEFORE UPDATE ON \\"asset_metadata\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await sql`DROP TABLE "asset_metadata_audit";`.execute(db);
|
||||
await sql`DROP TABLE "asset_metadata";`.execute(db);
|
||||
await sql`DROP FUNCTION asset_metadata_audit;`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_asset_metadata_audit';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_metadata_audit';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_metadata_updated_at';`.execute(db);
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||
import { AssetMetadataKey } from 'src/enum';
|
||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
||||
|
||||
@Table('asset_metadata_audit')
|
||||
export class AssetMetadataAuditTable {
|
||||
@PrimaryGeneratedUuidV7Column()
|
||||
id!: Generated<string>;
|
||||
|
||||
@Column({ type: 'uuid', index: true })
|
||||
assetId!: string;
|
||||
|
||||
@Column({ index: true })
|
||||
key!: AssetMetadataKey;
|
||||
|
||||
@CreateDateColumn({ default: () => 'clock_timestamp()', index: true })
|
||||
deletedAt!: Generated<Timestamp>;
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||
import { AssetMetadataKey } from 'src/enum';
|
||||
import { asset_metadata_audit } from 'src/schema/functions';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import {
|
||||
AfterDeleteTrigger,
|
||||
Column,
|
||||
ForeignKeyColumn,
|
||||
Generated,
|
||||
PrimaryColumn,
|
||||
Table,
|
||||
Timestamp,
|
||||
UpdateDateColumn,
|
||||
} from 'src/sql-tools';
|
||||
import { AssetMetadata, AssetMetadataItem } from 'src/types';
|
||||
|
||||
@UpdatedAtTrigger('asset_metadata_updated_at')
|
||||
@Table('asset_metadata')
|
||||
@AfterDeleteTrigger({
|
||||
scope: 'statement',
|
||||
function: asset_metadata_audit,
|
||||
referencingOldTableAs: 'old',
|
||||
when: 'pg_trigger_depth() = 0',
|
||||
})
|
||||
export class AssetMetadataTable<T extends keyof AssetMetadata = AssetMetadataKey> implements AssetMetadataItem<T> {
|
||||
@ForeignKeyColumn(() => AssetTable, {
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
primary: true,
|
||||
// [assetId, key] is the PK constraint
|
||||
index: false,
|
||||
})
|
||||
assetId!: string;
|
||||
|
||||
@PrimaryColumn({ type: 'character varying' })
|
||||
key!: T;
|
||||
|
||||
@Column({ type: 'jsonb' })
|
||||
value!: AssetMetadata[T];
|
||||
|
||||
@UpdateIdColumn({ index: true })
|
||||
updateId!: Generated<string>;
|
||||
|
||||
@UpdateDateColumn({ index: true })
|
||||
updatedAt!: Generated<Timestamp>;
|
||||
}
|
||||
@ -0,0 +1,126 @@
|
||||
import { Kysely } from 'kysely';
|
||||
import { AssetMetadataKey, SyncEntityType, SyncRequestType } from 'src/enum';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { DB } from 'src/schema';
|
||||
import { SyncTestContext } from 'test/medium.factory';
|
||||
import { getKyselyDB } from 'test/utils';
|
||||
|
||||
let defaultDatabase: Kysely<DB>;
|
||||
|
||||
const setup = async (db?: Kysely<DB>) => {
|
||||
const ctx = new SyncTestContext(db || defaultDatabase);
|
||||
const { auth, user, session } = await ctx.newSyncAuthUser();
|
||||
return { auth, user, session, ctx };
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
defaultDatabase = await getKyselyDB();
|
||||
});
|
||||
|
||||
describe(SyncEntityType.AssetMetadataV1, () => {
|
||||
it('should detect and sync new asset metadata', async () => {
|
||||
const { auth, user, ctx } = await setup();
|
||||
|
||||
const assetRepo = ctx.get(AssetRepository);
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await assetRepo.upsertMetadata(asset.id, [{ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'abc123' } }]);
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AssetMetadataV1]);
|
||||
expect(response).toHaveLength(1);
|
||||
expect(response).toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: {
|
||||
key: AssetMetadataKey.MobileApp,
|
||||
assetId: asset.id,
|
||||
value: { iCloudId: 'abc123' },
|
||||
},
|
||||
type: 'AssetMetadataV1',
|
||||
},
|
||||
]);
|
||||
|
||||
await ctx.syncAckAll(auth, response);
|
||||
await expect(ctx.syncStream(auth, [SyncRequestType.AssetMetadataV1])).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('should update asset metadata', async () => {
|
||||
const { auth, user, ctx } = await setup();
|
||||
|
||||
const assetRepo = ctx.get(AssetRepository);
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await assetRepo.upsertMetadata(asset.id, [{ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'abc123' } }]);
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AssetMetadataV1]);
|
||||
expect(response).toHaveLength(1);
|
||||
expect(response).toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: {
|
||||
key: AssetMetadataKey.MobileApp,
|
||||
assetId: asset.id,
|
||||
value: { iCloudId: 'abc123' },
|
||||
},
|
||||
type: 'AssetMetadataV1',
|
||||
},
|
||||
]);
|
||||
|
||||
await ctx.syncAckAll(auth, response);
|
||||
|
||||
await assetRepo.upsertMetadata(asset.id, [{ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'abc456' } }]);
|
||||
|
||||
const updatedResponse = await ctx.syncStream(auth, [SyncRequestType.AssetMetadataV1]);
|
||||
expect(updatedResponse).toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: {
|
||||
key: AssetMetadataKey.MobileApp,
|
||||
assetId: asset.id,
|
||||
value: { iCloudId: 'abc456' },
|
||||
},
|
||||
type: 'AssetMetadataV1',
|
||||
},
|
||||
]);
|
||||
|
||||
await ctx.syncAckAll(auth, updatedResponse);
|
||||
await expect(ctx.syncStream(auth, [SyncRequestType.AssetMetadataV1])).resolves.toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe(SyncEntityType.AssetMetadataDeleteV1, () => {
|
||||
it('should delete and sync asset metadata', async () => {
|
||||
const { auth, user, ctx } = await setup();
|
||||
|
||||
const assetRepo = ctx.get(AssetRepository);
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await assetRepo.upsertMetadata(asset.id, [{ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'abc123' } }]);
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AssetMetadataV1]);
|
||||
expect(response).toHaveLength(1);
|
||||
expect(response).toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: {
|
||||
key: AssetMetadataKey.MobileApp,
|
||||
assetId: asset.id,
|
||||
value: { iCloudId: 'abc123' },
|
||||
},
|
||||
type: 'AssetMetadataV1',
|
||||
},
|
||||
]);
|
||||
|
||||
await ctx.syncAckAll(auth, response);
|
||||
|
||||
await assetRepo.deleteMetadataByKey(asset.id, AssetMetadataKey.MobileApp);
|
||||
|
||||
await expect(ctx.syncStream(auth, [SyncRequestType.AssetMetadataV1])).resolves.toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: {
|
||||
assetId: asset.id,
|
||||
key: AssetMetadataKey.MobileApp,
|
||||
},
|
||||
type: 'AssetMetadataDeleteV1',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue