refactor(server): trash endpoints (#6652)

* refactor(server): trash endpoints

* chore: open api

* chore: fix wrong rename
This commit is contained in:
Jason Rasmussen 2024-01-26 11:48:37 -05:00 committed by GitHub
parent 33757689fe
commit 96b7885583
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 967 additions and 137 deletions

View File

@ -22,7 +22,7 @@ class TrashService {
try {
List<String> remoteIds =
assetList.where((a) => a.isRemote).map((e) => e.remoteId!).toList();
await _apiService.assetApi.restoreAssets(BulkIdsDto(ids: remoteIds));
await _apiService.assetApi.restoreAssetsOld(BulkIdsDto(ids: remoteIds));
return true;
} catch (error, stack) {
_log.severe("Cannot restore assets ${error.toString()}", error, stack);
@ -32,7 +32,7 @@ class TrashService {
Future<void> emptyTrash() async {
try {
await _apiService.assetApi.emptyTrash();
await _apiService.assetApi.emptyTrashOld();
} catch (error, stack) {
_log.severe("Cannot empty trash ${error.toString()}", error, stack);
}
@ -40,7 +40,7 @@ class TrashService {
Future<void> restoreTrash() async {
try {
await _apiService.assetApi.restoreTrash();
await _apiService.assetApi.restoreTrashOld();
} catch (error, stack) {
_log.severe("Cannot restore trash ${error.toString()}", error, stack);
}

View File

@ -165,6 +165,7 @@ doc/TimeBucketSize.md
doc/ToneMapping.md
doc/TranscodeHWAccel.md
doc/TranscodePolicy.md
doc/TrashApi.md
doc/UpdateAlbumDto.md
doc/UpdateAssetDto.md
doc/UpdateLibraryDto.md
@ -199,6 +200,7 @@ lib/api/server_info_api.dart
lib/api/shared_link_api.dart
lib/api/system_config_api.dart
lib/api/tag_api.dart
lib/api/trash_api.dart
lib/api/user_api.dart
lib/api_client.dart
lib/api_exception.dart
@ -528,6 +530,7 @@ test/time_bucket_size_test.dart
test/tone_mapping_test.dart
test/transcode_hw_accel_test.dart
test/transcode_policy_test.dart
test/trash_api_test.dart
test/update_album_dto_test.dart
test/update_asset_dto_test.dart
test/update_library_dto_test.dart

View File

@ -96,7 +96,7 @@ Class | Method | HTTP request | Description
*AssetApi* | [**deleteAssets**](doc//AssetApi.md#deleteassets) | **DELETE** /asset |
*AssetApi* | [**downloadArchiveOld**](doc//AssetApi.md#downloadarchiveold) | **POST** /asset/download/archive |
*AssetApi* | [**downloadFileOld**](doc//AssetApi.md#downloadfileold) | **POST** /asset/download/{id} |
*AssetApi* | [**emptyTrash**](doc//AssetApi.md#emptytrash) | **POST** /asset/trash/empty |
*AssetApi* | [**emptyTrashOld**](doc//AssetApi.md#emptytrashold) | **POST** /asset/trash/empty |
*AssetApi* | [**getAllAssets**](doc//AssetApi.md#getallassets) | **GET** /asset |
*AssetApi* | [**getAllUserAssetsByDeviceId**](doc//AssetApi.md#getalluserassetsbydeviceid) | **GET** /asset/device/{deviceId} |
*AssetApi* | [**getAssetById**](doc//AssetApi.md#getassetbyid) | **GET** /asset/assetById/{id} |
@ -112,8 +112,8 @@ Class | Method | HTTP request | Description
*AssetApi* | [**getRandom**](doc//AssetApi.md#getrandom) | **GET** /asset/random |
*AssetApi* | [**getTimeBucket**](doc//AssetApi.md#gettimebucket) | **GET** /asset/time-bucket |
*AssetApi* | [**getTimeBuckets**](doc//AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets |
*AssetApi* | [**restoreAssets**](doc//AssetApi.md#restoreassets) | **POST** /asset/restore |
*AssetApi* | [**restoreTrash**](doc//AssetApi.md#restoretrash) | **POST** /asset/trash/restore |
*AssetApi* | [**restoreAssetsOld**](doc//AssetApi.md#restoreassetsold) | **POST** /asset/restore |
*AssetApi* | [**restoreTrashOld**](doc//AssetApi.md#restoretrashold) | **POST** /asset/trash/restore |
*AssetApi* | [**runAssetJobs**](doc//AssetApi.md#runassetjobs) | **POST** /asset/jobs |
*AssetApi* | [**searchAssets**](doc//AssetApi.md#searchassets) | **GET** /assets |
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{id} |
@ -201,6 +201,9 @@ Class | Method | HTTP request | Description
*TagApi* | [**tagAssets**](doc//TagApi.md#tagassets) | **PUT** /tag/{id}/assets |
*TagApi* | [**untagAssets**](doc//TagApi.md#untagassets) | **DELETE** /tag/{id}/assets |
*TagApi* | [**updateTag**](doc//TagApi.md#updatetag) | **PATCH** /tag/{id} |
*TrashApi* | [**emptyTrash**](doc//TrashApi.md#emptytrash) | **POST** /trash/empty |
*TrashApi* | [**restoreAssets**](doc//TrashApi.md#restoreassets) | **POST** /trash/restore/assets |
*TrashApi* | [**restoreTrash**](doc//TrashApi.md#restoretrash) | **POST** /trash/restore |
*UserApi* | [**createProfileImage**](doc//UserApi.md#createprofileimage) | **POST** /user/profile-image |
*UserApi* | [**createUser**](doc//UserApi.md#createuser) | **POST** /user |
*UserApi* | [**deleteProfileImage**](doc//UserApi.md#deleteprofileimage) | **DELETE** /user/profile-image |

View File

@ -14,7 +14,7 @@ Method | HTTP request | Description
[**deleteAssets**](AssetApi.md#deleteassets) | **DELETE** /asset |
[**downloadArchiveOld**](AssetApi.md#downloadarchiveold) | **POST** /asset/download/archive |
[**downloadFileOld**](AssetApi.md#downloadfileold) | **POST** /asset/download/{id} |
[**emptyTrash**](AssetApi.md#emptytrash) | **POST** /asset/trash/empty |
[**emptyTrashOld**](AssetApi.md#emptytrashold) | **POST** /asset/trash/empty |
[**getAllAssets**](AssetApi.md#getallassets) | **GET** /asset |
[**getAllUserAssetsByDeviceId**](AssetApi.md#getalluserassetsbydeviceid) | **GET** /asset/device/{deviceId} |
[**getAssetById**](AssetApi.md#getassetbyid) | **GET** /asset/assetById/{id} |
@ -30,8 +30,8 @@ Method | HTTP request | Description
[**getRandom**](AssetApi.md#getrandom) | **GET** /asset/random |
[**getTimeBucket**](AssetApi.md#gettimebucket) | **GET** /asset/time-bucket |
[**getTimeBuckets**](AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets |
[**restoreAssets**](AssetApi.md#restoreassets) | **POST** /asset/restore |
[**restoreTrash**](AssetApi.md#restoretrash) | **POST** /asset/trash/restore |
[**restoreAssetsOld**](AssetApi.md#restoreassetsold) | **POST** /asset/restore |
[**restoreTrashOld**](AssetApi.md#restoretrashold) | **POST** /asset/trash/restore |
[**runAssetJobs**](AssetApi.md#runassetjobs) | **POST** /asset/jobs |
[**searchAssets**](AssetApi.md#searchassets) | **GET** /assets |
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file/{id} |
@ -323,8 +323,8 @@ 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)
# **emptyTrash**
> emptyTrash()
# **emptyTrashOld**
> emptyTrashOld()
@ -349,9 +349,9 @@ import 'package:openapi/api.dart';
final api_instance = AssetApi();
try {
api_instance.emptyTrash();
api_instance.emptyTrashOld();
} catch (e) {
print('Exception when calling AssetApi->emptyTrash: $e\n');
print('Exception when calling AssetApi->emptyTrashOld: $e\n');
}
```
@ -1266,8 +1266,8 @@ 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)
# **restoreAssets**
> restoreAssets(bulkIdsDto)
# **restoreAssetsOld**
> restoreAssetsOld(bulkIdsDto)
@ -1293,9 +1293,9 @@ final api_instance = AssetApi();
final bulkIdsDto = BulkIdsDto(); // BulkIdsDto |
try {
api_instance.restoreAssets(bulkIdsDto);
api_instance.restoreAssetsOld(bulkIdsDto);
} catch (e) {
print('Exception when calling AssetApi->restoreAssets: $e\n');
print('Exception when calling AssetApi->restoreAssetsOld: $e\n');
}
```
@ -1320,8 +1320,8 @@ void (empty response body)
[[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)
# **restoreTrash**
> restoreTrash()
# **restoreTrashOld**
> restoreTrashOld()
@ -1346,9 +1346,9 @@ import 'package:openapi/api.dart';
final api_instance = AssetApi();
try {
api_instance.restoreTrash();
api_instance.restoreTrashOld();
} catch (e) {
print('Exception when calling AssetApi->restoreTrash: $e\n');
print('Exception when calling AssetApi->restoreTrashOld: $e\n');
}
```

170
mobile/openapi/doc/TrashApi.md generated Normal file
View File

@ -0,0 +1,170 @@
# openapi.api.TrashApi
## Load the API package
```dart
import 'package:openapi/api.dart';
```
All URIs are relative to */api*
Method | HTTP request | Description
------------- | ------------- | -------------
[**emptyTrash**](TrashApi.md#emptytrash) | **POST** /trash/empty |
[**restoreAssets**](TrashApi.md#restoreassets) | **POST** /trash/restore/assets |
[**restoreTrash**](TrashApi.md#restoretrash) | **POST** /trash/restore |
# **emptyTrash**
> emptyTrash()
### 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 = TrashApi();
try {
api_instance.emptyTrash();
} catch (e) {
print('Exception when calling TrashApi->emptyTrash: $e\n');
}
```
### Parameters
This endpoint does not need any parameter.
### Return type
void (empty response body)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: Not defined
[[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)
# **restoreAssets**
> restoreAssets(bulkIdsDto)
### 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 = TrashApi();
final bulkIdsDto = BulkIdsDto(); // BulkIdsDto |
try {
api_instance.restoreAssets(bulkIdsDto);
} catch (e) {
print('Exception when calling TrashApi->restoreAssets: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**bulkIdsDto** | [**BulkIdsDto**](BulkIdsDto.md)| |
### Return type
void (empty response body)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: Not defined
[[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)
# **restoreTrash**
> restoreTrash()
### 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 = TrashApi();
try {
api_instance.restoreTrash();
} catch (e) {
print('Exception when calling TrashApi->restoreTrash: $e\n');
}
```
### Parameters
This endpoint does not need any parameter.
### Return type
void (empty response body)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: Not defined
[[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)

View File

@ -47,6 +47,7 @@ part 'api/server_info_api.dart';
part 'api/shared_link_api.dart';
part 'api/system_config_api.dart';
part 'api/tag_api.dart';
part 'api/trash_api.dart';
part 'api/user_api.dart';
part 'model/api_key_create_dto.dart';

View File

@ -271,7 +271,7 @@ class AssetApi {
}
/// Performs an HTTP 'POST /asset/trash/empty' operation and returns the [Response].
Future<Response> emptyTrashWithHttpInfo() async {
Future<Response> emptyTrashOldWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/asset/trash/empty';
@ -296,8 +296,8 @@ class AssetApi {
);
}
Future<void> emptyTrash() async {
final response = await emptyTrashWithHttpInfo();
Future<void> emptyTrashOld() async {
final response = await emptyTrashOldWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@ -1327,7 +1327,7 @@ class AssetApi {
/// Parameters:
///
/// * [BulkIdsDto] bulkIdsDto (required):
Future<Response> restoreAssetsWithHttpInfo(BulkIdsDto bulkIdsDto,) async {
Future<Response> restoreAssetsOldWithHttpInfo(BulkIdsDto bulkIdsDto,) async {
// ignore: prefer_const_declarations
final path = r'/asset/restore';
@ -1355,15 +1355,15 @@ class AssetApi {
/// Parameters:
///
/// * [BulkIdsDto] bulkIdsDto (required):
Future<void> restoreAssets(BulkIdsDto bulkIdsDto,) async {
final response = await restoreAssetsWithHttpInfo(bulkIdsDto,);
Future<void> restoreAssetsOld(BulkIdsDto bulkIdsDto,) async {
final response = await restoreAssetsOldWithHttpInfo(bulkIdsDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Performs an HTTP 'POST /asset/trash/restore' operation and returns the [Response].
Future<Response> restoreTrashWithHttpInfo() async {
Future<Response> restoreTrashOldWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/asset/trash/restore';
@ -1388,8 +1388,8 @@ class AssetApi {
);
}
Future<void> restoreTrash() async {
final response = await restoreTrashWithHttpInfo();
Future<void> restoreTrashOld() async {
final response = await restoreTrashOldWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

123
mobile/openapi/lib/api/trash_api.dart generated Normal file
View File

@ -0,0 +1,123 @@
//
// 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 TrashApi {
TrashApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
final ApiClient apiClient;
/// Performs an HTTP 'POST /trash/empty' operation and returns the [Response].
Future<Response> emptyTrashWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/trash/empty';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
Future<void> emptyTrash() async {
final response = await emptyTrashWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Performs an HTTP 'POST /trash/restore/assets' operation and returns the [Response].
/// Parameters:
///
/// * [BulkIdsDto] bulkIdsDto (required):
Future<Response> restoreAssetsWithHttpInfo(BulkIdsDto bulkIdsDto,) async {
// ignore: prefer_const_declarations
final path = r'/trash/restore/assets';
// ignore: prefer_final_locals
Object? postBody = bulkIdsDto;
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:
///
/// * [BulkIdsDto] bulkIdsDto (required):
Future<void> restoreAssets(BulkIdsDto bulkIdsDto,) async {
final response = await restoreAssetsWithHttpInfo(bulkIdsDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Performs an HTTP 'POST /trash/restore' operation and returns the [Response].
Future<Response> restoreTrashWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/trash/restore';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
Future<void> restoreTrash() async {
final response = await restoreTrashWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
}

View File

@ -46,8 +46,8 @@ void main() {
// TODO
});
//Future emptyTrash() async
test('test emptyTrash', () async {
//Future emptyTrashOld() async
test('test emptyTrashOld', () async {
// TODO
});
@ -132,13 +132,13 @@ void main() {
// TODO
});
//Future restoreAssets(BulkIdsDto bulkIdsDto) async
test('test restoreAssets', () async {
//Future restoreAssetsOld(BulkIdsDto bulkIdsDto) async
test('test restoreAssetsOld', () async {
// TODO
});
//Future restoreTrash() async
test('test restoreTrash', () async {
//Future restoreTrashOld() async
test('test restoreTrashOld', () async {
// TODO
});

36
mobile/openapi/test/trash_api_test.dart generated Normal file
View File

@ -0,0 +1,36 @@
//
// 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 TrashApi
void main() {
// final instance = TrashApi();
group('tests for TrashApi', () {
//Future emptyTrash() async
test('test emptyTrash', () async {
// TODO
});
//Future restoreAssets(BulkIdsDto bulkIdsDto) async
test('test restoreAssets', () async {
// TODO
});
//Future restoreTrash() async
test('test restoreTrash', () async {
// TODO
});
});
}

View File

@ -1734,7 +1734,7 @@
},
"/asset/restore": {
"post": {
"operationId": "restoreAssets",
"operationId": "restoreAssetsOld",
"parameters": [],
"requestBody": {
"content": {
@ -2219,7 +2219,7 @@
},
"/asset/trash/empty": {
"post": {
"operationId": "emptyTrash",
"operationId": "emptyTrashOld",
"parameters": [],
"responses": {
"204": {
@ -2244,7 +2244,7 @@
},
"/asset/trash/restore": {
"post": {
"operationId": "restoreTrash",
"operationId": "restoreTrashOld",
"parameters": [],
"responses": {
"204": {
@ -5983,6 +5983,91 @@
]
}
},
"/trash/empty": {
"post": {
"operationId": "emptyTrash",
"parameters": [],
"responses": {
"204": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Trash"
]
}
},
"/trash/restore": {
"post": {
"operationId": "restoreTrash",
"parameters": [],
"responses": {
"204": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Trash"
]
}
},
"/trash/restore/assets": {
"post": {
"operationId": "restoreAssets",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkIdsDto"
}
}
},
"required": true
},
"responses": {
"204": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Trash"
]
}
},
"/user": {
"get": {
"operationId": "getAllUsers",

View File

@ -7026,7 +7026,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
emptyTrash: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
emptyTrashOld: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/asset/trash/empty`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@ -7896,9 +7896,9 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
restoreAssets: async (bulkIdsDto: BulkIdsDto, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
restoreAssetsOld: async (bulkIdsDto: BulkIdsDto, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'bulkIdsDto' is not null or undefined
assertParamExists('restoreAssets', 'bulkIdsDto', bulkIdsDto)
assertParamExists('restoreAssetsOld', 'bulkIdsDto', bulkIdsDto)
const localVarPath = `/asset/restore`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@ -7939,7 +7939,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
restoreTrash: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
restoreTrashOld: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/asset/trash/restore`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@ -8672,10 +8672,10 @@ export const AssetApiFp = function(configuration?: Configuration) {
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async emptyTrash(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.emptyTrash(options);
async emptyTrashOld(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.emptyTrashOld(options);
const index = configuration?.serverIndex ?? 0;
const operationBasePath = operationServerMap['AssetApi.emptyTrash']?.[index]?.url;
const operationBasePath = operationServerMap['AssetApi.emptyTrashOld']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
@ -8899,10 +8899,10 @@ export const AssetApiFp = function(configuration?: Configuration) {
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async restoreAssets(bulkIdsDto: BulkIdsDto, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.restoreAssets(bulkIdsDto, options);
async restoreAssetsOld(bulkIdsDto: BulkIdsDto, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.restoreAssetsOld(bulkIdsDto, options);
const index = configuration?.serverIndex ?? 0;
const operationBasePath = operationServerMap['AssetApi.restoreAssets']?.[index]?.url;
const operationBasePath = operationServerMap['AssetApi.restoreAssetsOld']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
@ -8910,10 +8910,10 @@ export const AssetApiFp = function(configuration?: Configuration) {
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async restoreTrash(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.restoreTrash(options);
async restoreTrashOld(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.restoreTrashOld(options);
const index = configuration?.serverIndex ?? 0;
const operationBasePath = operationServerMap['AssetApi.restoreTrash']?.[index]?.url;
const operationBasePath = operationServerMap['AssetApi.restoreTrashOld']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
@ -9118,8 +9118,8 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
emptyTrash(options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.emptyTrash(options).then((request) => request(axios, basePath));
emptyTrashOld(options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.emptyTrashOld(options).then((request) => request(axios, basePath));
},
/**
* Get all AssetEntity belong to the user
@ -9256,20 +9256,20 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
},
/**
*
* @param {AssetApiRestoreAssetsRequest} requestParameters Request parameters.
* @param {AssetApiRestoreAssetsOldRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
restoreAssets(requestParameters: AssetApiRestoreAssetsRequest, options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.restoreAssets(requestParameters.bulkIdsDto, options).then((request) => request(axios, basePath));
restoreAssetsOld(requestParameters: AssetApiRestoreAssetsOldRequest, options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.restoreAssetsOld(requestParameters.bulkIdsDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
restoreTrash(options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.restoreTrash(options).then((request) => request(axios, basePath));
restoreTrashOld(options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.restoreTrashOld(options).then((request) => request(axios, basePath));
},
/**
*
@ -9849,15 +9849,15 @@ export interface AssetApiGetTimeBucketsRequest {
}
/**
* Request parameters for restoreAssets operation in AssetApi.
* Request parameters for restoreAssetsOld operation in AssetApi.
* @export
* @interface AssetApiRestoreAssetsRequest
* @interface AssetApiRestoreAssetsOldRequest
*/
export interface AssetApiRestoreAssetsRequest {
export interface AssetApiRestoreAssetsOldRequest {
/**
*
* @type {BulkIdsDto}
* @memberof AssetApiRestoreAssets
* @memberof AssetApiRestoreAssetsOld
*/
readonly bulkIdsDto: BulkIdsDto
}
@ -10434,8 +10434,8 @@ export class AssetApi extends BaseAPI {
* @throws {RequiredError}
* @memberof AssetApi
*/
public emptyTrash(options?: RawAxiosRequestConfig) {
return AssetApiFp(this.configuration).emptyTrash(options).then((request) => request(this.axios, this.basePath));
public emptyTrashOld(options?: RawAxiosRequestConfig) {
return AssetApiFp(this.configuration).emptyTrashOld(options).then((request) => request(this.axios, this.basePath));
}
/**
@ -10603,13 +10603,13 @@ export class AssetApi extends BaseAPI {
/**
*
* @param {AssetApiRestoreAssetsRequest} requestParameters Request parameters.
* @param {AssetApiRestoreAssetsOldRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AssetApi
*/
public restoreAssets(requestParameters: AssetApiRestoreAssetsRequest, options?: RawAxiosRequestConfig) {
return AssetApiFp(this.configuration).restoreAssets(requestParameters.bulkIdsDto, options).then((request) => request(this.axios, this.basePath));
public restoreAssetsOld(requestParameters: AssetApiRestoreAssetsOldRequest, options?: RawAxiosRequestConfig) {
return AssetApiFp(this.configuration).restoreAssetsOld(requestParameters.bulkIdsDto, options).then((request) => request(this.axios, this.basePath));
}
/**
@ -10618,8 +10618,8 @@ export class AssetApi extends BaseAPI {
* @throws {RequiredError}
* @memberof AssetApi
*/
public restoreTrash(options?: RawAxiosRequestConfig) {
return AssetApiFp(this.configuration).restoreTrash(options).then((request) => request(this.axios, this.basePath));
public restoreTrashOld(options?: RawAxiosRequestConfig) {
return AssetApiFp(this.configuration).restoreTrashOld(options).then((request) => request(this.axios, this.basePath));
}
/**
@ -18135,6 +18135,269 @@ export class TagApi extends BaseAPI {
/**
* TrashApi - axios parameter creator
* @export
*/
export const TrashApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
emptyTrash: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/trash/empty`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {BulkIdsDto} bulkIdsDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
restoreAssets: async (bulkIdsDto: BulkIdsDto, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'bulkIdsDto' is not null or undefined
assertParamExists('restoreAssets', 'bulkIdsDto', bulkIdsDto)
const localVarPath = `/trash/restore/assets`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(bulkIdsDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
restoreTrash: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/trash/restore`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
}
};
/**
* TrashApi - functional programming interface
* @export
*/
export const TrashApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = TrashApiAxiosParamCreator(configuration)
return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async emptyTrash(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.emptyTrash(options);
const index = configuration?.serverIndex ?? 0;
const operationBasePath = operationServerMap['TrashApi.emptyTrash']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
*
* @param {BulkIdsDto} bulkIdsDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async restoreAssets(bulkIdsDto: BulkIdsDto, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.restoreAssets(bulkIdsDto, options);
const index = configuration?.serverIndex ?? 0;
const operationBasePath = operationServerMap['TrashApi.restoreAssets']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async restoreTrash(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.restoreTrash(options);
const index = configuration?.serverIndex ?? 0;
const operationBasePath = operationServerMap['TrashApi.restoreTrash']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
}
};
/**
* TrashApi - factory interface
* @export
*/
export const TrashApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = TrashApiFp(configuration)
return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
emptyTrash(options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.emptyTrash(options).then((request) => request(axios, basePath));
},
/**
*
* @param {TrashApiRestoreAssetsRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
restoreAssets(requestParameters: TrashApiRestoreAssetsRequest, options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.restoreAssets(requestParameters.bulkIdsDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
restoreTrash(options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.restoreTrash(options).then((request) => request(axios, basePath));
},
};
};
/**
* Request parameters for restoreAssets operation in TrashApi.
* @export
* @interface TrashApiRestoreAssetsRequest
*/
export interface TrashApiRestoreAssetsRequest {
/**
*
* @type {BulkIdsDto}
* @memberof TrashApiRestoreAssets
*/
readonly bulkIdsDto: BulkIdsDto
}
/**
* TrashApi - object-oriented interface
* @export
* @class TrashApi
* @extends {BaseAPI}
*/
export class TrashApi extends BaseAPI {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof TrashApi
*/
public emptyTrash(options?: RawAxiosRequestConfig) {
return TrashApiFp(this.configuration).emptyTrash(options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {TrashApiRestoreAssetsRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof TrashApi
*/
public restoreAssets(requestParameters: TrashApiRestoreAssetsRequest, options?: RawAxiosRequestConfig) {
return TrashApiFp(this.configuration).restoreAssets(requestParameters.bulkIdsDto, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof TrashApi
*/
public restoreTrash(options?: RawAxiosRequestConfig) {
return TrashApiFp(this.configuration).restoreTrash(options).then((request) => request(this.axios, this.basePath));
}
}
/**
* UserApi - axios parameter creator
* @export

View File

@ -679,25 +679,6 @@ describe(AssetService.name, () => {
});
});
describe('restoreAll', () => {
it('should require asset restore access for all ids', async () => {
await expect(
sut.deleteAll(authStub.user1, {
ids: ['asset-1'],
}),
).rejects.toBeInstanceOf(BadRequestException);
});
it('should restore a batch of assets', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1', 'asset2']));
await sut.restoreAll(authStub.user1, { ids: ['asset1', 'asset2'] });
expect(assetMock.restoreAll).toHaveBeenCalledWith(['asset1', 'asset2']);
expect(jobMock.queue.mock.calls).toEqual([]);
});
});
describe('handleAssetDeletion', () => {
beforeEach(() => {
when(jobMock.queue)

View File

@ -37,14 +37,12 @@ import {
MemoryLaneDto,
TimeBucketAssetDto,
TimeBucketDto,
TrashAction,
UpdateAssetDto,
UpdateStackParentDto,
mapStats,
} from './dto';
import {
AssetResponseDto,
BulkIdsDto,
MapMarkerResponseDto,
MemoryLaneResponseDto,
SanitizedAssetResponseDto,
@ -451,37 +449,6 @@ export class AssetService {
}
}
async handleTrashAction(auth: AuthDto, action: TrashAction): Promise<void> {
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) =>
this.assetRepository.getByUserId(pagination, auth.user.id, { trashedBefore: DateTime.now().toJSDate() }),
);
if (action == TrashAction.RESTORE_ALL) {
for await (const assets of assetPagination) {
const ids = assets.map((a) => a.id);
await this.assetRepository.restoreAll(ids);
this.communicationRepository.send(ClientEvent.ASSET_RESTORE, auth.user.id, ids);
}
return;
}
if (action == TrashAction.EMPTY_ALL) {
for await (const assets of assetPagination) {
await this.jobRepository.queueAll(
assets.map((asset) => ({ name: JobName.ASSET_DELETION, data: { id: asset.id } })),
);
}
return;
}
}
async restoreAll(auth: AuthDto, dto: BulkIdsDto): Promise<void> {
const { ids } = dto;
await this.access.requirePermission(auth, Permission.ASSET_RESTORE, ids);
await this.assetRepository.restoreAll(ids);
this.communicationRepository.send(ClientEvent.ASSET_RESTORE, auth.user.id, ids);
}
async updateStackParent(auth: AuthDto, dto: UpdateStackParentDto): Promise<void> {
const { oldParentId, newParentId } = dto;
await this.access.requirePermission(auth, Permission.ASSET_READ, oldParentId);

View File

@ -246,11 +246,6 @@ export class RandomAssetsDto {
count?: number;
}
export enum TrashAction {
EMPTY_ALL = 'empty-all',
RESTORE_ALL = 'restore-all',
}
export class AssetBulkDeleteDto extends BulkIdsDto {
@Optional()
@IsBoolean()

View File

@ -22,6 +22,7 @@ import { StorageService } from './storage';
import { StorageTemplateService } from './storage-template';
import { SystemConfigService } from './system-config';
import { TagService } from './tag';
import { TrashService } from './trash';
import { UserService } from './user';
const providers: Provider[] = [
@ -48,6 +49,7 @@ const providers: Provider[] = [
StorageTemplateService,
SystemConfigService,
TagService,
TrashService,
UserService,
];

View File

@ -26,4 +26,5 @@ export * from './storage';
export * from './storage-template';
export * from './system-config';
export * from './tag';
export * from './trash';
export * from './user';

View File

@ -0,0 +1 @@
export * from './trash.service';

View File

@ -0,0 +1,87 @@
import { BadRequestException } from '@nestjs/common';
import {
IAccessRepositoryMock,
assetStub,
authStub,
newAccessRepositoryMock,
newAssetRepositoryMock,
newCommunicationRepositoryMock,
newJobRepositoryMock,
} from '@test';
import { JobName } from '..';
import { ClientEvent, IAssetRepository, ICommunicationRepository, IJobRepository } from '../repositories';
import { TrashService } from './trash.service';
describe(TrashService.name, () => {
let sut: TrashService;
let accessMock: IAccessRepositoryMock;
let assetMock: jest.Mocked<IAssetRepository>;
let jobMock: jest.Mocked<IJobRepository>;
let communicationMock: jest.Mocked<ICommunicationRepository>;
it('should work', () => {
expect(sut).toBeDefined();
});
beforeEach(async () => {
accessMock = newAccessRepositoryMock();
assetMock = newAssetRepositoryMock();
communicationMock = newCommunicationRepositoryMock();
jobMock = newJobRepositoryMock();
sut = new TrashService(accessMock, assetMock, jobMock, communicationMock);
});
describe('restoreAssets', () => {
it('should require asset restore access for all ids', async () => {
await expect(
sut.restoreAssets(authStub.user1, {
ids: ['asset-1'],
}),
).rejects.toBeInstanceOf(BadRequestException);
});
it('should restore a batch of assets', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1', 'asset2']));
await sut.restoreAssets(authStub.user1, { ids: ['asset1', 'asset2'] });
expect(assetMock.restoreAll).toHaveBeenCalledWith(['asset1', 'asset2']);
expect(jobMock.queue.mock.calls).toEqual([]);
});
});
describe('restore', () => {
it('should handle an empty trash', async () => {
assetMock.getByUserId.mockResolvedValue({ items: [], hasNextPage: false });
await expect(sut.restore(authStub.user1)).resolves.toBeUndefined();
expect(assetMock.restoreAll).not.toHaveBeenCalled();
expect(communicationMock.send).not.toHaveBeenCalled();
});
it('should restore and notify', async () => {
assetMock.getByUserId.mockResolvedValue({ items: [assetStub.image], hasNextPage: false });
await expect(sut.restore(authStub.user1)).resolves.toBeUndefined();
expect(assetMock.restoreAll).toHaveBeenCalledWith([assetStub.image.id]);
expect(communicationMock.send).toHaveBeenCalledWith(ClientEvent.ASSET_RESTORE, authStub.user1.user.id, [
assetStub.image.id,
]);
});
});
describe('empty', () => {
it('should handle an empty trash', async () => {
assetMock.getByUserId.mockResolvedValue({ items: [], hasNextPage: false });
await expect(sut.empty(authStub.user1)).resolves.toBeUndefined();
expect(jobMock.queueAll).toHaveBeenCalledWith([]);
});
it('should empty the trash', async () => {
assetMock.getByUserId.mockResolvedValue({ items: [assetStub.image], hasNextPage: false });
await expect(sut.empty(authStub.user1)).resolves.toBeUndefined();
expect(jobMock.queueAll).toHaveBeenCalledWith([
{ name: JobName.ASSET_DELETION, data: { id: assetStub.image.id } },
]);
});
});
});

View File

@ -0,0 +1,65 @@
import { Inject } from '@nestjs/common';
import { DateTime } from 'luxon';
import { AccessCore, Permission } from '../access';
import { BulkIdsDto } from '../asset';
import { AuthDto } from '../auth';
import { usePagination } from '../domain.util';
import { JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job';
import {
ClientEvent,
IAccessRepository,
IAssetRepository,
ICommunicationRepository,
IJobRepository,
} from '../repositories';
export class TrashService {
private access: AccessCore;
constructor(
@Inject(IAccessRepository) accessRepository: IAccessRepository,
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
@Inject(IJobRepository) private jobRepository: IJobRepository,
@Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository,
) {
this.access = AccessCore.create(accessRepository);
}
async restoreAssets(auth: AuthDto, dto: BulkIdsDto): Promise<void> {
const { ids } = dto;
await this.access.requirePermission(auth, Permission.ASSET_RESTORE, ids);
await this.restoreAndSend(auth, ids);
}
async restore(auth: AuthDto): Promise<void> {
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) =>
this.assetRepository.getByUserId(pagination, auth.user.id, { trashedBefore: DateTime.now().toJSDate() }),
);
for await (const assets of assetPagination) {
const ids = assets.map((a) => a.id);
await this.restoreAndSend(auth, ids);
}
}
async empty(auth: AuthDto): Promise<void> {
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) =>
this.assetRepository.getByUserId(pagination, auth.user.id, { trashedBefore: DateTime.now().toJSDate() }),
);
for await (const assets of assetPagination) {
await this.jobRepository.queueAll(
assets.map((asset) => ({ name: JobName.ASSET_DELETION, data: { id: asset.id } })),
);
}
}
private async restoreAndSend(auth: AuthDto, ids: string[]) {
if (ids.length === 0) {
return;
}
await this.assetRepository.restoreAll(ids);
this.communicationRepository.send(ClientEvent.ASSET_RESTORE, auth.user.id, ids);
}
}

View File

@ -31,6 +31,7 @@ import {
SharedLinkController,
SystemConfigController,
TagController,
TrashController,
UserController,
} from './controllers';
import { ErrorInterceptor, FileUploadInterceptor } from './interceptors';
@ -64,6 +65,7 @@ import { ErrorInterceptor, FileUploadInterceptor } from './interceptors';
SharedLinkController,
SystemConfigController,
TagController,
TrashController,
UserController,
PersonController,
],

View File

@ -22,7 +22,7 @@ import {
TimeBucketAssetDto,
TimeBucketDto,
TimeBucketResponseDto,
TrashAction,
TrashService,
UpdateAssetDto as UpdateDto,
UpdateStackParentDto,
} from '@app/domain';
@ -69,6 +69,7 @@ export class AssetController {
constructor(
private service: AssetService,
private downloadService: DownloadService,
private trashService: TrashService,
) {}
@Get('map-marker')
@ -165,22 +166,31 @@ export class AssetController {
return this.service.deleteAll(auth, dto);
}
/**
* @deprecated use `POST /trash/restore/assets`
*/
@Post('restore')
@HttpCode(HttpStatus.NO_CONTENT)
restoreAssets(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
return this.service.restoreAll(auth, dto);
restoreAssetsOld(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
return this.trashService.restoreAssets(auth, dto);
}
/**
* @deprecated use `POST /trash/empty`
*/
@Post('trash/empty')
@HttpCode(HttpStatus.NO_CONTENT)
emptyTrash(@Auth() auth: AuthDto): Promise<void> {
return this.service.handleTrashAction(auth, TrashAction.EMPTY_ALL);
emptyTrashOld(@Auth() auth: AuthDto): Promise<void> {
return this.trashService.empty(auth);
}
/**
* @deprecated use `POST /trash/restore`
*/
@Post('trash/restore')
@HttpCode(HttpStatus.NO_CONTENT)
restoreTrash(@Auth() auth: AuthDto): Promise<void> {
return this.service.handleTrashAction(auth, TrashAction.RESTORE_ALL);
restoreTrashOld(@Auth() auth: AuthDto): Promise<void> {
return this.trashService.restore(auth);
}
@Put('stack/parent')

View File

@ -17,4 +17,5 @@ export * from './server-info.controller';
export * from './shared-link.controller';
export * from './system-config.controller';
export * from './tag.controller';
export * from './trash.controller';
export * from './user.controller';

View File

@ -0,0 +1,31 @@
import { AuthDto, BulkIdsDto, TrashService } from '@app/domain';
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Auth, Authenticated } from '../app.guard';
import { UseValidation } from '../app.utils';
@ApiTags('Trash')
@Controller('trash')
@Authenticated()
@UseValidation()
export class TrashController {
constructor(private service: TrashService) {}
@Post('empty')
@HttpCode(HttpStatus.NO_CONTENT)
emptyTrash(@Auth() auth: AuthDto): Promise<void> {
return this.service.empty(auth);
}
@Post('restore')
@HttpCode(HttpStatus.NO_CONTENT)
restoreTrash(@Auth() auth: AuthDto): Promise<void> {
return this.service.restore(auth);
}
@Post('restore/assets')
@HttpCode(HttpStatus.NO_CONTENT)
restoreAssets(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
return this.service.restoreAssets(auth, dto);
}
}

View File

@ -19,6 +19,7 @@ import {
ServerInfoApi,
SharedLinkApi,
SystemConfigApi,
TrashApi,
UserApi,
UserApiFp,
base,
@ -46,6 +47,7 @@ class ImmichApi {
public personApi: PersonApi;
public systemConfigApi: SystemConfigApi;
public userApi: UserApi;
public trashApi: TrashApi;
private config: configuration.Configuration;
private key?: string;
@ -75,6 +77,7 @@ class ImmichApi {
this.personApi = new PersonApi(this.config);
this.systemConfigApi = new SystemConfigApi(this.config);
this.userApi = new UserApi(this.config);
this.trashApi = new TrashApi(this.config);
}
private createUrl(path: string, params?: Record<string, unknown>) {

View File

@ -22,7 +22,7 @@
try {
const ids = Array.from(getAssets()).map((a) => a.id);
await api.assetApi.restoreAssets({ bulkIdsDto: { ids } });
await api.trashApi.restoreAssets({ bulkIdsDto: { ids } });
onRestore?.(ids);
notificationController.show({

View File

@ -37,7 +37,7 @@
const handleEmptyTrash = async () => {
isShowEmptyConfirmation = false;
try {
await api.assetApi.emptyTrash();
await api.trashApi.emptyTrash();
notificationController.show({
message: `Empty trash initiated. Refresh the page to see the changes`,
@ -50,7 +50,7 @@
const handleRestoreTrash = async () => {
try {
await api.assetApi.restoreTrash();
await api.trashApi.restoreTrash();
notificationController.show({
message: `Restore trash initiated. Refresh the page to see the changes`,