1
0
mirror of https://github.com/immich-app/immich.git synced 2025-08-09 23:17:29 +02:00

feat(server,web) Semantic import path validation (#7076)

* add library validation api

* chore: open api

* show warning i UI

* add flex row

* fix e2e

* tests

* fix tests

* enforce path validation

* enforce validation on refresh

* return 400 on bad import path

* add limits to import paths

* set response code to 200

* fix e2e

* fix lint

* fix test

* restore e2e folder

* fix import

* use startsWith

* icon color

* notify user of failed validation

* add parent div to validation

* add docs to the import validation

* improve library troubleshooting docs

* fix button alignment

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Jonathan Jogenfors
2024-02-20 16:53:12 +01:00
committed by GitHub
parent e7a875eadd
commit b3c7bebbd4
32 changed files with 1472 additions and 75 deletions

View File

@@ -209,6 +209,9 @@ part 'model/user_avatar_color.dart';
part 'model/user_dto.dart';
part 'model/user_response_dto.dart';
part 'model/validate_access_token_response_dto.dart';
part 'model/validate_library_dto.dart';
part 'model/validate_library_import_path_response_dto.dart';
part 'model/validate_library_response_dto.dart';
part 'model/video_codec.dart';

View File

@@ -378,4 +378,56 @@ class LibraryApi {
}
return null;
}
/// Performs an HTTP 'POST /library/{id}/validate' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
///
/// * [ValidateLibraryDto] validateLibraryDto (required):
Future<Response> validateWithHttpInfo(String id, ValidateLibraryDto validateLibraryDto,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}/validate'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody = validateLibraryDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
///
/// * [ValidateLibraryDto] validateLibraryDto (required):
Future<ValidateLibraryResponseDto?> validate(String id, ValidateLibraryDto validateLibraryDto,) async {
final response = await validateWithHttpInfo(id, validateLibraryDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ValidateLibraryResponseDto',) as ValidateLibraryResponseDto;
}
return null;
}
}

View File

@@ -500,6 +500,12 @@ class ApiClient {
return UserResponseDto.fromJson(value);
case 'ValidateAccessTokenResponseDto':
return ValidateAccessTokenResponseDto.fromJson(value);
case 'ValidateLibraryDto':
return ValidateLibraryDto.fromJson(value);
case 'ValidateLibraryImportPathResponseDto':
return ValidateLibraryImportPathResponseDto.fromJson(value);
case 'ValidateLibraryResponseDto':
return ValidateLibraryResponseDto.fromJson(value);
case 'VideoCodec':
return VideoCodecTypeTransformer().decode(value);
default:

View File

@@ -0,0 +1,108 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class ValidateLibraryDto {
/// Returns a new [ValidateLibraryDto] instance.
ValidateLibraryDto({
this.exclusionPatterns = const [],
this.importPaths = const [],
});
List<String> exclusionPatterns;
List<String> importPaths;
@override
bool operator ==(Object other) => identical(this, other) || other is ValidateLibraryDto &&
_deepEquality.equals(other.exclusionPatterns, exclusionPatterns) &&
_deepEquality.equals(other.importPaths, importPaths);
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(exclusionPatterns.hashCode) +
(importPaths.hashCode);
@override
String toString() => 'ValidateLibraryDto[exclusionPatterns=$exclusionPatterns, importPaths=$importPaths]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'exclusionPatterns'] = this.exclusionPatterns;
json[r'importPaths'] = this.importPaths;
return json;
}
/// Returns a new [ValidateLibraryDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static ValidateLibraryDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return ValidateLibraryDto(
exclusionPatterns: json[r'exclusionPatterns'] is Iterable
? (json[r'exclusionPatterns'] as Iterable).cast<String>().toList(growable: false)
: const [],
importPaths: json[r'importPaths'] is Iterable
? (json[r'importPaths'] as Iterable).cast<String>().toList(growable: false)
: const [],
);
}
return null;
}
static List<ValidateLibraryDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <ValidateLibraryDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = ValidateLibraryDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, ValidateLibraryDto> mapFromJson(dynamic json) {
final map = <String, ValidateLibraryDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = ValidateLibraryDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of ValidateLibraryDto-objects as value to a dart map
static Map<String, List<ValidateLibraryDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<ValidateLibraryDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = ValidateLibraryDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}

View File

@@ -0,0 +1,122 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class ValidateLibraryImportPathResponseDto {
/// Returns a new [ValidateLibraryImportPathResponseDto] instance.
ValidateLibraryImportPathResponseDto({
required this.importPath,
this.isValid = false,
this.message,
});
String importPath;
bool isValid;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? message;
@override
bool operator ==(Object other) => identical(this, other) || other is ValidateLibraryImportPathResponseDto &&
other.importPath == importPath &&
other.isValid == isValid &&
other.message == message;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(importPath.hashCode) +
(isValid.hashCode) +
(message == null ? 0 : message!.hashCode);
@override
String toString() => 'ValidateLibraryImportPathResponseDto[importPath=$importPath, isValid=$isValid, message=$message]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'importPath'] = this.importPath;
json[r'isValid'] = this.isValid;
if (this.message != null) {
json[r'message'] = this.message;
} else {
// json[r'message'] = null;
}
return json;
}
/// Returns a new [ValidateLibraryImportPathResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static ValidateLibraryImportPathResponseDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return ValidateLibraryImportPathResponseDto(
importPath: mapValueOfType<String>(json, r'importPath')!,
isValid: mapValueOfType<bool>(json, r'isValid') ?? false,
message: mapValueOfType<String>(json, r'message'),
);
}
return null;
}
static List<ValidateLibraryImportPathResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <ValidateLibraryImportPathResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = ValidateLibraryImportPathResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, ValidateLibraryImportPathResponseDto> mapFromJson(dynamic json) {
final map = <String, ValidateLibraryImportPathResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = ValidateLibraryImportPathResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of ValidateLibraryImportPathResponseDto-objects as value to a dart map
static Map<String, List<ValidateLibraryImportPathResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<ValidateLibraryImportPathResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = ValidateLibraryImportPathResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'importPath',
};
}

View File

@@ -0,0 +1,97 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class ValidateLibraryResponseDto {
/// Returns a new [ValidateLibraryResponseDto] instance.
ValidateLibraryResponseDto({
this.importPaths = const [],
});
List<ValidateLibraryImportPathResponseDto> importPaths;
@override
bool operator ==(Object other) => identical(this, other) || other is ValidateLibraryResponseDto &&
_deepEquality.equals(other.importPaths, importPaths);
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(importPaths.hashCode);
@override
String toString() => 'ValidateLibraryResponseDto[importPaths=$importPaths]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'importPaths'] = this.importPaths;
return json;
}
/// Returns a new [ValidateLibraryResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static ValidateLibraryResponseDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return ValidateLibraryResponseDto(
importPaths: ValidateLibraryImportPathResponseDto.listFromJson(json[r'importPaths']),
);
}
return null;
}
static List<ValidateLibraryResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <ValidateLibraryResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = ValidateLibraryResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, ValidateLibraryResponseDto> mapFromJson(dynamic json) {
final map = <String, ValidateLibraryResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = ValidateLibraryResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of ValidateLibraryResponseDto-objects as value to a dart map
static Map<String, List<ValidateLibraryResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<ValidateLibraryResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = ValidateLibraryResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}