feat: getAssetEdits respond with edit IDs (#26445)

* feat: getAssetEdits respond with edit IDs

* chore: cleanup typings for edit API

* chore: cleanup types with jason

* fix: openapi sync

* fix: factory
This commit is contained in:
Brandon Wees
2026-02-23 14:57:57 -06:00
committed by GitHub
parent f616de5af8
commit e5722c525b
24 changed files with 462 additions and 549 deletions

View File

@@ -358,12 +358,11 @@ Class | Method | HTTP request | Description
- [AssetDeltaSyncDto](doc//AssetDeltaSyncDto.md) - [AssetDeltaSyncDto](doc//AssetDeltaSyncDto.md)
- [AssetDeltaSyncResponseDto](doc//AssetDeltaSyncResponseDto.md) - [AssetDeltaSyncResponseDto](doc//AssetDeltaSyncResponseDto.md)
- [AssetEditAction](doc//AssetEditAction.md) - [AssetEditAction](doc//AssetEditAction.md)
- [AssetEditActionCrop](doc//AssetEditActionCrop.md) - [AssetEditActionItemDto](doc//AssetEditActionItemDto.md)
- [AssetEditActionListDto](doc//AssetEditActionListDto.md) - [AssetEditActionItemDtoParameters](doc//AssetEditActionItemDtoParameters.md)
- [AssetEditActionListDtoEditsInner](doc//AssetEditActionListDtoEditsInner.md) - [AssetEditActionItemResponseDto](doc//AssetEditActionItemResponseDto.md)
- [AssetEditActionMirror](doc//AssetEditActionMirror.md) - [AssetEditsCreateDto](doc//AssetEditsCreateDto.md)
- [AssetEditActionRotate](doc//AssetEditActionRotate.md) - [AssetEditsResponseDto](doc//AssetEditsResponseDto.md)
- [AssetEditsDto](doc//AssetEditsDto.md)
- [AssetFaceCreateDto](doc//AssetFaceCreateDto.md) - [AssetFaceCreateDto](doc//AssetFaceCreateDto.md)
- [AssetFaceDeleteDto](doc//AssetFaceDeleteDto.md) - [AssetFaceDeleteDto](doc//AssetFaceDeleteDto.md)
- [AssetFaceResponseDto](doc//AssetFaceResponseDto.md) - [AssetFaceResponseDto](doc//AssetFaceResponseDto.md)

View File

@@ -97,12 +97,11 @@ part 'model/asset_copy_dto.dart';
part 'model/asset_delta_sync_dto.dart'; part 'model/asset_delta_sync_dto.dart';
part 'model/asset_delta_sync_response_dto.dart'; part 'model/asset_delta_sync_response_dto.dart';
part 'model/asset_edit_action.dart'; part 'model/asset_edit_action.dart';
part 'model/asset_edit_action_crop.dart'; part 'model/asset_edit_action_item_dto.dart';
part 'model/asset_edit_action_list_dto.dart'; part 'model/asset_edit_action_item_dto_parameters.dart';
part 'model/asset_edit_action_list_dto_edits_inner.dart'; part 'model/asset_edit_action_item_response_dto.dart';
part 'model/asset_edit_action_mirror.dart'; part 'model/asset_edits_create_dto.dart';
part 'model/asset_edit_action_rotate.dart'; part 'model/asset_edits_response_dto.dart';
part 'model/asset_edits_dto.dart';
part 'model/asset_face_create_dto.dart'; part 'model/asset_face_create_dto.dart';
part 'model/asset_face_delete_dto.dart'; part 'model/asset_face_delete_dto.dart';
part 'model/asset_face_response_dto.dart'; part 'model/asset_face_response_dto.dart';

View File

@@ -421,14 +421,14 @@ class AssetsApi {
/// ///
/// * [String] id (required): /// * [String] id (required):
/// ///
/// * [AssetEditActionListDto] assetEditActionListDto (required): /// * [AssetEditsCreateDto] assetEditsCreateDto (required):
Future<Response> editAssetWithHttpInfo(String id, AssetEditActionListDto assetEditActionListDto,) async { Future<Response> editAssetWithHttpInfo(String id, AssetEditsCreateDto assetEditsCreateDto,) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final apiPath = r'/assets/{id}/edits' final apiPath = r'/assets/{id}/edits'
.replaceAll('{id}', id); .replaceAll('{id}', id);
// ignore: prefer_final_locals // ignore: prefer_final_locals
Object? postBody = assetEditActionListDto; Object? postBody = assetEditsCreateDto;
final queryParams = <QueryParam>[]; final queryParams = <QueryParam>[];
final headerParams = <String, String>{}; final headerParams = <String, String>{};
@@ -456,9 +456,9 @@ class AssetsApi {
/// ///
/// * [String] id (required): /// * [String] id (required):
/// ///
/// * [AssetEditActionListDto] assetEditActionListDto (required): /// * [AssetEditsCreateDto] assetEditsCreateDto (required):
Future<AssetEditsDto?> editAsset(String id, AssetEditActionListDto assetEditActionListDto,) async { Future<AssetEditsResponseDto?> editAsset(String id, AssetEditsCreateDto assetEditsCreateDto,) async {
final response = await editAssetWithHttpInfo(id, assetEditActionListDto,); final response = await editAssetWithHttpInfo(id, assetEditsCreateDto,);
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
} }
@@ -466,7 +466,7 @@ class AssetsApi {
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string. // FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetEditsDto',) as AssetEditsDto; return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetEditsResponseDto',) as AssetEditsResponseDto;
} }
return null; return null;
@@ -576,7 +576,7 @@ class AssetsApi {
/// Parameters: /// Parameters:
/// ///
/// * [String] id (required): /// * [String] id (required):
Future<AssetEditsDto?> getAssetEdits(String id,) async { Future<AssetEditsResponseDto?> getAssetEdits(String id,) async {
final response = await getAssetEditsWithHttpInfo(id,); final response = await getAssetEditsWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
@@ -585,7 +585,7 @@ class AssetsApi {
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string. // FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetEditsDto',) as AssetEditsDto; return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetEditsResponseDto',) as AssetEditsResponseDto;
} }
return null; return null;

View File

@@ -240,18 +240,16 @@ class ApiClient {
return AssetDeltaSyncResponseDto.fromJson(value); return AssetDeltaSyncResponseDto.fromJson(value);
case 'AssetEditAction': case 'AssetEditAction':
return AssetEditActionTypeTransformer().decode(value); return AssetEditActionTypeTransformer().decode(value);
case 'AssetEditActionCrop': case 'AssetEditActionItemDto':
return AssetEditActionCrop.fromJson(value); return AssetEditActionItemDto.fromJson(value);
case 'AssetEditActionListDto': case 'AssetEditActionItemDtoParameters':
return AssetEditActionListDto.fromJson(value); return AssetEditActionItemDtoParameters.fromJson(value);
case 'AssetEditActionListDtoEditsInner': case 'AssetEditActionItemResponseDto':
return AssetEditActionListDtoEditsInner.fromJson(value); return AssetEditActionItemResponseDto.fromJson(value);
case 'AssetEditActionMirror': case 'AssetEditsCreateDto':
return AssetEditActionMirror.fromJson(value); return AssetEditsCreateDto.fromJson(value);
case 'AssetEditActionRotate': case 'AssetEditsResponseDto':
return AssetEditActionRotate.fromJson(value); return AssetEditsResponseDto.fromJson(value);
case 'AssetEditsDto':
return AssetEditsDto.fromJson(value);
case 'AssetFaceCreateDto': case 'AssetFaceCreateDto':
return AssetFaceCreateDto.fromJson(value); return AssetFaceCreateDto.fromJson(value);
case 'AssetFaceDeleteDto': case 'AssetFaceDeleteDto':

View File

@@ -10,9 +10,9 @@
part of openapi.api; part of openapi.api;
class AssetEditActionCrop { class AssetEditActionItemDto {
/// Returns a new [AssetEditActionCrop] instance. /// Returns a new [AssetEditActionItemDto] instance.
AssetEditActionCrop({ AssetEditActionItemDto({
required this.action, required this.action,
required this.parameters, required this.parameters,
}); });
@@ -20,10 +20,10 @@ class AssetEditActionCrop {
/// Type of edit action to perform /// Type of edit action to perform
AssetEditAction action; AssetEditAction action;
CropParameters parameters; AssetEditActionItemDtoParameters parameters;
@override @override
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionCrop && bool operator ==(Object other) => identical(this, other) || other is AssetEditActionItemDto &&
other.action == action && other.action == action &&
other.parameters == parameters; other.parameters == parameters;
@@ -34,7 +34,7 @@ class AssetEditActionCrop {
(parameters.hashCode); (parameters.hashCode);
@override @override
String toString() => 'AssetEditActionCrop[action=$action, parameters=$parameters]'; String toString() => 'AssetEditActionItemDto[action=$action, parameters=$parameters]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -43,27 +43,27 @@ class AssetEditActionCrop {
return json; return json;
} }
/// Returns a new [AssetEditActionCrop] instance and imports its values from /// Returns a new [AssetEditActionItemDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetEditActionCrop? fromJson(dynamic value) { static AssetEditActionItemDto? fromJson(dynamic value) {
upgradeDto(value, "AssetEditActionCrop"); upgradeDto(value, "AssetEditActionItemDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return AssetEditActionCrop( return AssetEditActionItemDto(
action: AssetEditAction.fromJson(json[r'action'])!, action: AssetEditAction.fromJson(json[r'action'])!,
parameters: CropParameters.fromJson(json[r'parameters'])!, parameters: AssetEditActionItemDtoParameters.fromJson(json[r'parameters'])!,
); );
} }
return null; return null;
} }
static List<AssetEditActionCrop> listFromJson(dynamic json, {bool growable = false,}) { static List<AssetEditActionItemDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditActionCrop>[]; final result = <AssetEditActionItemDto>[];
if (json is List && json.isNotEmpty) { if (json is List && json.isNotEmpty) {
for (final row in json) { for (final row in json) {
final value = AssetEditActionCrop.fromJson(row); final value = AssetEditActionItemDto.fromJson(row);
if (value != null) { if (value != null) {
result.add(value); result.add(value);
} }
@@ -72,12 +72,12 @@ class AssetEditActionCrop {
return result.toList(growable: growable); return result.toList(growable: growable);
} }
static Map<String, AssetEditActionCrop> mapFromJson(dynamic json) { static Map<String, AssetEditActionItemDto> mapFromJson(dynamic json) {
final map = <String, AssetEditActionCrop>{}; final map = <String, AssetEditActionItemDto>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) { for (final entry in json.entries) {
final value = AssetEditActionCrop.fromJson(entry.value); final value = AssetEditActionItemDto.fromJson(entry.value);
if (value != null) { if (value != null) {
map[entry.key] = value; map[entry.key] = value;
} }
@@ -86,14 +86,14 @@ class AssetEditActionCrop {
return map; return map;
} }
// maps a json object with a list of AssetEditActionCrop-objects as value to a dart map // maps a json object with a list of AssetEditActionItemDto-objects as value to a dart map
static Map<String, List<AssetEditActionCrop>> mapListFromJson(dynamic json, {bool growable = false,}) { static Map<String, List<AssetEditActionItemDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditActionCrop>>{}; final map = <String, List<AssetEditActionItemDto>>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments // ignore: parameter_assignments
json = json.cast<String, dynamic>(); json = json.cast<String, dynamic>();
for (final entry in json.entries) { for (final entry in json.entries) {
map[entry.key] = AssetEditActionCrop.listFromJson(entry.value, growable: growable,); map[entry.key] = AssetEditActionItemDto.listFromJson(entry.value, growable: growable,);
} }
} }
return map; return map;

View File

@@ -0,0 +1,153 @@
//
// 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 AssetEditActionItemDtoParameters {
/// Returns a new [AssetEditActionItemDtoParameters] instance.
AssetEditActionItemDtoParameters({
required this.height,
required this.width,
required this.x,
required this.y,
required this.angle,
required this.axis,
});
/// Height of the crop
///
/// Minimum value: 1
num height;
/// Width of the crop
///
/// Minimum value: 1
num width;
/// Top-Left X coordinate of crop
///
/// Minimum value: 0
num x;
/// Top-Left Y coordinate of crop
///
/// Minimum value: 0
num y;
/// Rotation angle in degrees
num angle;
/// Axis to mirror along
MirrorAxis axis;
@override
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionItemDtoParameters &&
other.height == height &&
other.width == width &&
other.x == x &&
other.y == y &&
other.angle == angle &&
other.axis == axis;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(height.hashCode) +
(width.hashCode) +
(x.hashCode) +
(y.hashCode) +
(angle.hashCode) +
(axis.hashCode);
@override
String toString() => 'AssetEditActionItemDtoParameters[height=$height, width=$width, x=$x, y=$y, angle=$angle, axis=$axis]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'height'] = this.height;
json[r'width'] = this.width;
json[r'x'] = this.x;
json[r'y'] = this.y;
json[r'angle'] = this.angle;
json[r'axis'] = this.axis;
return json;
}
/// Returns a new [AssetEditActionItemDtoParameters] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static AssetEditActionItemDtoParameters? fromJson(dynamic value) {
upgradeDto(value, "AssetEditActionItemDtoParameters");
if (value is Map) {
final json = value.cast<String, dynamic>();
return AssetEditActionItemDtoParameters(
height: num.parse('${json[r'height']}'),
width: num.parse('${json[r'width']}'),
x: num.parse('${json[r'x']}'),
y: num.parse('${json[r'y']}'),
angle: num.parse('${json[r'angle']}'),
axis: MirrorAxis.fromJson(json[r'axis'])!,
);
}
return null;
}
static List<AssetEditActionItemDtoParameters> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditActionItemDtoParameters>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetEditActionItemDtoParameters.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, AssetEditActionItemDtoParameters> mapFromJson(dynamic json) {
final map = <String, AssetEditActionItemDtoParameters>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = AssetEditActionItemDtoParameters.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of AssetEditActionItemDtoParameters-objects as value to a dart map
static Map<String, List<AssetEditActionItemDtoParameters>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditActionItemDtoParameters>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = AssetEditActionItemDtoParameters.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'height',
'width',
'x',
'y',
'angle',
'axis',
};
}

View File

@@ -10,60 +10,67 @@
part of openapi.api; part of openapi.api;
class AssetEditActionListDtoEditsInner { class AssetEditActionItemResponseDto {
/// Returns a new [AssetEditActionListDtoEditsInner] instance. /// Returns a new [AssetEditActionItemResponseDto] instance.
AssetEditActionListDtoEditsInner({ AssetEditActionItemResponseDto({
required this.action, required this.action,
required this.id,
required this.parameters, required this.parameters,
}); });
/// Type of edit action to perform /// Type of edit action to perform
AssetEditAction action; AssetEditAction action;
MirrorParameters parameters; String id;
AssetEditActionItemDtoParameters parameters;
@override @override
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionListDtoEditsInner && bool operator ==(Object other) => identical(this, other) || other is AssetEditActionItemResponseDto &&
other.action == action && other.action == action &&
other.id == id &&
other.parameters == parameters; other.parameters == parameters;
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(action.hashCode) + (action.hashCode) +
(id.hashCode) +
(parameters.hashCode); (parameters.hashCode);
@override @override
String toString() => 'AssetEditActionListDtoEditsInner[action=$action, parameters=$parameters]'; String toString() => 'AssetEditActionItemResponseDto[action=$action, id=$id, parameters=$parameters]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json[r'action'] = this.action; json[r'action'] = this.action;
json[r'id'] = this.id;
json[r'parameters'] = this.parameters; json[r'parameters'] = this.parameters;
return json; return json;
} }
/// Returns a new [AssetEditActionListDtoEditsInner] instance and imports its values from /// Returns a new [AssetEditActionItemResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetEditActionListDtoEditsInner? fromJson(dynamic value) { static AssetEditActionItemResponseDto? fromJson(dynamic value) {
upgradeDto(value, "AssetEditActionListDtoEditsInner"); upgradeDto(value, "AssetEditActionItemResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return AssetEditActionListDtoEditsInner( return AssetEditActionItemResponseDto(
action: AssetEditAction.fromJson(json[r'action'])!, action: AssetEditAction.fromJson(json[r'action'])!,
parameters: MirrorParameters.fromJson(json[r'parameters'])!, id: mapValueOfType<String>(json, r'id')!,
parameters: AssetEditActionItemDtoParameters.fromJson(json[r'parameters'])!,
); );
} }
return null; return null;
} }
static List<AssetEditActionListDtoEditsInner> listFromJson(dynamic json, {bool growable = false,}) { static List<AssetEditActionItemResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditActionListDtoEditsInner>[]; final result = <AssetEditActionItemResponseDto>[];
if (json is List && json.isNotEmpty) { if (json is List && json.isNotEmpty) {
for (final row in json) { for (final row in json) {
final value = AssetEditActionListDtoEditsInner.fromJson(row); final value = AssetEditActionItemResponseDto.fromJson(row);
if (value != null) { if (value != null) {
result.add(value); result.add(value);
} }
@@ -72,12 +79,12 @@ class AssetEditActionListDtoEditsInner {
return result.toList(growable: growable); return result.toList(growable: growable);
} }
static Map<String, AssetEditActionListDtoEditsInner> mapFromJson(dynamic json) { static Map<String, AssetEditActionItemResponseDto> mapFromJson(dynamic json) {
final map = <String, AssetEditActionListDtoEditsInner>{}; final map = <String, AssetEditActionItemResponseDto>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) { for (final entry in json.entries) {
final value = AssetEditActionListDtoEditsInner.fromJson(entry.value); final value = AssetEditActionItemResponseDto.fromJson(entry.value);
if (value != null) { if (value != null) {
map[entry.key] = value; map[entry.key] = value;
} }
@@ -86,14 +93,14 @@ class AssetEditActionListDtoEditsInner {
return map; return map;
} }
// maps a json object with a list of AssetEditActionListDtoEditsInner-objects as value to a dart map // maps a json object with a list of AssetEditActionItemResponseDto-objects as value to a dart map
static Map<String, List<AssetEditActionListDtoEditsInner>> mapListFromJson(dynamic json, {bool growable = false,}) { static Map<String, List<AssetEditActionItemResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditActionListDtoEditsInner>>{}; final map = <String, List<AssetEditActionItemResponseDto>>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments // ignore: parameter_assignments
json = json.cast<String, dynamic>(); json = json.cast<String, dynamic>();
for (final entry in json.entries) { for (final entry in json.entries) {
map[entry.key] = AssetEditActionListDtoEditsInner.listFromJson(entry.value, growable: growable,); map[entry.key] = AssetEditActionItemResponseDto.listFromJson(entry.value, growable: growable,);
} }
} }
return map; return map;
@@ -102,6 +109,7 @@ class AssetEditActionListDtoEditsInner {
/// The list of required keys that must be present in a JSON. /// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{ static const requiredKeys = <String>{
'action', 'action',
'id',
'parameters', 'parameters',
}; };
} }

View File

@@ -1,108 +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 AssetEditActionMirror {
/// Returns a new [AssetEditActionMirror] instance.
AssetEditActionMirror({
required this.action,
required this.parameters,
});
/// Type of edit action to perform
AssetEditAction action;
MirrorParameters parameters;
@override
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionMirror &&
other.action == action &&
other.parameters == parameters;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(action.hashCode) +
(parameters.hashCode);
@override
String toString() => 'AssetEditActionMirror[action=$action, parameters=$parameters]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'action'] = this.action;
json[r'parameters'] = this.parameters;
return json;
}
/// Returns a new [AssetEditActionMirror] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static AssetEditActionMirror? fromJson(dynamic value) {
upgradeDto(value, "AssetEditActionMirror");
if (value is Map) {
final json = value.cast<String, dynamic>();
return AssetEditActionMirror(
action: AssetEditAction.fromJson(json[r'action'])!,
parameters: MirrorParameters.fromJson(json[r'parameters'])!,
);
}
return null;
}
static List<AssetEditActionMirror> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditActionMirror>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetEditActionMirror.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, AssetEditActionMirror> mapFromJson(dynamic json) {
final map = <String, AssetEditActionMirror>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = AssetEditActionMirror.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of AssetEditActionMirror-objects as value to a dart map
static Map<String, List<AssetEditActionMirror>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditActionMirror>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = AssetEditActionMirror.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'action',
'parameters',
};
}

View File

@@ -1,108 +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 AssetEditActionRotate {
/// Returns a new [AssetEditActionRotate] instance.
AssetEditActionRotate({
required this.action,
required this.parameters,
});
/// Type of edit action to perform
AssetEditAction action;
RotateParameters parameters;
@override
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionRotate &&
other.action == action &&
other.parameters == parameters;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(action.hashCode) +
(parameters.hashCode);
@override
String toString() => 'AssetEditActionRotate[action=$action, parameters=$parameters]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'action'] = this.action;
json[r'parameters'] = this.parameters;
return json;
}
/// Returns a new [AssetEditActionRotate] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static AssetEditActionRotate? fromJson(dynamic value) {
upgradeDto(value, "AssetEditActionRotate");
if (value is Map) {
final json = value.cast<String, dynamic>();
return AssetEditActionRotate(
action: AssetEditAction.fromJson(json[r'action'])!,
parameters: RotateParameters.fromJson(json[r'parameters'])!,
);
}
return null;
}
static List<AssetEditActionRotate> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditActionRotate>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetEditActionRotate.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, AssetEditActionRotate> mapFromJson(dynamic json) {
final map = <String, AssetEditActionRotate>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = AssetEditActionRotate.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of AssetEditActionRotate-objects as value to a dart map
static Map<String, List<AssetEditActionRotate>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditActionRotate>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = AssetEditActionRotate.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'action',
'parameters',
};
}

View File

@@ -10,17 +10,17 @@
part of openapi.api; part of openapi.api;
class AssetEditActionListDto { class AssetEditsCreateDto {
/// Returns a new [AssetEditActionListDto] instance. /// Returns a new [AssetEditsCreateDto] instance.
AssetEditActionListDto({ AssetEditsCreateDto({
this.edits = const [], this.edits = const [],
}); });
/// List of edit actions to apply (crop, rotate, or mirror) /// List of edit actions to apply (crop, rotate, or mirror)
List<AssetEditActionListDtoEditsInner> edits; List<AssetEditActionItemDto> edits;
@override @override
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionListDto && bool operator ==(Object other) => identical(this, other) || other is AssetEditsCreateDto &&
_deepEquality.equals(other.edits, edits); _deepEquality.equals(other.edits, edits);
@override @override
@@ -29,7 +29,7 @@ class AssetEditActionListDto {
(edits.hashCode); (edits.hashCode);
@override @override
String toString() => 'AssetEditActionListDto[edits=$edits]'; String toString() => 'AssetEditsCreateDto[edits=$edits]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -37,26 +37,26 @@ class AssetEditActionListDto {
return json; return json;
} }
/// Returns a new [AssetEditActionListDto] instance and imports its values from /// Returns a new [AssetEditsCreateDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetEditActionListDto? fromJson(dynamic value) { static AssetEditsCreateDto? fromJson(dynamic value) {
upgradeDto(value, "AssetEditActionListDto"); upgradeDto(value, "AssetEditsCreateDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return AssetEditActionListDto( return AssetEditsCreateDto(
edits: AssetEditActionListDtoEditsInner.listFromJson(json[r'edits']), edits: AssetEditActionItemDto.listFromJson(json[r'edits']),
); );
} }
return null; return null;
} }
static List<AssetEditActionListDto> listFromJson(dynamic json, {bool growable = false,}) { static List<AssetEditsCreateDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditActionListDto>[]; final result = <AssetEditsCreateDto>[];
if (json is List && json.isNotEmpty) { if (json is List && json.isNotEmpty) {
for (final row in json) { for (final row in json) {
final value = AssetEditActionListDto.fromJson(row); final value = AssetEditsCreateDto.fromJson(row);
if (value != null) { if (value != null) {
result.add(value); result.add(value);
} }
@@ -65,12 +65,12 @@ class AssetEditActionListDto {
return result.toList(growable: growable); return result.toList(growable: growable);
} }
static Map<String, AssetEditActionListDto> mapFromJson(dynamic json) { static Map<String, AssetEditsCreateDto> mapFromJson(dynamic json) {
final map = <String, AssetEditActionListDto>{}; final map = <String, AssetEditsCreateDto>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) { for (final entry in json.entries) {
final value = AssetEditActionListDto.fromJson(entry.value); final value = AssetEditsCreateDto.fromJson(entry.value);
if (value != null) { if (value != null) {
map[entry.key] = value; map[entry.key] = value;
} }
@@ -79,14 +79,14 @@ class AssetEditActionListDto {
return map; return map;
} }
// maps a json object with a list of AssetEditActionListDto-objects as value to a dart map // maps a json object with a list of AssetEditsCreateDto-objects as value to a dart map
static Map<String, List<AssetEditActionListDto>> mapListFromJson(dynamic json, {bool growable = false,}) { static Map<String, List<AssetEditsCreateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditActionListDto>>{}; final map = <String, List<AssetEditsCreateDto>>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments // ignore: parameter_assignments
json = json.cast<String, dynamic>(); json = json.cast<String, dynamic>();
for (final entry in json.entries) { for (final entry in json.entries) {
map[entry.key] = AssetEditActionListDto.listFromJson(entry.value, growable: growable,); map[entry.key] = AssetEditsCreateDto.listFromJson(entry.value, growable: growable,);
} }
} }
return map; return map;

View File

@@ -10,21 +10,21 @@
part of openapi.api; part of openapi.api;
class AssetEditsDto { class AssetEditsResponseDto {
/// Returns a new [AssetEditsDto] instance. /// Returns a new [AssetEditsResponseDto] instance.
AssetEditsDto({ AssetEditsResponseDto({
required this.assetId, required this.assetId,
this.edits = const [], this.edits = const [],
}); });
/// Asset ID to apply edits to /// Asset ID these edits belong to
String assetId; String assetId;
/// List of edit actions to apply (crop, rotate, or mirror) /// List of edit actions applied to the asset
List<AssetEditActionListDtoEditsInner> edits; List<AssetEditActionItemResponseDto> edits;
@override @override
bool operator ==(Object other) => identical(this, other) || other is AssetEditsDto && bool operator ==(Object other) => identical(this, other) || other is AssetEditsResponseDto &&
other.assetId == assetId && other.assetId == assetId &&
_deepEquality.equals(other.edits, edits); _deepEquality.equals(other.edits, edits);
@@ -35,7 +35,7 @@ class AssetEditsDto {
(edits.hashCode); (edits.hashCode);
@override @override
String toString() => 'AssetEditsDto[assetId=$assetId, edits=$edits]'; String toString() => 'AssetEditsResponseDto[assetId=$assetId, edits=$edits]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -44,27 +44,27 @@ class AssetEditsDto {
return json; return json;
} }
/// Returns a new [AssetEditsDto] instance and imports its values from /// Returns a new [AssetEditsResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetEditsDto? fromJson(dynamic value) { static AssetEditsResponseDto? fromJson(dynamic value) {
upgradeDto(value, "AssetEditsDto"); upgradeDto(value, "AssetEditsResponseDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return AssetEditsDto( return AssetEditsResponseDto(
assetId: mapValueOfType<String>(json, r'assetId')!, assetId: mapValueOfType<String>(json, r'assetId')!,
edits: AssetEditActionListDtoEditsInner.listFromJson(json[r'edits']), edits: AssetEditActionItemResponseDto.listFromJson(json[r'edits']),
); );
} }
return null; return null;
} }
static List<AssetEditsDto> listFromJson(dynamic json, {bool growable = false,}) { static List<AssetEditsResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditsDto>[]; final result = <AssetEditsResponseDto>[];
if (json is List && json.isNotEmpty) { if (json is List && json.isNotEmpty) {
for (final row in json) { for (final row in json) {
final value = AssetEditsDto.fromJson(row); final value = AssetEditsResponseDto.fromJson(row);
if (value != null) { if (value != null) {
result.add(value); result.add(value);
} }
@@ -73,12 +73,12 @@ class AssetEditsDto {
return result.toList(growable: growable); return result.toList(growable: growable);
} }
static Map<String, AssetEditsDto> mapFromJson(dynamic json) { static Map<String, AssetEditsResponseDto> mapFromJson(dynamic json) {
final map = <String, AssetEditsDto>{}; final map = <String, AssetEditsResponseDto>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) { for (final entry in json.entries) {
final value = AssetEditsDto.fromJson(entry.value); final value = AssetEditsResponseDto.fromJson(entry.value);
if (value != null) { if (value != null) {
map[entry.key] = value; map[entry.key] = value;
} }
@@ -87,14 +87,14 @@ class AssetEditsDto {
return map; return map;
} }
// maps a json object with a list of AssetEditsDto-objects as value to a dart map // maps a json object with a list of AssetEditsResponseDto-objects as value to a dart map
static Map<String, List<AssetEditsDto>> mapListFromJson(dynamic json, {bool growable = false,}) { static Map<String, List<AssetEditsResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditsDto>>{}; final map = <String, List<AssetEditsResponseDto>>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments // ignore: parameter_assignments
json = json.cast<String, dynamic>(); json = json.cast<String, dynamic>();
for (final entry in json.entries) { for (final entry in json.entries) {
map[entry.key] = AssetEditsDto.listFromJson(entry.value, growable: growable,); map[entry.key] = AssetEditsResponseDto.listFromJson(entry.value, growable: growable,);
} }
} }
return map; return map;

View File

@@ -3703,7 +3703,7 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/AssetEditsDto" "$ref": "#/components/schemas/AssetEditsResponseDto"
} }
} }
}, },
@@ -3756,7 +3756,7 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/AssetEditActionListDto" "$ref": "#/components/schemas/AssetEditsCreateDto"
} }
} }
}, },
@@ -3767,7 +3767,7 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/AssetEditsDto" "$ref": "#/components/schemas/AssetEditsResponseDto"
} }
} }
}, },
@@ -16082,7 +16082,7 @@
], ],
"type": "string" "type": "string"
}, },
"AssetEditActionCrop": { "AssetEditActionItemDto": {
"properties": { "properties": {
"action": { "action": {
"allOf": [ "allOf": [
@@ -16093,7 +16093,18 @@
"description": "Type of edit action to perform" "description": "Type of edit action to perform"
}, },
"parameters": { "parameters": {
"$ref": "#/components/schemas/CropParameters" "anyOf": [
{
"$ref": "#/components/schemas/CropParameters"
},
{
"$ref": "#/components/schemas/RotateParameters"
},
{
"$ref": "#/components/schemas/MirrorParameters"
}
],
"description": "List of edit actions to apply (crop, rotate, or mirror)"
} }
}, },
"required": [ "required": [
@@ -16102,30 +16113,48 @@
], ],
"type": "object" "type": "object"
}, },
"AssetEditActionListDto": { "AssetEditActionItemResponseDto": {
"properties": {
"action": {
"allOf": [
{
"$ref": "#/components/schemas/AssetEditAction"
}
],
"description": "Type of edit action to perform"
},
"id": {
"format": "uuid",
"type": "string"
},
"parameters": {
"anyOf": [
{
"$ref": "#/components/schemas/CropParameters"
},
{
"$ref": "#/components/schemas/RotateParameters"
},
{
"$ref": "#/components/schemas/MirrorParameters"
}
],
"description": "List of edit actions to apply (crop, rotate, or mirror)"
}
},
"required": [
"action",
"id",
"parameters"
],
"type": "object"
},
"AssetEditsCreateDto": {
"properties": { "properties": {
"edits": { "edits": {
"description": "List of edit actions to apply (crop, rotate, or mirror)", "description": "List of edit actions to apply (crop, rotate, or mirror)",
"items": { "items": {
"anyOf": [ "$ref": "#/components/schemas/AssetEditActionItemDto"
{
"$ref": "#/components/schemas/AssetEditActionCrop"
},
{
"$ref": "#/components/schemas/AssetEditActionRotate"
},
{
"$ref": "#/components/schemas/AssetEditActionMirror"
}
],
"discriminator": {
"mapping": {
"crop": "#/components/schemas/AssetEditActionCrop",
"mirror": "#/components/schemas/AssetEditActionMirror",
"rotate": "#/components/schemas/AssetEditActionRotate"
},
"propertyName": "action"
}
}, },
"minItems": 1, "minItems": 1,
"type": "array" "type": "array"
@@ -16136,77 +16165,18 @@
], ],
"type": "object" "type": "object"
}, },
"AssetEditActionMirror": { "AssetEditsResponseDto": {
"properties": {
"action": {
"allOf": [
{
"$ref": "#/components/schemas/AssetEditAction"
}
],
"description": "Type of edit action to perform"
},
"parameters": {
"$ref": "#/components/schemas/MirrorParameters"
}
},
"required": [
"action",
"parameters"
],
"type": "object"
},
"AssetEditActionRotate": {
"properties": {
"action": {
"allOf": [
{
"$ref": "#/components/schemas/AssetEditAction"
}
],
"description": "Type of edit action to perform"
},
"parameters": {
"$ref": "#/components/schemas/RotateParameters"
}
},
"required": [
"action",
"parameters"
],
"type": "object"
},
"AssetEditsDto": {
"properties": { "properties": {
"assetId": { "assetId": {
"description": "Asset ID to apply edits to", "description": "Asset ID these edits belong to",
"format": "uuid", "format": "uuid",
"type": "string" "type": "string"
}, },
"edits": { "edits": {
"description": "List of edit actions to apply (crop, rotate, or mirror)", "description": "List of edit actions applied to the asset",
"items": { "items": {
"anyOf": [ "$ref": "#/components/schemas/AssetEditActionItemResponseDto"
{
"$ref": "#/components/schemas/AssetEditActionCrop"
},
{
"$ref": "#/components/schemas/AssetEditActionRotate"
},
{
"$ref": "#/components/schemas/AssetEditActionMirror"
}
],
"discriminator": {
"mapping": {
"crop": "#/components/schemas/AssetEditActionCrop",
"mirror": "#/components/schemas/AssetEditActionMirror",
"rotate": "#/components/schemas/AssetEditActionRotate"
},
"propertyName": "action"
}
}, },
"minItems": 1,
"type": "array" "type": "array"
} }
}, },

View File

@@ -959,38 +959,36 @@ export type CropParameters = {
/** Top-Left Y coordinate of crop */ /** Top-Left Y coordinate of crop */
y: number; y: number;
}; };
export type AssetEditActionCrop = {
/** Type of edit action to perform */
action: AssetEditAction;
parameters: CropParameters;
};
export type RotateParameters = { export type RotateParameters = {
/** Rotation angle in degrees */ /** Rotation angle in degrees */
angle: number; angle: number;
}; };
export type AssetEditActionRotate = {
/** Type of edit action to perform */
action: AssetEditAction;
parameters: RotateParameters;
};
export type MirrorParameters = { export type MirrorParameters = {
/** Axis to mirror along */ /** Axis to mirror along */
axis: MirrorAxis; axis: MirrorAxis;
}; };
export type AssetEditActionMirror = { export type AssetEditActionItemResponseDto = {
/** Type of edit action to perform */ /** Type of edit action to perform */
action: AssetEditAction; action: AssetEditAction;
parameters: MirrorParameters; id: string;
/** List of edit actions to apply (crop, rotate, or mirror) */
parameters: CropParameters | RotateParameters | MirrorParameters;
}; };
export type AssetEditsDto = { export type AssetEditsResponseDto = {
/** Asset ID to apply edits to */ /** Asset ID these edits belong to */
assetId: string; assetId: string;
/** List of edit actions to apply (crop, rotate, or mirror) */ /** List of edit actions applied to the asset */
edits: (AssetEditActionCrop | AssetEditActionRotate | AssetEditActionMirror)[]; edits: AssetEditActionItemResponseDto[];
}; };
export type AssetEditActionListDto = { export type AssetEditActionItemDto = {
/** Type of edit action to perform */
action: AssetEditAction;
/** List of edit actions to apply (crop, rotate, or mirror) */ /** List of edit actions to apply (crop, rotate, or mirror) */
edits: (AssetEditActionCrop | AssetEditActionRotate | AssetEditActionMirror)[]; parameters: CropParameters | RotateParameters | MirrorParameters;
};
export type AssetEditsCreateDto = {
/** List of edit actions to apply (crop, rotate, or mirror) */
edits: AssetEditActionItemDto[];
}; };
export type AssetMetadataResponseDto = { export type AssetMetadataResponseDto = {
/** Metadata key */ /** Metadata key */
@@ -4149,7 +4147,7 @@ export function getAssetEdits({ id }: {
}, opts?: Oazapfts.RequestOpts) { }, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{ return oazapfts.ok(oazapfts.fetchJson<{
status: 200; status: 200;
data: AssetEditsDto; data: AssetEditsResponseDto;
}>(`/assets/${encodeURIComponent(id)}/edits`, { }>(`/assets/${encodeURIComponent(id)}/edits`, {
...opts ...opts
})); }));
@@ -4157,17 +4155,17 @@ export function getAssetEdits({ id }: {
/** /**
* Apply edits to an existing asset * Apply edits to an existing asset
*/ */
export function editAsset({ id, assetEditActionListDto }: { export function editAsset({ id, assetEditsCreateDto }: {
id: string; id: string;
assetEditActionListDto: AssetEditActionListDto; assetEditsCreateDto: AssetEditsCreateDto;
}, opts?: Oazapfts.RequestOpts) { }, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{ return oazapfts.ok(oazapfts.fetchJson<{
status: 200; status: 200;
data: AssetEditsDto; data: AssetEditsResponseDto;
}>(`/assets/${encodeURIComponent(id)}/edits`, oazapfts.json({ }>(`/assets/${encodeURIComponent(id)}/edits`, oazapfts.json({
...opts, ...opts,
method: "PUT", method: "PUT",
body: assetEditActionListDto body: assetEditsCreateDto
}))); })));
} }
/** /**

View File

@@ -369,6 +369,31 @@ describe(AssetController.name, () => {
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 check the action and parameters discriminator', async () => {
const { status, body } = await request(ctx.getHttpServer())
.put(`/assets/${factory.uuid()}/edits`)
.send({
edits: [
{
action: 'rotate',
parameters: {
x: 0,
y: 0,
width: 100,
height: 100,
},
},
],
});
expect(status).toBe(400);
expect(body).toEqual(
factory.responses.badRequest(
expect.arrayContaining([expect.stringContaining('parameters.angle must be one of the following values')]),
),
);
});
it('should require at least one edit', async () => { it('should require at least one edit', async () => {
const { status, body } = await request(ctx.getHttpServer()) const { status, body } = await request(ctx.getHttpServer())
.put(`/assets/${factory.uuid()}/edits`) .put(`/assets/${factory.uuid()}/edits`)

View File

@@ -20,7 +20,7 @@ import {
UpdateAssetDto, UpdateAssetDto,
} 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 { AssetEditActionListDto, AssetEditsDto } from 'src/dtos/editing.dto'; import { AssetEditsCreateDto, AssetEditsResponseDto } from 'src/dtos/editing.dto';
import { AssetOcrResponseDto } from 'src/dtos/ocr.dto'; import { AssetOcrResponseDto } from 'src/dtos/ocr.dto';
import { ApiTag, Permission, RouteKey } from 'src/enum'; import { ApiTag, Permission, RouteKey } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { Auth, Authenticated } from 'src/middleware/auth.guard';
@@ -235,7 +235,7 @@ export class AssetController {
description: 'Retrieve a series of edit actions (crop, rotate, mirror) associated with the specified asset.', description: 'Retrieve a series of edit actions (crop, rotate, mirror) associated with the specified asset.',
history: new HistoryBuilder().added('v2.5.0').beta('v2.5.0'), history: new HistoryBuilder().added('v2.5.0').beta('v2.5.0'),
}) })
getAssetEdits(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetEditsDto> { getAssetEdits(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetEditsResponseDto> {
return this.service.getAssetEdits(auth, id); return this.service.getAssetEdits(auth, id);
} }
@@ -249,8 +249,8 @@ export class AssetController {
editAsset( editAsset(
@Auth() auth: AuthDto, @Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto, @Param() { id }: UUIDParamDto,
@Body() dto: AssetEditActionListDto, @Body() dto: AssetEditsCreateDto,
): Promise<AssetEditsDto> { ): Promise<AssetEditsResponseDto> {
return this.service.editAsset(auth, id, dto); return this.service.editAsset(auth, id, dto);
} }

View File

@@ -1,7 +1,8 @@
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger'; import { ApiProperty, getSchemaPath } from '@nestjs/swagger';
import { ClassConstructor, plainToInstance, Transform, Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { ArrayMinSize, IsEnum, IsInt, Min, ValidateNested } from 'class-validator'; import { ArrayMinSize, IsEnum, IsInt, Min, ValidateNested } from 'class-validator';
import { IsAxisAlignedRotation, IsUniqueEditActions, ValidateUUID } from 'src/validation'; import { ExtraModel } from 'src/dtos/sync.dto';
import { IsAxisAlignedRotation, IsUniqueEditActions, ValidateEnum, ValidateUUID } from 'src/validation';
export enum AssetEditAction { export enum AssetEditAction {
Crop = 'crop', Crop = 'crop',
@@ -14,6 +15,7 @@ export enum MirrorAxis {
Vertical = 'vertical', Vertical = 'vertical',
} }
@ExtraModel()
export class CropParameters { export class CropParameters {
@IsInt() @IsInt()
@Min(0) @Min(0)
@@ -36,48 +38,21 @@ export class CropParameters {
height!: number; height!: number;
} }
@ExtraModel()
export class RotateParameters { export class RotateParameters {
@IsAxisAlignedRotation() @IsAxisAlignedRotation()
@ApiProperty({ description: 'Rotation angle in degrees' }) @ApiProperty({ description: 'Rotation angle in degrees' })
angle!: number; angle!: number;
} }
@ExtraModel()
export class MirrorParameters { export class MirrorParameters {
@IsEnum(MirrorAxis) @IsEnum(MirrorAxis)
@ApiProperty({ enum: MirrorAxis, enumName: 'MirrorAxis', description: 'Axis to mirror along' }) @ApiProperty({ enum: MirrorAxis, enumName: 'MirrorAxis', description: 'Axis to mirror along' })
axis!: MirrorAxis; axis!: MirrorAxis;
} }
class AssetEditActionBase { export type AssetEditParameters = CropParameters | RotateParameters | MirrorParameters;
@IsEnum(AssetEditAction)
@ApiProperty({ enum: AssetEditAction, enumName: 'AssetEditAction', description: 'Type of edit action to perform' })
action!: AssetEditAction;
}
export class AssetEditActionCrop extends AssetEditActionBase {
@ValidateNested()
@Type(() => CropParameters)
// Description lives on schema to avoid duplication
@ApiProperty({ description: undefined })
parameters!: CropParameters;
}
export class AssetEditActionRotate extends AssetEditActionBase {
@ValidateNested()
@Type(() => RotateParameters)
// Description lives on schema to avoid duplication
@ApiProperty({ description: undefined })
parameters!: RotateParameters;
}
export class AssetEditActionMirror extends AssetEditActionBase {
@ValidateNested()
@Type(() => MirrorParameters)
// Description lives on schema to avoid duplication
@ApiProperty({ description: undefined })
parameters!: MirrorParameters;
}
export type AssetEditActionItem = export type AssetEditActionItem =
| { | {
action: AssetEditAction.Crop; action: AssetEditAction.Crop;
@@ -92,47 +67,48 @@ export type AssetEditActionItem =
parameters: MirrorParameters; parameters: MirrorParameters;
}; };
export type AssetEditActionParameter = { export class AssetEditActionItemDto {
[AssetEditAction.Crop]: CropParameters; @ValidateEnum({ name: 'AssetEditAction', enum: AssetEditAction, description: 'Type of edit action to perform' })
[AssetEditAction.Rotate]: RotateParameters; action!: AssetEditAction;
[AssetEditAction.Mirror]: MirrorParameters;
@ApiProperty({
description: 'List of edit actions to apply (crop, rotate, or mirror)',
anyOf: [CropParameters, RotateParameters, MirrorParameters].map((type) => ({
$ref: getSchemaPath(type),
})),
})
@ValidateNested()
@Type((options) => actionParameterMap[options?.object.action as keyof AssetEditActionParameter])
parameters!: AssetEditActionItem['parameters'];
}
export class AssetEditActionItemResponseDto extends AssetEditActionItemDto {
@ValidateUUID()
id!: string;
}
export type AssetEditActionParameter = typeof actionParameterMap;
const actionParameterMap = {
[AssetEditAction.Crop]: CropParameters,
[AssetEditAction.Rotate]: RotateParameters,
[AssetEditAction.Mirror]: MirrorParameters,
}; };
type AssetEditActions = AssetEditActionCrop | AssetEditActionRotate | AssetEditActionMirror; export class AssetEditsCreateDto {
const actionToClass: Record<AssetEditAction, ClassConstructor<AssetEditActions>> = {
[AssetEditAction.Crop]: AssetEditActionCrop,
[AssetEditAction.Rotate]: AssetEditActionRotate,
[AssetEditAction.Mirror]: AssetEditActionMirror,
} as const;
const getActionClass = (item: { action: AssetEditAction }): ClassConstructor<AssetEditActions> =>
actionToClass[item.action];
@ApiExtraModels(AssetEditActionRotate, AssetEditActionMirror, AssetEditActionCrop)
export class AssetEditActionListDto {
/** list of edits */
@ArrayMinSize(1) @ArrayMinSize(1)
@IsUniqueEditActions() @IsUniqueEditActions()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Transform(({ value: edits }) => @Type(() => AssetEditActionItemDto)
Array.isArray(edits) ? edits.map((item) => plainToInstance(getActionClass(item), item)) : edits, @ApiProperty({ description: 'List of edit actions to apply (crop, rotate, or mirror)' })
) edits!: AssetEditActionItemDto[];
@ApiProperty({
items: {
anyOf: Object.values(actionToClass).map((type) => ({ $ref: getSchemaPath(type) })),
discriminator: {
propertyName: 'action',
mapping: Object.fromEntries(
Object.entries(actionToClass).map(([action, type]) => [action, getSchemaPath(type)]),
),
},
},
description: 'List of edit actions to apply (crop, rotate, or mirror)',
})
edits!: AssetEditActionItem[];
} }
export class AssetEditsDto extends AssetEditActionListDto { export class AssetEditsResponseDto {
@ValidateUUID({ description: 'Asset ID to apply edits to' }) @ValidateUUID({ description: 'Asset ID these edits belong to' })
assetId!: string; assetId!: string;
@ApiProperty({
description: 'List of edit actions applied to the asset',
})
edits!: AssetEditActionItemResponseDto[];
} }

View File

@@ -9,6 +9,7 @@ rollback
-- AssetEditRepository.getAll -- AssetEditRepository.getAll
select select
"id",
"action", "action",
"parameters" "parameters"
from from

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { Kysely } from 'kysely'; import { Kysely } from 'kysely';
import { InjectKysely } from 'nestjs-kysely'; import { InjectKysely } from 'nestjs-kysely';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { AssetEditActionItem } from 'src/dtos/editing.dto'; import { AssetEditActionItem, AssetEditActionItemResponseDto } from 'src/dtos/editing.dto';
import { DB } from 'src/schema'; import { DB } from 'src/schema';
@Injectable() @Injectable()
@@ -12,7 +12,7 @@ export class AssetEditRepository {
@GenerateSql({ @GenerateSql({
params: [DummyValue.UUID], params: [DummyValue.UUID],
}) })
replaceAll(assetId: string, edits: AssetEditActionItem[]): Promise<AssetEditActionItem[]> { replaceAll(assetId: string, edits: AssetEditActionItem[]): Promise<AssetEditActionItemResponseDto[]> {
return this.db.transaction().execute(async (trx) => { return this.db.transaction().execute(async (trx) => {
await trx.deleteFrom('asset_edit').where('assetId', '=', assetId).execute(); await trx.deleteFrom('asset_edit').where('assetId', '=', assetId).execute();
@@ -20,8 +20,8 @@ export class AssetEditRepository {
return trx return trx
.insertInto('asset_edit') .insertInto('asset_edit')
.values(edits.map((edit, i) => ({ assetId, sequence: i, ...edit }))) .values(edits.map((edit, i) => ({ assetId, sequence: i, ...edit })))
.returning(['action', 'parameters']) .returning(['id', 'action', 'parameters'])
.execute() as Promise<AssetEditActionItem[]>; .execute();
} }
return []; return [];
@@ -31,12 +31,12 @@ export class AssetEditRepository {
@GenerateSql({ @GenerateSql({
params: [DummyValue.UUID], params: [DummyValue.UUID],
}) })
getAll(assetId: string): Promise<AssetEditActionItem[]> { getAll(assetId: string): Promise<AssetEditActionItemResponseDto[]> {
return this.db return this.db
.selectFrom('asset_edit') .selectFrom('asset_edit')
.select(['action', 'parameters']) .select(['id', 'action', 'parameters'])
.where('assetId', '=', assetId) .where('assetId', '=', assetId)
.orderBy('sequence', 'asc') .orderBy('sequence', 'asc')
.execute() as Promise<AssetEditActionItem[]>; .execute();
} }
} }

View File

@@ -8,7 +8,7 @@ import {
Table, Table,
Unique, Unique,
} from '@immich/sql-tools'; } from '@immich/sql-tools';
import { AssetEditAction, AssetEditActionParameter } from 'src/dtos/editing.dto'; import { AssetEditAction, AssetEditParameters } from 'src/dtos/editing.dto';
import { asset_edit_delete, asset_edit_insert } from 'src/schema/functions'; import { asset_edit_delete, asset_edit_insert } from 'src/schema/functions';
import { AssetTable } from 'src/schema/tables/asset.table'; import { AssetTable } from 'src/schema/tables/asset.table';
@@ -21,7 +21,7 @@ import { AssetTable } from 'src/schema/tables/asset.table';
when: 'pg_trigger_depth() = 0', when: 'pg_trigger_depth() = 0',
}) })
@Unique({ columns: ['assetId', 'sequence'] }) @Unique({ columns: ['assetId', 'sequence'] })
export class AssetEditTable<T extends AssetEditAction = AssetEditAction> { export class AssetEditTable {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id!: Generated<string>; id!: Generated<string>;
@@ -29,10 +29,10 @@ export class AssetEditTable<T extends AssetEditAction = AssetEditAction> {
assetId!: string; assetId!: string;
@Column() @Column()
action!: T; action!: AssetEditAction;
@Column({ type: 'jsonb' }) @Column({ type: 'jsonb' })
parameters!: AssetEditActionParameter[T]; parameters!: AssetEditParameters;
@Column({ type: 'integer' }) @Column({ type: 'integer' })
sequence!: number; sequence!: number;

View File

@@ -21,7 +21,7 @@ import {
mapStats, mapStats,
} 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 { AssetEditAction, AssetEditActionCrop, AssetEditActionListDto, AssetEditsDto } from 'src/dtos/editing.dto'; import { AssetEditAction, AssetEditActionItem, AssetEditsCreateDto, AssetEditsResponseDto } from 'src/dtos/editing.dto';
import { AssetOcrResponseDto } from 'src/dtos/ocr.dto'; import { AssetOcrResponseDto } from 'src/dtos/ocr.dto';
import { import {
AssetFileType, AssetFileType,
@@ -543,7 +543,7 @@ export class AssetService extends BaseService {
} }
} }
async getAssetEdits(auth: AuthDto, id: string): Promise<AssetEditsDto> { async getAssetEdits(auth: AuthDto, id: string): Promise<AssetEditsResponseDto> {
await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] }); await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] });
const edits = await this.assetEditRepository.getAll(id); const edits = await this.assetEditRepository.getAll(id);
return { return {
@@ -552,7 +552,7 @@ export class AssetService extends BaseService {
}; };
} }
async editAsset(auth: AuthDto, id: string, dto: AssetEditActionListDto): Promise<AssetEditsDto> { async editAsset(auth: AuthDto, id: string, dto: AssetEditsCreateDto): Promise<AssetEditsResponseDto> {
await this.requireAccess({ auth, permission: Permission.AssetEditCreate, ids: [id] }); await this.requireAccess({ auth, permission: Permission.AssetEditCreate, ids: [id] });
const asset = await this.assetRepository.getForEdit(id); const asset = await this.assetRepository.getForEdit(id);
@@ -587,12 +587,13 @@ export class AssetService extends BaseService {
throw new BadRequestException('Asset dimensions are not available for editing'); throw new BadRequestException('Asset dimensions are not available for editing');
} }
const cropIndex = dto.edits.findIndex((e) => e.action === AssetEditAction.Crop); const edits = dto.edits as AssetEditActionItem[];
if (cropIndex > 0) { const crop = edits.find((e) => e.action === AssetEditAction.Crop);
throw new BadRequestException('Crop action must be the first edit action');
}
const crop = cropIndex === -1 ? null : (dto.edits[cropIndex] as AssetEditActionCrop);
if (crop) { if (crop) {
if (edits[0].action !== AssetEditAction.Crop) {
throw new BadRequestException('Crop action must be the first edit action');
}
// check that crop parameters will not go out of bounds // check that crop parameters will not go out of bounds
const { width: assetWidth, height: assetHeight } = getDimensions(asset); const { width: assetWidth, height: assetHeight } = getDimensions(asset);
@@ -606,7 +607,7 @@ export class AssetService extends BaseService {
} }
} }
const newEdits = await this.assetEditRepository.replaceAll(id, dto.edits); const newEdits = await this.assetEditRepository.replaceAll(id, edits);
await this.jobRepository.queue({ name: JobName.AssetEditThumbnailGeneration, data: { id } }); await this.jobRepository.queue({ name: JobName.AssetEditThumbnailGeneration, data: { id } });
// Return the asset and its applied edits // Return the asset and its applied edits

View File

@@ -1,5 +1,5 @@
import { Selectable } from 'kysely'; import { Selectable } from 'kysely';
import { AssetEditAction } from 'src/dtos/editing.dto'; import { AssetEditAction, AssetEditActionItem } from 'src/dtos/editing.dto';
import { AssetEditTable } from 'src/schema/tables/asset-edit.table'; import { AssetEditTable } from 'src/schema/tables/asset-edit.table';
import { AssetFactory } from 'test/factories/asset.factory'; import { AssetFactory } from 'test/factories/asset.factory';
import { build } from 'test/factories/builder.factory'; import { build } from 'test/factories/builder.factory';
@@ -33,6 +33,6 @@ export class AssetEditFactory {
} }
build() { build() {
return { ...this.value } as Selectable<AssetEditTable<AssetEditAction.Crop>>; return { ...this.value } as Omit<Selectable<AssetEditTable>, 'action' | 'parameters'> & AssetEditActionItem;
} }
} }

View File

@@ -6,7 +6,7 @@ import { Stats } from 'node:fs';
import { Writable } from 'node:stream'; import { Writable } from 'node:stream';
import { AssetFace } from 'src/database'; import { AssetFace } from 'src/database';
import { AuthDto, LoginResponseDto } from 'src/dtos/auth.dto'; import { AuthDto, LoginResponseDto } from 'src/dtos/auth.dto';
import { AssetEditActionListDto } from 'src/dtos/editing.dto'; import { AssetEditActionItem, AssetEditsCreateDto } from 'src/dtos/editing.dto';
import { import {
AlbumUserRole, AlbumUserRole,
AssetType, AssetType,
@@ -282,8 +282,8 @@ export class MediumTestContext<S extends BaseService = BaseService> {
return { tagsAssets, result }; return { tagsAssets, result };
} }
async newEdits(assetId: string, dto: AssetEditActionListDto) { async newEdits(assetId: string, dto: AssetEditsCreateDto) {
const edits = await this.get(AssetEditRepository).replaceAll(assetId, dto.edits); const edits = await this.get(AssetEditRepository).replaceAll(assetId, dto.edits as AssetEditActionItem[]);
return { edits }; return { edits };
} }
} }

View File

@@ -887,15 +887,16 @@ describe(AssetService.name, () => {
await ctx.newExif({ assetId: asset.id, exifImageHeight: 42, exifImageWidth: 69, orientation: '1' }); await ctx.newExif({ assetId: asset.id, exifImageHeight: 42, exifImageWidth: 69, orientation: '1' });
const editAction = { action: AssetEditAction.Rotate, parameters: { angle: 90 } } as const; const editAction = { action: AssetEditAction.Rotate, parameters: { angle: 90 } } as const;
const editResponse = { ...editAction, id: expect.any(String) };
await expect(sut.editAsset(auth, asset.id, { edits: [editAction] })).resolves.toEqual({ await expect(sut.editAsset(auth, asset.id, { edits: [editAction] })).resolves.toEqual({
assetId: asset.id, assetId: asset.id,
edits: [editAction], edits: [editResponse],
}); });
await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toEqual( await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toEqual(
expect.objectContaining({ isEdited: true }), expect.objectContaining({ isEdited: true }),
); );
await expect(ctx.get(AssetEditRepository).getAll(asset.id)).resolves.toEqual([editAction]); await expect(ctx.get(AssetEditRepository).getAll(asset.id)).resolves.toEqual([editResponse]);
}); });
}); });
}); });

View File

@@ -3,12 +3,12 @@ import { transformManager } from '$lib/managers/edit/transform-manager.svelte';
import { eventManager } from '$lib/managers/event-manager.svelte'; import { eventManager } from '$lib/managers/event-manager.svelte';
import { waitForWebsocketEvent } from '$lib/stores/websocket'; import { waitForWebsocketEvent } from '$lib/stores/websocket';
import { getFormatter } from '$lib/utils/i18n'; import { getFormatter } from '$lib/utils/i18n';
import { editAsset, removeAssetEdits, type AssetEditsDto, type AssetResponseDto } from '@immich/sdk'; import { editAsset, removeAssetEdits, type AssetEditsCreateDto, type AssetResponseDto } from '@immich/sdk';
import { ConfirmModal, modalManager, toastManager } from '@immich/ui'; import { ConfirmModal, modalManager, toastManager } from '@immich/ui';
import { mdiCropRotate } from '@mdi/js'; import { mdiCropRotate } from '@mdi/js';
import type { Component } from 'svelte'; import type { Component } from 'svelte';
export type EditAction = AssetEditsDto['edits'][number]; export type EditAction = AssetEditsCreateDto['edits'][number];
export type EditActions = EditAction[]; export type EditActions = EditAction[];
export interface EditToolManager { export interface EditToolManager {
@@ -84,7 +84,7 @@ export class EditManager {
this.selectedTool = this.tools[0]; this.selectedTool = this.tools[0];
} }
async activateTool(toolType: EditToolType, asset: AssetResponseDto, edits: AssetEditsDto) { async activateTool(toolType: EditToolType, asset: AssetResponseDto, edits: AssetEditsCreateDto) {
this.hasAppliedEdits = false; this.hasAppliedEdits = false;
if (this.selectedTool?.type === toolType) { if (this.selectedTool?.type === toolType) {
return; return;
@@ -133,7 +133,7 @@ export class EditManager {
? removeAssetEdits({ id: assetId }) ? removeAssetEdits({ id: assetId })
: editAsset({ : editAsset({
id: assetId, id: assetId,
assetEditActionListDto: { assetEditsCreateDto: {
edits, edits,
}, },
})); }));