1
0
mirror of https://github.com/immich-app/immich.git synced 2025-10-31 00:18:28 +02:00

feat(server): support for read-only assets and importing existing items in the filesystem (#2715)

* Added read-only flag for assets, endpoint to trigger file import vs upload

* updated fixtures with new property

* if upload is 'read-only', ensure there is no existing asset at the designated originalPath

* added test for file import as well as detecting existing image at read-only destination location

* Added storage service test for a case where it should not move read-only assets

* upload doesn't need the read-only flag available, just importing

* default isReadOnly on import endpoint to true

* formatting fixes

* create-asset dto needs isReadOnly, so set it to false by default on create, updated api generation

* updated code to reflect changes in MR

* fixed read stream promise return type

* new index for originalPath, check for existing path on import, reglardless of user, to prevent duplicates

* refactor: import asset

* chore: open api

* chore: tests

* Added externalPath support for individual users, updated UI to allow this to be set by admin

* added missing var for externalPath in ui

* chore: open api

* fix: compilation issues

* fix: server test

* built api, fixed user-response dto to include externalPath

* reverted accidental commit

* bad commit of duplicate externalPath in user response  dto

* fixed tests to include externalPath on expected result

* fix: unit tests

* centralized supported filetypes, perform file type checking of asset and sidecar during file import process

* centralized supported filetype check method to keep regex DRY

* fixed typo

* combined migrations into one

* update api

* Removed externalPath from shared-link code, added column to admin user page whether external paths / import is enabled or not

* update mimetype

* Fixed detect correct mimetype

* revert asset-upload config

* reverted domain.constant

* refactor

* fix mime-type issue

* fix format

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Alex Phillips
2023-06-21 22:33:20 -04:00
committed by GitHub
parent 7f44d508dc
commit e171fec5aa
55 changed files with 1321 additions and 128 deletions

View File

@@ -49,6 +49,7 @@ doc/DownloadFilesDto.md
doc/ExifResponseDto.md
doc/GetAssetByTimeBucketDto.md
doc/GetAssetCountByTimeBucketDto.md
doc/ImportAssetDto.md
doc/JobApi.md
doc/JobCommand.md
doc/JobCommandDto.md
@@ -181,6 +182,7 @@ lib/model/download_files_dto.dart
lib/model/exif_response_dto.dart
lib/model/get_asset_by_time_bucket_dto.dart
lib/model/get_asset_count_by_time_bucket_dto.dart
lib/model/import_asset_dto.dart
lib/model/job_command.dart
lib/model/job_command_dto.dart
lib/model/job_counts_dto.dart
@@ -284,6 +286,7 @@ test/download_files_dto_test.dart
test/exif_response_dto_test.dart
test/get_asset_by_time_bucket_dto_test.dart
test/get_asset_count_by_time_bucket_dto_test.dart
test/import_asset_dto_test.dart
test/job_api_test.dart
test/job_command_dto_test.dart
test/job_command_test.dart

View File

@@ -108,6 +108,7 @@ Class | Method | HTTP request | Description
*AssetApi* | [**getMapMarkers**](doc//AssetApi.md#getmapmarkers) | **GET** /asset/map-marker |
*AssetApi* | [**getMemoryLane**](doc//AssetApi.md#getmemorylane) | **GET** /asset/memory-lane |
*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
*AssetApi* | [**importFile**](doc//AssetApi.md#importfile) | **POST** /asset/import |
*AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search |
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{id} |
*AssetApi* | [**updateAsset**](doc//AssetApi.md#updateasset) | **PUT** /asset/{id} |
@@ -218,6 +219,7 @@ Class | Method | HTTP request | Description
- [ExifResponseDto](doc//ExifResponseDto.md)
- [GetAssetByTimeBucketDto](doc//GetAssetByTimeBucketDto.md)
- [GetAssetCountByTimeBucketDto](doc//GetAssetCountByTimeBucketDto.md)
- [ImportAssetDto](doc//ImportAssetDto.md)
- [JobCommand](doc//JobCommand.md)
- [JobCommandDto](doc//JobCommandDto.md)
- [JobCountsDto](doc//JobCountsDto.md)

View File

@@ -29,6 +29,7 @@ Method | HTTP request | Description
[**getMapMarkers**](AssetApi.md#getmapmarkers) | **GET** /asset/map-marker |
[**getMemoryLane**](AssetApi.md#getmemorylane) | **GET** /asset/memory-lane |
[**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
[**importFile**](AssetApi.md#importfile) | **POST** /asset/import |
[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search |
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file/{id} |
[**updateAsset**](AssetApi.md#updateasset) | **PUT** /asset/{id} |
@@ -1159,6 +1160,61 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **importFile**
> AssetFileUploadResponseDto importFile(importAssetDto)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi();
final importAssetDto = ImportAssetDto(); // ImportAssetDto |
try {
final result = api_instance.importFile(importAssetDto);
print(result);
} catch (e) {
print('Exception when calling AssetApi->importFile: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**importAssetDto** | [**ImportAssetDto**](ImportAssetDto.md)| |
### Return type
[**AssetFileUploadResponseDto**](AssetFileUploadResponseDto.md)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **searchAsset**
> List<AssetResponseDto> searchAsset(searchAssetDto)
@@ -1335,7 +1391,7 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **uploadFile**
> AssetFileUploadResponseDto uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, sidecarData, isArchived, isVisible, duration)
> AssetFileUploadResponseDto uploadFile(assetType, assetData, fileExtension, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, livePhotoData, sidecarData, isReadOnly, isArchived, isVisible, duration)
@@ -1360,21 +1416,22 @@ import 'package:openapi/api.dart';
final api_instance = AssetApi();
final assetType = ; // AssetTypeEnum |
final assetData = BINARY_DATA_HERE; // MultipartFile |
final fileExtension = fileExtension_example; // String |
final deviceAssetId = deviceAssetId_example; // String |
final deviceId = deviceId_example; // String |
final fileCreatedAt = 2013-10-20T19:20:30+01:00; // DateTime |
final fileModifiedAt = 2013-10-20T19:20:30+01:00; // DateTime |
final isFavorite = true; // bool |
final fileExtension = fileExtension_example; // String |
final key = key_example; // String |
final livePhotoData = BINARY_DATA_HERE; // MultipartFile |
final sidecarData = BINARY_DATA_HERE; // MultipartFile |
final isReadOnly = true; // bool |
final isArchived = true; // bool |
final isVisible = true; // bool |
final duration = duration_example; // String |
try {
final result = api_instance.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, sidecarData, isArchived, isVisible, duration);
final result = api_instance.uploadFile(assetType, assetData, fileExtension, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, livePhotoData, sidecarData, isReadOnly, isArchived, isVisible, duration);
print(result);
} catch (e) {
print('Exception when calling AssetApi->uploadFile: $e\n');
@@ -1387,15 +1444,16 @@ Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetType** | [**AssetTypeEnum**](AssetTypeEnum.md)| |
**assetData** | **MultipartFile**| |
**fileExtension** | **String**| |
**deviceAssetId** | **String**| |
**deviceId** | **String**| |
**fileCreatedAt** | **DateTime**| |
**fileModifiedAt** | **DateTime**| |
**isFavorite** | **bool**| |
**fileExtension** | **String**| |
**key** | **String**| | [optional]
**livePhotoData** | **MultipartFile**| | [optional]
**sidecarData** | **MultipartFile**| | [optional]
**isReadOnly** | **bool**| | [optional] [default to false]
**isArchived** | **bool**| | [optional]
**isVisible** | **bool**| | [optional]
**duration** | **String**| | [optional]

View File

@@ -13,6 +13,7 @@ Name | Type | Description | Notes
**firstName** | **String** | |
**lastName** | **String** | |
**storageLabel** | **String** | | [optional]
**externalPath** | **String** | | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

26
mobile/openapi/doc/ImportAssetDto.md generated Normal file
View File

@@ -0,0 +1,26 @@
# openapi.model.ImportAssetDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**assetType** | [**AssetTypeEnum**](AssetTypeEnum.md) | |
**isReadOnly** | **bool** | | [optional] [default to true]
**assetPath** | **String** | |
**sidecarPath** | **String** | | [optional]
**deviceAssetId** | **String** | |
**deviceId** | **String** | |
**fileCreatedAt** | [**DateTime**](DateTime.md) | |
**fileModifiedAt** | [**DateTime**](DateTime.md) | |
**isFavorite** | **bool** | |
**isArchived** | **bool** | | [optional]
**isVisible** | **bool** | | [optional]
**duration** | **String** | | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -14,6 +14,7 @@ Name | Type | Description | Notes
**firstName** | **String** | | [optional]
**lastName** | **String** | | [optional]
**storageLabel** | **String** | | [optional]
**externalPath** | **String** | | [optional]
**isAdmin** | **bool** | | [optional]
**shouldChangePassword** | **bool** | | [optional]

View File

@@ -13,6 +13,7 @@ Name | Type | Description | Notes
**firstName** | **String** | |
**lastName** | **String** | |
**storageLabel** | **String** | |
**externalPath** | **String** | |
**profileImagePath** | **String** | |
**shouldChangePassword** | **bool** | |
**isAdmin** | **bool** | |

View File

@@ -85,6 +85,7 @@ part 'model/download_files_dto.dart';
part 'model/exif_response_dto.dart';
part 'model/get_asset_by_time_bucket_dto.dart';
part 'model/get_asset_count_by_time_bucket_dto.dart';
part 'model/import_asset_dto.dart';
part 'model/job_command.dart';
part 'model/job_command_dto.dart';
part 'model/job_counts_dto.dart';

View File

@@ -1123,6 +1123,53 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'POST /asset/import' operation and returns the [Response].
/// Parameters:
///
/// * [ImportAssetDto] importAssetDto (required):
Future<Response> importFileWithHttpInfo(ImportAssetDto importAssetDto,) async {
// ignore: prefer_const_declarations
final path = r'/asset/import';
// ignore: prefer_final_locals
Object? postBody = importAssetDto;
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:
///
/// * [ImportAssetDto] importAssetDto (required):
Future<AssetFileUploadResponseDto?> importFile(ImportAssetDto importAssetDto,) async {
final response = await importFileWithHttpInfo(importAssetDto,);
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), 'AssetFileUploadResponseDto',) as AssetFileUploadResponseDto;
}
return null;
}
/// Performs an HTTP 'POST /asset/search' operation and returns the [Response].
/// Parameters:
///
@@ -1307,6 +1354,8 @@ class AssetApi {
///
/// * [MultipartFile] assetData (required):
///
/// * [String] fileExtension (required):
///
/// * [String] deviceAssetId (required):
///
/// * [String] deviceId (required):
@@ -1317,20 +1366,20 @@ class AssetApi {
///
/// * [bool] isFavorite (required):
///
/// * [String] fileExtension (required):
///
/// * [String] key:
///
/// * [MultipartFile] livePhotoData:
///
/// * [MultipartFile] sidecarData:
///
/// * [bool] isReadOnly:
///
/// * [bool] isArchived:
///
/// * [bool] isVisible:
///
/// * [String] duration:
Future<Response> uploadFileWithHttpInfo(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isArchived, bool? isVisible, String? duration, }) async {
Future<Response> uploadFileWithHttpInfo(AssetTypeEnum assetType, MultipartFile assetData, String fileExtension, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isReadOnly, bool? isArchived, bool? isVisible, String? duration, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/upload';
@@ -1368,6 +1417,14 @@ class AssetApi {
mp.fields[r'sidecarData'] = sidecarData.field;
mp.files.add(sidecarData);
}
if (isReadOnly != null) {
hasFields = true;
mp.fields[r'isReadOnly'] = parameterToString(isReadOnly);
}
if (fileExtension != null) {
hasFields = true;
mp.fields[r'fileExtension'] = parameterToString(fileExtension);
}
if (deviceAssetId != null) {
hasFields = true;
mp.fields[r'deviceAssetId'] = parameterToString(deviceAssetId);
@@ -1396,10 +1453,6 @@ class AssetApi {
hasFields = true;
mp.fields[r'isVisible'] = parameterToString(isVisible);
}
if (fileExtension != null) {
hasFields = true;
mp.fields[r'fileExtension'] = parameterToString(fileExtension);
}
if (duration != null) {
hasFields = true;
mp.fields[r'duration'] = parameterToString(duration);
@@ -1425,6 +1478,8 @@ class AssetApi {
///
/// * [MultipartFile] assetData (required):
///
/// * [String] fileExtension (required):
///
/// * [String] deviceAssetId (required):
///
/// * [String] deviceId (required):
@@ -1435,21 +1490,21 @@ class AssetApi {
///
/// * [bool] isFavorite (required):
///
/// * [String] fileExtension (required):
///
/// * [String] key:
///
/// * [MultipartFile] livePhotoData:
///
/// * [MultipartFile] sidecarData:
///
/// * [bool] isReadOnly:
///
/// * [bool] isArchived:
///
/// * [bool] isVisible:
///
/// * [String] duration:
Future<AssetFileUploadResponseDto?> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isArchived, bool? isVisible, String? duration, }) async {
final response = await uploadFileWithHttpInfo(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key: key, livePhotoData: livePhotoData, sidecarData: sidecarData, isArchived: isArchived, isVisible: isVisible, duration: duration, );
Future<AssetFileUploadResponseDto?> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String fileExtension, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isReadOnly, bool? isArchived, bool? isVisible, String? duration, }) async {
final response = await uploadFileWithHttpInfo(assetType, assetData, fileExtension, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key: key, livePhotoData: livePhotoData, sidecarData: sidecarData, isReadOnly: isReadOnly, isArchived: isArchived, isVisible: isVisible, duration: duration, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

@@ -265,6 +265,8 @@ class ApiClient {
return GetAssetByTimeBucketDto.fromJson(value);
case 'GetAssetCountByTimeBucketDto':
return GetAssetCountByTimeBucketDto.fromJson(value);
case 'ImportAssetDto':
return ImportAssetDto.fromJson(value);
case 'JobCommand':
return JobCommandTypeTransformer().decode(value);
case 'JobCommandDto':

View File

@@ -18,6 +18,7 @@ class CreateUserDto {
required this.firstName,
required this.lastName,
this.storageLabel,
this.externalPath,
});
String email;
@@ -30,13 +31,16 @@ class CreateUserDto {
String? storageLabel;
String? externalPath;
@override
bool operator ==(Object other) => identical(this, other) || other is CreateUserDto &&
other.email == email &&
other.password == password &&
other.firstName == firstName &&
other.lastName == lastName &&
other.storageLabel == storageLabel;
other.storageLabel == storageLabel &&
other.externalPath == externalPath;
@override
int get hashCode =>
@@ -45,10 +49,11 @@ class CreateUserDto {
(password.hashCode) +
(firstName.hashCode) +
(lastName.hashCode) +
(storageLabel == null ? 0 : storageLabel!.hashCode);
(storageLabel == null ? 0 : storageLabel!.hashCode) +
(externalPath == null ? 0 : externalPath!.hashCode);
@override
String toString() => 'CreateUserDto[email=$email, password=$password, firstName=$firstName, lastName=$lastName, storageLabel=$storageLabel]';
String toString() => 'CreateUserDto[email=$email, password=$password, firstName=$firstName, lastName=$lastName, storageLabel=$storageLabel, externalPath=$externalPath]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -61,6 +66,11 @@ class CreateUserDto {
} else {
// json[r'storageLabel'] = null;
}
if (this.externalPath != null) {
json[r'externalPath'] = this.externalPath;
} else {
// json[r'externalPath'] = null;
}
return json;
}
@@ -88,6 +98,7 @@ class CreateUserDto {
firstName: mapValueOfType<String>(json, r'firstName')!,
lastName: mapValueOfType<String>(json, r'lastName')!,
storageLabel: mapValueOfType<String>(json, r'storageLabel'),
externalPath: mapValueOfType<String>(json, r'externalPath'),
);
}
return null;

View File

@@ -0,0 +1,232 @@
//
// 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 ImportAssetDto {
/// Returns a new [ImportAssetDto] instance.
ImportAssetDto({
required this.assetType,
this.isReadOnly = true,
required this.assetPath,
this.sidecarPath,
required this.deviceAssetId,
required this.deviceId,
required this.fileCreatedAt,
required this.fileModifiedAt,
required this.isFavorite,
this.isArchived,
this.isVisible,
this.duration,
});
AssetTypeEnum assetType;
bool isReadOnly;
String assetPath;
///
/// 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? sidecarPath;
String deviceAssetId;
String deviceId;
DateTime fileCreatedAt;
DateTime fileModifiedAt;
bool isFavorite;
///
/// 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.
///
bool? isArchived;
///
/// 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.
///
bool? isVisible;
///
/// 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? duration;
@override
bool operator ==(Object other) => identical(this, other) || other is ImportAssetDto &&
other.assetType == assetType &&
other.isReadOnly == isReadOnly &&
other.assetPath == assetPath &&
other.sidecarPath == sidecarPath &&
other.deviceAssetId == deviceAssetId &&
other.deviceId == deviceId &&
other.fileCreatedAt == fileCreatedAt &&
other.fileModifiedAt == fileModifiedAt &&
other.isFavorite == isFavorite &&
other.isArchived == isArchived &&
other.isVisible == isVisible &&
other.duration == duration;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assetType.hashCode) +
(isReadOnly.hashCode) +
(assetPath.hashCode) +
(sidecarPath == null ? 0 : sidecarPath!.hashCode) +
(deviceAssetId.hashCode) +
(deviceId.hashCode) +
(fileCreatedAt.hashCode) +
(fileModifiedAt.hashCode) +
(isFavorite.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isVisible == null ? 0 : isVisible!.hashCode) +
(duration == null ? 0 : duration!.hashCode);
@override
String toString() => 'ImportAssetDto[assetType=$assetType, isReadOnly=$isReadOnly, assetPath=$assetPath, sidecarPath=$sidecarPath, deviceAssetId=$deviceAssetId, deviceId=$deviceId, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, isFavorite=$isFavorite, isArchived=$isArchived, isVisible=$isVisible, duration=$duration]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetType'] = this.assetType;
json[r'isReadOnly'] = this.isReadOnly;
json[r'assetPath'] = this.assetPath;
if (this.sidecarPath != null) {
json[r'sidecarPath'] = this.sidecarPath;
} else {
// json[r'sidecarPath'] = null;
}
json[r'deviceAssetId'] = this.deviceAssetId;
json[r'deviceId'] = this.deviceId;
json[r'fileCreatedAt'] = this.fileCreatedAt.toUtc().toIso8601String();
json[r'fileModifiedAt'] = this.fileModifiedAt.toUtc().toIso8601String();
json[r'isFavorite'] = this.isFavorite;
if (this.isArchived != null) {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isVisible != null) {
json[r'isVisible'] = this.isVisible;
} else {
// json[r'isVisible'] = null;
}
if (this.duration != null) {
json[r'duration'] = this.duration;
} else {
// json[r'duration'] = null;
}
return json;
}
/// Returns a new [ImportAssetDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static ImportAssetDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
// Ensure that the map contains the required keys.
// Note 1: the values aren't checked for validity beyond being non-null.
// Note 2: this code is stripped in release mode!
assert(() {
requiredKeys.forEach((key) {
assert(json.containsKey(key), 'Required key "ImportAssetDto[$key]" is missing from JSON.');
assert(json[key] != null, 'Required key "ImportAssetDto[$key]" has a null value in JSON.');
});
return true;
}());
return ImportAssetDto(
assetType: AssetTypeEnum.fromJson(json[r'assetType'])!,
isReadOnly: mapValueOfType<bool>(json, r'isReadOnly') ?? true,
assetPath: mapValueOfType<String>(json, r'assetPath')!,
sidecarPath: mapValueOfType<String>(json, r'sidecarPath'),
deviceAssetId: mapValueOfType<String>(json, r'deviceAssetId')!,
deviceId: mapValueOfType<String>(json, r'deviceId')!,
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', '')!,
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', '')!,
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isVisible: mapValueOfType<bool>(json, r'isVisible'),
duration: mapValueOfType<String>(json, r'duration'),
);
}
return null;
}
static List<ImportAssetDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <ImportAssetDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = ImportAssetDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, ImportAssetDto> mapFromJson(dynamic json) {
final map = <String, ImportAssetDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = ImportAssetDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of ImportAssetDto-objects as value to a dart map
static Map<String, List<ImportAssetDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<ImportAssetDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = ImportAssetDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assetType',
'assetPath',
'deviceAssetId',
'deviceId',
'fileCreatedAt',
'fileModifiedAt',
'isFavorite',
};
}

View File

@@ -19,6 +19,7 @@ class UpdateUserDto {
this.firstName,
this.lastName,
this.storageLabel,
this.externalPath,
this.isAdmin,
this.shouldChangePassword,
});
@@ -65,6 +66,14 @@ class UpdateUserDto {
///
String? storageLabel;
///
/// 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? externalPath;
///
/// 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
@@ -89,6 +98,7 @@ class UpdateUserDto {
other.firstName == firstName &&
other.lastName == lastName &&
other.storageLabel == storageLabel &&
other.externalPath == externalPath &&
other.isAdmin == isAdmin &&
other.shouldChangePassword == shouldChangePassword;
@@ -101,11 +111,12 @@ class UpdateUserDto {
(firstName == null ? 0 : firstName!.hashCode) +
(lastName == null ? 0 : lastName!.hashCode) +
(storageLabel == null ? 0 : storageLabel!.hashCode) +
(externalPath == null ? 0 : externalPath!.hashCode) +
(isAdmin == null ? 0 : isAdmin!.hashCode) +
(shouldChangePassword == null ? 0 : shouldChangePassword!.hashCode);
@override
String toString() => 'UpdateUserDto[id=$id, email=$email, password=$password, firstName=$firstName, lastName=$lastName, storageLabel=$storageLabel, isAdmin=$isAdmin, shouldChangePassword=$shouldChangePassword]';
String toString() => 'UpdateUserDto[id=$id, email=$email, password=$password, firstName=$firstName, lastName=$lastName, storageLabel=$storageLabel, externalPath=$externalPath, isAdmin=$isAdmin, shouldChangePassword=$shouldChangePassword]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -135,6 +146,11 @@ class UpdateUserDto {
} else {
// json[r'storageLabel'] = null;
}
if (this.externalPath != null) {
json[r'externalPath'] = this.externalPath;
} else {
// json[r'externalPath'] = null;
}
if (this.isAdmin != null) {
json[r'isAdmin'] = this.isAdmin;
} else {
@@ -173,6 +189,7 @@ class UpdateUserDto {
firstName: mapValueOfType<String>(json, r'firstName'),
lastName: mapValueOfType<String>(json, r'lastName'),
storageLabel: mapValueOfType<String>(json, r'storageLabel'),
externalPath: mapValueOfType<String>(json, r'externalPath'),
isAdmin: mapValueOfType<bool>(json, r'isAdmin'),
shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword'),
);

View File

@@ -18,6 +18,7 @@ class UserResponseDto {
required this.firstName,
required this.lastName,
required this.storageLabel,
required this.externalPath,
required this.profileImagePath,
required this.shouldChangePassword,
required this.isAdmin,
@@ -37,6 +38,8 @@ class UserResponseDto {
String? storageLabel;
String? externalPath;
String profileImagePath;
bool shouldChangePassword;
@@ -58,6 +61,7 @@ class UserResponseDto {
other.firstName == firstName &&
other.lastName == lastName &&
other.storageLabel == storageLabel &&
other.externalPath == externalPath &&
other.profileImagePath == profileImagePath &&
other.shouldChangePassword == shouldChangePassword &&
other.isAdmin == isAdmin &&
@@ -74,6 +78,7 @@ class UserResponseDto {
(firstName.hashCode) +
(lastName.hashCode) +
(storageLabel == null ? 0 : storageLabel!.hashCode) +
(externalPath == null ? 0 : externalPath!.hashCode) +
(profileImagePath.hashCode) +
(shouldChangePassword.hashCode) +
(isAdmin.hashCode) +
@@ -83,7 +88,7 @@ class UserResponseDto {
(oauthId.hashCode);
@override
String toString() => 'UserResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, storageLabel=$storageLabel, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, isAdmin=$isAdmin, createdAt=$createdAt, deletedAt=$deletedAt, updatedAt=$updatedAt, oauthId=$oauthId]';
String toString() => 'UserResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, storageLabel=$storageLabel, externalPath=$externalPath, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, isAdmin=$isAdmin, createdAt=$createdAt, deletedAt=$deletedAt, updatedAt=$updatedAt, oauthId=$oauthId]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -95,6 +100,11 @@ class UserResponseDto {
json[r'storageLabel'] = this.storageLabel;
} else {
// json[r'storageLabel'] = null;
}
if (this.externalPath != null) {
json[r'externalPath'] = this.externalPath;
} else {
// json[r'externalPath'] = null;
}
json[r'profileImagePath'] = this.profileImagePath;
json[r'shouldChangePassword'] = this.shouldChangePassword;
@@ -134,6 +144,7 @@ class UserResponseDto {
firstName: mapValueOfType<String>(json, r'firstName')!,
lastName: mapValueOfType<String>(json, r'lastName')!,
storageLabel: mapValueOfType<String>(json, r'storageLabel'),
externalPath: mapValueOfType<String>(json, r'externalPath'),
profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword')!,
isAdmin: mapValueOfType<bool>(json, r'isAdmin')!,
@@ -193,6 +204,7 @@ class UserResponseDto {
'firstName',
'lastName',
'storageLabel',
'externalPath',
'profileImagePath',
'shouldChangePassword',
'isAdmin',

View File

@@ -131,6 +131,11 @@ void main() {
// TODO
});
//Future<AssetFileUploadResponseDto> importFile(ImportAssetDto importAssetDto) async
test('test importFile', () async {
// TODO
});
//Future<List<AssetResponseDto>> searchAsset(SearchAssetDto searchAssetDto) async
test('test searchAsset', () async {
// TODO
@@ -148,7 +153,7 @@ void main() {
// TODO
});
//Future<AssetFileUploadResponseDto> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, String fileExtension, { String key, MultipartFile livePhotoData, MultipartFile sidecarData, bool isArchived, bool isVisible, String duration }) async
//Future<AssetFileUploadResponseDto> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String fileExtension, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String key, MultipartFile livePhotoData, MultipartFile sidecarData, bool isReadOnly, bool isArchived, bool isVisible, String duration }) async
test('test uploadFile', () async {
// TODO
});

View File

@@ -41,6 +41,11 @@ void main() {
// TODO
});
// String externalPath
test('to test the property `externalPath`', () async {
// TODO
});
});

View File

@@ -0,0 +1,82 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for ImportAssetDto
void main() {
// final instance = ImportAssetDto();
group('test ImportAssetDto', () {
// AssetTypeEnum assetType
test('to test the property `assetType`', () async {
// TODO
});
// bool isReadOnly (default value: true)
test('to test the property `isReadOnly`', () async {
// TODO
});
// String assetPath
test('to test the property `assetPath`', () async {
// TODO
});
// String sidecarPath
test('to test the property `sidecarPath`', () async {
// TODO
});
// String deviceAssetId
test('to test the property `deviceAssetId`', () async {
// TODO
});
// String deviceId
test('to test the property `deviceId`', () async {
// TODO
});
// DateTime fileCreatedAt
test('to test the property `fileCreatedAt`', () async {
// TODO
});
// DateTime fileModifiedAt
test('to test the property `fileModifiedAt`', () async {
// TODO
});
// bool isFavorite
test('to test the property `isFavorite`', () async {
// TODO
});
// bool isArchived
test('to test the property `isArchived`', () async {
// TODO
});
// bool isVisible
test('to test the property `isVisible`', () async {
// TODO
});
// String duration
test('to test the property `duration`', () async {
// TODO
});
});
}

View File

@@ -46,6 +46,11 @@ void main() {
// TODO
});
// String externalPath
test('to test the property `externalPath`', () async {
// TODO
});
// bool isAdmin
test('to test the property `isAdmin`', () async {
// TODO

View File

@@ -41,6 +41,11 @@ void main() {
// TODO
});
// String externalPath
test('to test the property `externalPath`', () async {
// TODO
});
// String profileImagePath
test('to test the property `profileImagePath`', () async {
// TODO